Skip to main content

surrealdb_sql/
access_type.rs

1use common::fmt::Fmt;
2use rand::distr::{Alphanumeric, SampleString};
3use surrealdb_types::{SqlFormat, ToSql, write_sql};
4
5use super::Expr;
6use crate::{Algorithm, CoverStmts, Literal};
7
8pub fn random_key() -> String {
9	Alphanumeric.sample_string(&mut rand::rng(), 128)
10}
11
12/// The type of access methods available
13#[derive(Debug, Clone, PartialEq, Eq)]
14#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
15pub enum AccessType {
16	Record(Box<RecordAccess>),
17	Jwt(JwtAccess),
18	Bearer(BearerAccess),
19}
20
21impl Default for AccessType {
22	fn default() -> Self {
23		// Access type defaults to the most specific
24		Self::Record(Box::new(RecordAccess {
25			..Default::default()
26		}))
27	}
28}
29
30impl ToSql for AccessType {
31	fn fmt_sql(&self, f: &mut String, sql_fmt: SqlFormat) {
32		match self {
33			AccessType::Jwt(ac) => {
34				write_sql!(f, sql_fmt, "JWT {}", ac);
35			}
36			AccessType::Record(ac) => {
37				write_sql!(f, sql_fmt, "RECORD");
38				if let Some(ref v) = ac.signup {
39					write_sql!(f, sql_fmt, " SIGNUP {}", CoverStmts(v));
40				}
41				if let Some(ref v) = ac.signin {
42					write_sql!(f, sql_fmt, " SIGNIN {}", CoverStmts(v));
43				}
44				if ac.bearer.is_some() {
45					write_sql!(f, sql_fmt, " WITH REFRESH")
46				}
47				write_sql!(f, sql_fmt, " WITH JWT {}", ac.jwt);
48			}
49			AccessType::Bearer(ac) => {
50				write_sql!(f, sql_fmt, "BEARER");
51				match ac.subject {
52					BearerAccessSubject::User => write_sql!(f, sql_fmt, " FOR USER"),
53					BearerAccessSubject::Record => write_sql!(f, sql_fmt, " FOR RECORD"),
54				}
55			}
56		}
57	}
58}
59
60impl AccessType {
61	/// Returns whether or not the access method can issue non-token grants
62	/// In this context, token refers exclusively to JWT
63	#[allow(dead_code)]
64	pub fn can_issue_grants(&self) -> bool {
65		match self {
66			// The JWT access method cannot issue stateful grants.
67			AccessType::Jwt(_) => false,
68			// The record access method can be used to issue grants if defined with bearer AKA
69			// refresh.
70			AccessType::Record(ac) => ac.bearer.is_some(),
71			AccessType::Bearer(_) => true,
72		}
73	}
74	/// Returns whether or not the access method can issue tokens
75	/// In this context, tokens refers exclusively to JWT
76	#[allow(dead_code)]
77	pub fn can_issue_tokens(&self) -> bool {
78		match self {
79			// The JWT access method can only issue tokens if an issuer is set
80			AccessType::Jwt(jwt) => jwt.issue.is_some(),
81			_ => true,
82		}
83	}
84}
85
86#[derive(Debug, Clone, Eq, PartialEq)]
87pub struct JwtAccess {
88	// Verify is required
89	pub verify: JwtAccessVerify,
90	// Issue is optional
91	// It is possible to only verify externally issued tokens
92	pub issue: Option<JwtAccessIssue>,
93	/// Accepted values for the token `aud` claim. When set, verification
94	/// requires the claim to be present and to intersect this list.
95	pub audience: Option<Vec<Expr>>,
96}
97
98//TODO: Move this logic out of the parser
99impl Default for JwtAccess {
100	fn default() -> Self {
101		// Defaults to HS512 with a randomly generated key
102		let alg = Algorithm::Hs512;
103		let key = random_key();
104		// By default the access method can verify and issue tokens
105		Self {
106			verify: JwtAccessVerify::Key(JwtAccessVerifyKey {
107				alg,
108				key: Expr::Literal(Literal::String(key.as_str().into())),
109			}),
110			issue: Some(JwtAccessIssue {
111				alg,
112				key: Expr::Literal(Literal::String(key.into())),
113			}),
114			audience: None,
115		}
116	}
117}
118
119impl ToSql for JwtAccess {
120	fn fmt_sql(&self, f: &mut String, sql_fmt: SqlFormat) {
121		match &self.verify {
122			JwtAccessVerify::Key(v) => {
123				write_sql!(f, sql_fmt, "ALGORITHM {} KEY {}", v.alg, CoverStmts(&v.key));
124			}
125			JwtAccessVerify::Jwks(v) => {
126				write_sql!(f, sql_fmt, "URL {}", CoverStmts(&v.url));
127			}
128		}
129		if let Some(aud) = &self.audience {
130			write_sql!(
131				f,
132				sql_fmt,
133				" AUDIENCE {}",
134				Fmt::comma_separated(aud.iter().map(CoverStmts))
135			);
136		}
137		if let Some(iss) = &self.issue {
138			write_sql!(f, sql_fmt, " WITH ISSUER KEY {}", CoverStmts(&iss.key));
139		}
140	}
141}
142
143#[derive(Debug, Clone, Eq, PartialEq)]
144#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
145pub struct JwtAccessIssue {
146	pub alg: Algorithm,
147	pub key: Expr,
148}
149
150impl Default for JwtAccessIssue {
151	fn default() -> Self {
152		// TODO: Move this computation out of the AST
153		Self {
154			// Defaults to HS512
155			alg: Algorithm::Hs512,
156			// Avoid defaulting to empty key
157			key: Expr::Literal(Literal::String(random_key().into())),
158		}
159	}
160}
161
162#[derive(Debug, Clone, Eq, PartialEq)]
163#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
164pub enum JwtAccessVerify {
165	Key(JwtAccessVerifyKey),
166	Jwks(JwtAccessVerifyJwks),
167}
168
169#[derive(Debug, Clone, Eq, PartialEq)]
170#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
171pub struct JwtAccessVerifyKey {
172	pub alg: Algorithm,
173	pub key: Expr,
174}
175
176#[derive(Debug, Clone, Eq, PartialEq)]
177#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
178pub struct JwtAccessVerifyJwks {
179	pub url: Expr,
180}
181
182#[derive(Debug, Clone, Default, PartialEq, Eq)]
183#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
184pub struct RecordAccess {
185	pub signup: Option<Expr>,
186	pub signin: Option<Expr>,
187	pub jwt: JwtAccess,
188	pub bearer: Option<BearerAccess>,
189}
190
191#[derive(Debug, Clone, Eq, PartialEq)]
192#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
193pub struct BearerAccess {
194	pub kind: BearerAccessType,
195	pub subject: BearerAccessSubject,
196	pub jwt: JwtAccess,
197}
198
199impl Default for BearerAccess {
200	fn default() -> Self {
201		Self {
202			kind: BearerAccessType::Bearer,
203			subject: BearerAccessSubject::User,
204			jwt: JwtAccess::default(),
205		}
206	}
207}
208
209#[derive(Debug, Hash, Clone, Eq, PartialEq, PartialOrd)]
210#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
211pub enum BearerAccessType {
212	Bearer,
213	Refresh,
214}
215
216#[derive(Debug, Hash, Clone, Eq, PartialEq, PartialOrd)]
217#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
218pub enum BearerAccessSubject {
219	Record,
220	User,
221}