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				// `ac.jwt` is deliberately not rendered, which makes this the
56				// one access type whose text does not round-trip: reparsing it
57				// mints a fresh key rather than restoring this one.
58				//
59				// SECURITY: the alternative is worse. Every surface that
60				// renders an access redacts its symmetric keys first
61				// (`DefineAccessStatement::redact`), export included, so
62				// rendering the clause emits executable SurrealQL that names
63				// `[REDACTED]` as the signing secret — and a database restored
64				// from that dump verifies tokens anyone can forge. A random key
65				// is unusable; a published one is worse than unusable. The same
66				// render reaches the tracing span, the slow log and observers,
67				// where a real key does not belong either.
68				//
69				// Rendering this clause is correct only once redaction and
70				// restorability stop being the same code path.
71			}
72		}
73	}
74}
75
76impl AccessType {
77	/// Returns whether or not the access method can issue non-token grants
78	/// In this context, token refers exclusively to JWT
79	#[allow(dead_code)]
80	pub fn can_issue_grants(&self) -> bool {
81		match self {
82			// The JWT access method cannot issue stateful grants.
83			AccessType::Jwt(_) => false,
84			// The record access method can be used to issue grants if defined with bearer AKA
85			// refresh.
86			AccessType::Record(ac) => ac.bearer.is_some(),
87			AccessType::Bearer(_) => true,
88		}
89	}
90	/// Returns whether or not the access method can issue tokens
91	/// In this context, tokens refers exclusively to JWT
92	#[allow(dead_code)]
93	pub fn can_issue_tokens(&self) -> bool {
94		match self {
95			// The JWT access method can only issue tokens if an issuer is set
96			AccessType::Jwt(jwt) => jwt.issue.is_some(),
97			_ => true,
98		}
99	}
100}
101
102#[derive(Debug, Clone, Eq, PartialEq)]
103pub struct JwtAccess {
104	// Verify is required
105	pub verify: JwtAccessVerify,
106	// Issue is optional
107	// It is possible to only verify externally issued tokens
108	pub issue: Option<JwtAccessIssue>,
109	/// Accepted values for the token `aud` claim. When set, verification
110	/// requires the claim to be present and to intersect this list.
111	pub audience: Option<Vec<Expr>>,
112}
113
114//TODO: Move this logic out of the parser
115impl Default for JwtAccess {
116	fn default() -> Self {
117		// Defaults to HS512 with a randomly generated key
118		let alg = Algorithm::Hs512;
119		let key = random_key();
120		// By default the access method can verify and issue tokens
121		Self {
122			verify: JwtAccessVerify::Key(JwtAccessVerifyKey {
123				alg,
124				key: Expr::Literal(Literal::String(key.as_str().into())),
125			}),
126			issue: Some(JwtAccessIssue {
127				alg,
128				key: Expr::Literal(Literal::String(key.into())),
129			}),
130			audience: None,
131		}
132	}
133}
134
135impl ToSql for JwtAccess {
136	fn fmt_sql(&self, f: &mut String, sql_fmt: SqlFormat) {
137		match &self.verify {
138			JwtAccessVerify::Key(v) => {
139				write_sql!(f, sql_fmt, "ALGORITHM {} KEY {}", v.alg, CoverStmts(&v.key));
140			}
141			JwtAccessVerify::Jwks(v) => {
142				write_sql!(f, sql_fmt, "URL {}", CoverStmts(&v.url));
143			}
144		}
145		if let Some(aud) = &self.audience {
146			write_sql!(
147				f,
148				sql_fmt,
149				" AUDIENCE {}",
150				Fmt::comma_separated(aud.iter().map(CoverStmts))
151			);
152		}
153		if let Some(iss) = &self.issue {
154			write_sql!(f, sql_fmt, " WITH ISSUER KEY {}", CoverStmts(&iss.key));
155		}
156	}
157}
158
159#[derive(Debug, Clone, Eq, PartialEq)]
160#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
161pub struct JwtAccessIssue {
162	pub alg: Algorithm,
163	pub key: Expr,
164}
165
166impl Default for JwtAccessIssue {
167	fn default() -> Self {
168		// TODO: Move this computation out of the AST
169		Self {
170			// Defaults to HS512
171			alg: Algorithm::Hs512,
172			// Avoid defaulting to empty key
173			key: Expr::Literal(Literal::String(random_key().into())),
174		}
175	}
176}
177
178#[derive(Debug, Clone, Eq, PartialEq)]
179#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
180pub enum JwtAccessVerify {
181	Key(JwtAccessVerifyKey),
182	Jwks(JwtAccessVerifyJwks),
183}
184
185#[derive(Debug, Clone, Eq, PartialEq)]
186#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
187pub struct JwtAccessVerifyKey {
188	pub alg: Algorithm,
189	pub key: Expr,
190}
191
192#[derive(Debug, Clone, Eq, PartialEq)]
193#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
194pub struct JwtAccessVerifyJwks {
195	pub url: Expr,
196}
197
198#[derive(Debug, Clone, Default, PartialEq, Eq)]
199#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
200pub struct RecordAccess {
201	pub signup: Option<Expr>,
202	pub signin: Option<Expr>,
203	pub jwt: JwtAccess,
204	pub bearer: Option<BearerAccess>,
205}
206
207#[derive(Debug, Clone, Eq, PartialEq)]
208#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
209pub struct BearerAccess {
210	pub kind: BearerAccessType,
211	pub subject: BearerAccessSubject,
212	pub jwt: JwtAccess,
213}
214
215impl Default for BearerAccess {
216	fn default() -> Self {
217		Self {
218			kind: BearerAccessType::Bearer,
219			subject: BearerAccessSubject::User,
220			jwt: JwtAccess::default(),
221		}
222	}
223}
224
225#[derive(Debug, Hash, Clone, Eq, PartialEq, PartialOrd)]
226#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
227pub enum BearerAccessType {
228	Bearer,
229	Refresh,
230}
231
232#[derive(Debug, Hash, Clone, Eq, PartialEq, PartialOrd)]
233#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
234pub enum BearerAccessSubject {
235	Record,
236	User,
237}