1use std::{
4 fmt,
5 time::{Duration, SystemTime},
6};
7
8use base64::Engine;
9use lexe_crypto::ed25519::{self, Signed};
10use lexe_std::array;
11#[cfg(any(test, feature = "test-utils"))]
12use proptest_derive::Arbitrary;
13use serde::{Deserialize, Serialize};
14use thiserror::Error;
15
16use super::user::{NodePkProof, UserPk};
17use crate::byte_str::ByteStr;
18#[cfg(any(test, feature = "test-utils"))]
19use crate::test_utils::arbitrary;
20
21#[derive(Debug, Error)]
22pub enum Error {
23 #[error("Error verifying signed bearer auth request: {0}")]
24 UserVerifyError(#[source] ed25519::Error),
25
26 #[error("Decoded bearer auth token appears malformed")]
27 MalformedToken,
28
29 #[error("Issued timestamp is too far from current auth server clock")]
30 ClockDrift,
31
32 #[error("Auth token or auth request is expired")]
33 Expired,
34
35 #[error("Auth token is not valid yet")]
36 NotYetValid,
37
38 #[error("Timestamp is not a valid unix timestamp")]
39 InvalidTimestamp,
40
41 #[error("Requested token lifetime is too long")]
42 InvalidLifetime,
43
44 #[error("User not signed up yet")]
45 NoUser,
46
47 #[error("Bearer auth token is not valid base64")]
48 Base64Decode,
49
50 #[error("Bearer auth token was not provided")]
51 Missing,
52
53 #[error(
56 "Auth token's granted scope ({granted:?}) is not sufficient for \
57 requested scope ({requested:?})"
58 )]
59 InsufficientScope {
60 granted: LexeScope,
61 requested: LexeScope,
62 },
63}
64
65#[cfg_attr(any(test, feature = "test-utils"), derive(Arbitrary))]
78#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
79pub enum UserSignupRequestWire {
80 V2(UserSignupRequestWireV2),
81}
82
83#[cfg_attr(any(test, feature = "test-utils"), derive(Arbitrary))]
84#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
85pub struct UserSignupRequestWireV2 {
86 pub v1: UserSignupRequestWireV1,
87
88 pub partner: Option<UserPk>,
91}
92
93#[cfg_attr(any(test, feature = "test-utils"), derive(Arbitrary))]
94#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
95pub struct UserSignupRequestWireV1 {
96 pub node_pk_proof: NodePkProof,
98 #[cfg_attr(
103 any(test, feature = "test-utils"),
104 proptest(strategy = "arbitrary::any_option_string()")
105 )]
106 _signup_code: Option<String>,
107}
108
109impl UserSignupRequestWireV1 {
110 pub fn new(node_pk_proof: NodePkProof) -> Self {
111 Self {
112 node_pk_proof,
113 _signup_code: None,
114 }
115 }
116}
117
118#[derive(Clone, Debug)]
122pub struct BearerAuthRequest {
123 pub request_timestamp_secs: u64,
130
131 pub lifetime_secs: u32,
135
136 pub scope: LexeScope,
140}
141
142#[cfg_attr(any(test, feature = "test-utils"), derive(Arbitrary))]
147#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
148pub enum BearerAuthRequestWire {
149 V1(BearerAuthRequestWireV1),
150 V2(BearerAuthRequestWireV2),
152}
153
154#[cfg_attr(any(test, feature = "test-utils"), derive(Arbitrary))]
156#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
157pub struct BearerAuthRequestWireV1 {
158 request_timestamp_secs: u64,
159 lifetime_secs: u32,
160}
161
162#[cfg_attr(any(test, feature = "test-utils"), derive(Arbitrary))]
164#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
165pub struct BearerAuthRequestWireV2 {
166 v1: BearerAuthRequestWireV1,
168 scope: Option<LexeScope>,
174}
175
176#[cfg_attr(any(test, feature = "test-utils"), derive(Arbitrary))]
188#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
189pub enum LexeScope {
190 All,
192
193 GatewayProxy,
204 }
209
210#[derive(Clone, Debug, Serialize, Deserialize)]
211pub struct BearerAuthResponse {
212 pub bearer_auth_token: BearerAuthToken,
213}
214
215#[derive(Clone, Serialize, Deserialize)]
221#[cfg_attr(any(test, feature = "test-utils"), derive(Eq, PartialEq))]
222pub struct BearerAuthToken(pub ByteStr);
223
224impl fmt::Debug for BearerAuthToken {
225 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
226 f.write_str("BearerAuthToken(..)")
227 }
228}
229
230#[derive(Clone)]
233pub struct TokenWithExpiration {
234 pub token: BearerAuthToken,
235 pub expiration: Option<SystemTime>,
236}
237
238impl UserSignupRequestWire {
241 pub fn node_pk_proof(&self) -> &NodePkProof {
242 match self {
243 UserSignupRequestWire::V2(v2) => &v2.v1.node_pk_proof,
244 }
245 }
246
247 pub fn partner(&self) -> Option<&UserPk> {
248 match self {
249 UserSignupRequestWire::V2(v2) => v2.partner.as_ref(),
250 }
251 }
252}
253
254impl ed25519::Signable for UserSignupRequestWire {
255 const DOMAIN_SEPARATOR: [u8; 32] =
257 array::pad(*b"LEXE-REALM::UserSignupRequestWir");
258}
259
260impl UserSignupRequestWireV1 {
263 pub fn deserialize_verify(
264 serialized: &[u8],
265 ) -> Result<Signed<Self>, Error> {
266 ed25519::verify::signed_struct_by_any_signer(serialized)
269 .map_err(Error::UserVerifyError)
270 }
271}
272
273impl ed25519::Signable for UserSignupRequestWireV1 {
274 const DOMAIN_SEPARATOR: [u8; 32] =
276 array::pad(*b"LEXE-REALM::UserSignupRequest");
277}
278
279impl From<UserSignupRequestWireV1> for UserSignupRequestWireV2 {
282 fn from(v1: UserSignupRequestWireV1) -> Self {
283 Self { v1, partner: None }
284 }
285}
286
287impl BearerAuthRequest {
290 pub fn new(
291 now: SystemTime,
292 token_lifetime_secs: u32,
293 scope: LexeScope,
294 ) -> Self {
295 Self {
296 request_timestamp_secs: now
297 .duration_since(SystemTime::UNIX_EPOCH)
298 .expect("Something is very wrong with our clock")
299 .as_secs(),
300 lifetime_secs: token_lifetime_secs,
301 scope,
302 }
303 }
304
305 pub fn request_timestamp(&self) -> Result<SystemTime, Error> {
309 let t_secs = self.request_timestamp_secs;
310 let t_dur_secs = Duration::from_secs(t_secs);
311 SystemTime::UNIX_EPOCH
312 .checked_add(t_dur_secs)
313 .ok_or(Error::InvalidTimestamp)
314 }
315}
316
317impl From<BearerAuthRequestWire> for BearerAuthRequest {
318 fn from(wire: BearerAuthRequestWire) -> Self {
319 match wire {
320 BearerAuthRequestWire::V1(v1) => Self {
323 request_timestamp_secs: v1.request_timestamp_secs,
324 lifetime_secs: v1.lifetime_secs,
325 scope: LexeScope::All,
326 },
327 BearerAuthRequestWire::V2(v2) => Self {
329 request_timestamp_secs: v2.v1.request_timestamp_secs,
330 lifetime_secs: v2.v1.lifetime_secs,
331 scope: v2.scope.unwrap_or(LexeScope::All),
332 },
333 }
334 }
335}
336
337impl From<BearerAuthRequest> for BearerAuthRequestWire {
338 fn from(req: BearerAuthRequest) -> Self {
339 Self::V2(BearerAuthRequestWireV2 {
340 v1: BearerAuthRequestWireV1 {
341 request_timestamp_secs: req.request_timestamp_secs,
342 lifetime_secs: req.lifetime_secs,
343 },
344 scope: Some(req.scope),
345 })
346 }
347}
348
349impl BearerAuthRequestWire {
352 pub fn deserialize_verify(
353 serialized: &[u8],
354 ) -> Result<Signed<Self>, Error> {
355 ed25519::verify::signed_struct_by_any_signer(serialized)
358 .map_err(Error::UserVerifyError)
359 }
360}
361
362impl ed25519::Signable for BearerAuthRequestWire {
363 const DOMAIN_SEPARATOR: [u8; 32] =
365 array::pad(*b"LEXE-REALM::BearerAuthRequest");
366}
367
368impl BearerAuthToken {
371 pub fn encode_from_raw_bytes(signed_token_bytes: &[u8]) -> Self {
373 let b64_token = base64::engine::general_purpose::URL_SAFE_NO_PAD
374 .encode(signed_token_bytes);
375 Self(ByteStr::from(b64_token))
376 }
377
378 pub fn decode_into_raw_bytes(&self) -> Result<Vec<u8>, Error> {
380 Self::decode_slice_into_raw_bytes(self.0.as_bytes())
381 }
382
383 pub fn decode_slice_into_raw_bytes(bytes: &[u8]) -> Result<Vec<u8>, Error> {
385 base64::engine::general_purpose::URL_SAFE_NO_PAD
386 .decode(bytes)
387 .map_err(|_| Error::Base64Decode)
388 }
389}
390
391impl fmt::Display for BearerAuthToken {
392 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
393 f.write_str(self.0.as_str())
394 }
395}
396
397#[cfg(any(test, feature = "test-utils"))]
398mod arbitrary_impl {
399 use proptest::{
400 arbitrary::{Arbitrary, any},
401 strategy::{BoxedStrategy, Strategy},
402 };
403
404 use super::*;
405
406 impl Arbitrary for BearerAuthToken {
407 type Parameters = ();
408 type Strategy = BoxedStrategy<Self>;
409
410 fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
411 any::<Vec<u8>>()
414 .prop_map(|bytes| {
415 BearerAuthToken::encode_from_raw_bytes(&bytes)
416 })
417 .boxed()
418 }
419 }
420}
421
422impl LexeScope {
425 pub fn has_permission_for(&self, requested: &Self) -> bool {
427 match (self, requested) {
428 (LexeScope::All, _) => true,
429 (LexeScope::GatewayProxy, LexeScope::All) => false,
430 (LexeScope::GatewayProxy, LexeScope::GatewayProxy) => true,
431 }
432 }
433}
434
435#[cfg(test)]
436mod test {
437 use base64::Engine;
438 use lexe_hex::hex;
439
440 use super::*;
441 use crate::test_utils::roundtrip::{
442 bcs_roundtrip_ok, bcs_roundtrip_proptest, signed_roundtrip_proptest,
443 };
444
445 #[test]
446 fn test_user_signup_request_wire_canonical() {
447 bcs_roundtrip_proptest::<UserSignupRequestWire>();
448 }
449
450 #[test]
451 fn test_user_signed_request_wire_sign_verify() {
452 signed_roundtrip_proptest::<UserSignupRequestWire>();
453 }
454
455 #[test]
456 fn test_bearer_auth_request_wire_canonical() {
457 bcs_roundtrip_proptest::<BearerAuthRequestWire>();
458 }
459
460 #[test]
461 fn test_bearer_auth_request_wire_sign_verify() {
462 signed_roundtrip_proptest::<BearerAuthRequestWire>();
463 }
464
465 #[test]
466 fn test_bearer_auth_request_wire_snapshot() {
467 let input = "00d20296490000000058020000";
468 let req = BearerAuthRequestWire::V1(BearerAuthRequestWireV1 {
469 request_timestamp_secs: 1234567890,
470 lifetime_secs: 10 * 60,
471 });
472 bcs_roundtrip_ok(&hex::decode(input).unwrap(), &req);
473
474 let input = "01d2029649000000005802000000";
475 let req = BearerAuthRequestWire::V2(BearerAuthRequestWireV2 {
476 v1: BearerAuthRequestWireV1 {
477 request_timestamp_secs: 1234567890,
478 lifetime_secs: 10 * 60,
479 },
480 scope: None,
481 });
482 bcs_roundtrip_ok(&hex::decode(input).unwrap(), &req);
483
484 let input = "01d202964900000000580200000101";
485 let req = BearerAuthRequestWire::V2(BearerAuthRequestWireV2 {
486 v1: BearerAuthRequestWireV1 {
487 request_timestamp_secs: 1234567890,
488 lifetime_secs: 10 * 60,
489 },
490 scope: Some(LexeScope::GatewayProxy),
491 });
492 bcs_roundtrip_ok(&hex::decode(input).unwrap(), &req);
493 }
494
495 #[test]
496 fn test_auth_scope_canonical() {
497 bcs_roundtrip_proptest::<LexeScope>();
498 }
499
500 #[test]
501 fn test_auth_scope_snapshot() {
502 let input = b"\x00";
503 let scope = LexeScope::All;
504 bcs_roundtrip_ok(input, &scope);
505
506 let input = b"\x01";
507 let scope = LexeScope::GatewayProxy;
508 bcs_roundtrip_ok(input, &scope);
509 }
510
511 #[test]
516 fn test_user_signup_request_wire_v1_snapshot() {
517 let b64 = base64::engine::general_purpose::STANDARD;
518
519 let input_with_code = "AqqWkI6A9EExJ9suasa1a4Vte7dSztOpSsGNVUHClpLb\
521 RjBEAiANgXon77EhDl3dq6ZASg9u/xjS3OET2um+OA6+/58UmQIgEYmJGcNNWfMy\
522 npScmW9joOortpvHul9bHyojSj3Im70BCUFCQ0QtMTIzNA==";
523 let bytes_with_code = b64.decode(input_with_code).unwrap();
524 let req: UserSignupRequestWireV1 =
525 bcs::from_bytes(&bytes_with_code).unwrap();
526 assert!(req.node_pk_proof.verify().is_ok());
527
528 let input_none = "AqqWkI6A9EExJ9suasa1a4Vte7dSztOpSsGNVUHClpLbRjBE\
530 AiANgXon77EhDl3dq6ZASg9u/xjS3OET2um+OA6+/58UmQIgEYmJGcNNWfMynpSc\
531 mW9joOortpvHul9bHyojSj3Im70A";
532 let bytes_none = b64.decode(input_none).unwrap();
533 let req: UserSignupRequestWireV1 =
534 bcs::from_bytes(&bytes_none).unwrap();
535 assert!(req.node_pk_proof.verify().is_ok());
536 }
537}