1use std::net::IpAddr;
8
9use chrono::{DateTime, Utc};
10use mas_data_model::{
11 Device,
12 personal::{
13 PersonalAccessToken as DataModelPersonalAccessToken,
14 session::{PersonalSession as DataModelPersonalSession, PersonalSessionOwner},
15 },
16};
17use schemars::JsonSchema;
18use serde::Serialize;
19use thiserror::Error;
20use ulid::Ulid;
21use url::Url;
22
23pub trait Resource {
25 const KIND: &'static str;
27
28 const PATH: &'static str;
30
31 fn id(&self) -> Ulid;
33
34 fn path(&self) -> String {
38 format!("{}/{}", Self::PATH, self.id())
39 }
40}
41
42#[derive(Serialize, JsonSchema)]
44pub struct User {
45 #[serde(skip)]
46 id: Ulid,
47
48 username: String,
50
51 created_at: DateTime<Utc>,
53
54 locked_at: Option<DateTime<Utc>>,
56
57 deactivated_at: Option<DateTime<Utc>>,
59
60 admin: bool,
62
63 legacy_guest: bool,
65}
66
67impl User {
68 pub fn samples() -> [Self; 3] {
70 [
71 Self {
72 id: Ulid::from_bytes([0x01; 16]),
73 username: "alice".to_owned(),
74 created_at: DateTime::default(),
75 locked_at: None,
76 deactivated_at: None,
77 admin: false,
78 legacy_guest: false,
79 },
80 Self {
81 id: Ulid::from_bytes([0x02; 16]),
82 username: "bob".to_owned(),
83 created_at: DateTime::default(),
84 locked_at: None,
85 deactivated_at: None,
86 admin: true,
87 legacy_guest: false,
88 },
89 Self {
90 id: Ulid::from_bytes([0x03; 16]),
91 username: "charlie".to_owned(),
92 created_at: DateTime::default(),
93 locked_at: Some(DateTime::default()),
94 deactivated_at: None,
95 admin: false,
96 legacy_guest: true,
97 },
98 ]
99 }
100}
101
102impl From<mas_data_model::User> for User {
103 fn from(user: mas_data_model::User) -> Self {
104 Self {
105 id: user.id,
106 username: user.username,
107 created_at: user.created_at,
108 locked_at: user.locked_at,
109 deactivated_at: user.deactivated_at,
110 admin: user.can_request_admin,
111 legacy_guest: user.is_guest,
112 }
113 }
114}
115
116impl Resource for User {
117 const KIND: &'static str = "user";
118 const PATH: &'static str = "/api/admin/v1/users";
119
120 fn id(&self) -> Ulid {
121 self.id
122 }
123}
124
125#[derive(Serialize, JsonSchema)]
127pub struct UserEmail {
128 #[serde(skip)]
129 id: Ulid,
130
131 created_at: DateTime<Utc>,
133
134 #[schemars(with = "super::schema::Ulid")]
136 user_id: Ulid,
137
138 email: String,
140}
141
142impl Resource for UserEmail {
143 const KIND: &'static str = "user-email";
144 const PATH: &'static str = "/api/admin/v1/user-emails";
145
146 fn id(&self) -> Ulid {
147 self.id
148 }
149}
150
151impl From<mas_data_model::UserEmail> for UserEmail {
152 fn from(value: mas_data_model::UserEmail) -> Self {
153 Self {
154 id: value.id,
155 created_at: value.created_at,
156 user_id: value.user_id,
157 email: value.email,
158 }
159 }
160}
161
162impl UserEmail {
163 pub fn samples() -> [Self; 1] {
164 [Self {
165 id: Ulid::from_bytes([0x01; 16]),
166 created_at: DateTime::default(),
167 user_id: Ulid::from_bytes([0x02; 16]),
168 email: "alice@example.com".to_owned(),
169 }]
170 }
171}
172
173#[derive(Serialize, JsonSchema)]
175pub struct CompatSession {
176 #[serde(skip)]
177 pub id: Ulid,
178
179 #[schemars(with = "super::schema::Ulid")]
181 pub user_id: Ulid,
182
183 #[schemars(with = "super::schema::Device")]
185 pub device_id: Option<Device>,
186
187 #[schemars(with = "super::schema::Ulid")]
189 pub user_session_id: Option<Ulid>,
190
191 pub redirect_uri: Option<Url>,
193
194 pub created_at: DateTime<Utc>,
196
197 pub user_agent: Option<String>,
199
200 pub last_active_at: Option<DateTime<Utc>>,
202
203 pub last_active_ip: Option<std::net::IpAddr>,
205
206 pub finished_at: Option<DateTime<Utc>>,
208
209 pub human_name: Option<String>,
211}
212
213impl
214 From<(
215 mas_data_model::CompatSession,
216 Option<mas_data_model::CompatSsoLogin>,
217 )> for CompatSession
218{
219 fn from(
220 (session, sso_login): (
221 mas_data_model::CompatSession,
222 Option<mas_data_model::CompatSsoLogin>,
223 ),
224 ) -> Self {
225 let finished_at = session.finished_at();
226 Self {
227 id: session.id,
228 user_id: session.user_id,
229 device_id: session.device,
230 user_session_id: session.user_session_id,
231 redirect_uri: sso_login.map(|sso| sso.redirect_uri),
232 created_at: session.created_at,
233 user_agent: session.user_agent,
234 last_active_at: session.last_active_at,
235 last_active_ip: session.last_active_ip,
236 finished_at,
237 human_name: session.human_name,
238 }
239 }
240}
241
242impl Resource for CompatSession {
243 const KIND: &'static str = "compat-session";
244 const PATH: &'static str = "/api/admin/v1/compat-sessions";
245
246 fn id(&self) -> Ulid {
247 self.id
248 }
249}
250
251impl CompatSession {
252 pub fn samples() -> [Self; 3] {
253 [
254 Self {
255 id: Ulid::from_bytes([0x01; 16]),
256 user_id: Ulid::from_bytes([0x01; 16]),
257 device_id: Some("AABBCCDDEE".to_owned().into()),
258 user_session_id: Some(Ulid::from_bytes([0x11; 16])),
259 redirect_uri: Some("https://example.com/redirect".parse().unwrap()),
260 created_at: DateTime::default(),
261 user_agent: Some("Mozilla/5.0".to_owned()),
262 last_active_at: Some(DateTime::default()),
263 last_active_ip: Some([1, 2, 3, 4].into()),
264 finished_at: None,
265 human_name: Some("Laptop".to_owned()),
266 },
267 Self {
268 id: Ulid::from_bytes([0x02; 16]),
269 user_id: Ulid::from_bytes([0x01; 16]),
270 device_id: Some("FFGGHHIIJJ".to_owned().into()),
271 user_session_id: Some(Ulid::from_bytes([0x12; 16])),
272 redirect_uri: None,
273 created_at: DateTime::default(),
274 user_agent: Some("Mozilla/5.0".to_owned()),
275 last_active_at: Some(DateTime::default()),
276 last_active_ip: Some([1, 2, 3, 4].into()),
277 finished_at: Some(DateTime::default()),
278 human_name: None,
279 },
280 Self {
281 id: Ulid::from_bytes([0x03; 16]),
282 user_id: Ulid::from_bytes([0x01; 16]),
283 device_id: None,
284 user_session_id: None,
285 redirect_uri: None,
286 created_at: DateTime::default(),
287 user_agent: None,
288 last_active_at: None,
289 last_active_ip: None,
290 finished_at: None,
291 human_name: None,
292 },
293 ]
294 }
295}
296
297#[derive(Serialize, JsonSchema)]
299pub struct OAuth2Session {
300 #[serde(skip)]
301 id: Ulid,
302
303 created_at: DateTime<Utc>,
305
306 finished_at: Option<DateTime<Utc>>,
308
309 #[schemars(with = "Option<super::schema::Ulid>")]
311 user_id: Option<Ulid>,
312
313 #[schemars(with = "Option<super::schema::Ulid>")]
315 user_session_id: Option<Ulid>,
316
317 #[schemars(with = "super::schema::Ulid")]
319 client_id: Ulid,
320
321 scope: String,
323
324 user_agent: Option<String>,
326
327 last_active_at: Option<DateTime<Utc>>,
329
330 last_active_ip: Option<IpAddr>,
332
333 human_name: Option<String>,
335}
336
337impl From<mas_data_model::Session> for OAuth2Session {
338 fn from(session: mas_data_model::Session) -> Self {
339 Self {
340 id: session.id,
341 created_at: session.created_at,
342 finished_at: session.finished_at(),
343 user_id: session.user_id,
344 user_session_id: session.user_session_id,
345 client_id: session.client_id,
346 scope: session.scope.to_string(),
347 user_agent: session.user_agent,
348 last_active_at: session.last_active_at,
349 last_active_ip: session.last_active_ip,
350 human_name: session.human_name,
351 }
352 }
353}
354
355impl OAuth2Session {
356 pub fn samples() -> [Self; 3] {
358 [
359 Self {
360 id: Ulid::from_bytes([0x01; 16]),
361 created_at: DateTime::default(),
362 finished_at: None,
363 user_id: Some(Ulid::from_bytes([0x02; 16])),
364 user_session_id: Some(Ulid::from_bytes([0x03; 16])),
365 client_id: Ulid::from_bytes([0x04; 16]),
366 scope: "openid".to_owned(),
367 user_agent: Some("Mozilla/5.0".to_owned()),
368 last_active_at: Some(DateTime::default()),
369 last_active_ip: Some("127.0.0.1".parse().unwrap()),
370 human_name: Some("Laptop".to_owned()),
371 },
372 Self {
373 id: Ulid::from_bytes([0x02; 16]),
374 created_at: DateTime::default(),
375 finished_at: None,
376 user_id: None,
377 user_session_id: None,
378 client_id: Ulid::from_bytes([0x05; 16]),
379 scope: "urn:mas:admin".to_owned(),
380 user_agent: None,
381 last_active_at: None,
382 last_active_ip: None,
383 human_name: None,
384 },
385 Self {
386 id: Ulid::from_bytes([0x03; 16]),
387 created_at: DateTime::default(),
388 finished_at: Some(DateTime::default()),
389 user_id: Some(Ulid::from_bytes([0x04; 16])),
390 user_session_id: Some(Ulid::from_bytes([0x05; 16])),
391 client_id: Ulid::from_bytes([0x06; 16]),
392 scope: "urn:matrix:client:api:*".to_owned(),
393 user_agent: Some("Mozilla/5.0".to_owned()),
394 last_active_at: Some(DateTime::default()),
395 last_active_ip: Some("127.0.0.1".parse().unwrap()),
396 human_name: None,
397 },
398 ]
399 }
400}
401
402impl Resource for OAuth2Session {
403 const KIND: &'static str = "oauth2-session";
404 const PATH: &'static str = "/api/admin/v1/oauth2-sessions";
405
406 fn id(&self) -> Ulid {
407 self.id
408 }
409}
410
411#[derive(Serialize, JsonSchema)]
413pub struct UserSession {
414 #[serde(skip)]
415 id: Ulid,
416
417 created_at: DateTime<Utc>,
419
420 finished_at: Option<DateTime<Utc>>,
422
423 #[schemars(with = "super::schema::Ulid")]
425 user_id: Ulid,
426
427 user_agent: Option<String>,
429
430 last_active_at: Option<DateTime<Utc>>,
432
433 last_active_ip: Option<IpAddr>,
435}
436
437impl From<mas_data_model::BrowserSession> for UserSession {
438 fn from(value: mas_data_model::BrowserSession) -> Self {
439 Self {
440 id: value.id,
441 created_at: value.created_at,
442 finished_at: value.finished_at,
443 user_id: value.user.id,
444 user_agent: value.user_agent,
445 last_active_at: value.last_active_at,
446 last_active_ip: value.last_active_ip,
447 }
448 }
449}
450
451impl UserSession {
452 pub fn samples() -> [Self; 3] {
454 [
455 Self {
456 id: Ulid::from_bytes([0x01; 16]),
457 created_at: DateTime::default(),
458 finished_at: None,
459 user_id: Ulid::from_bytes([0x02; 16]),
460 user_agent: Some("Mozilla/5.0".to_owned()),
461 last_active_at: Some(DateTime::default()),
462 last_active_ip: Some("127.0.0.1".parse().unwrap()),
463 },
464 Self {
465 id: Ulid::from_bytes([0x02; 16]),
466 created_at: DateTime::default(),
467 finished_at: None,
468 user_id: Ulid::from_bytes([0x03; 16]),
469 user_agent: None,
470 last_active_at: None,
471 last_active_ip: None,
472 },
473 Self {
474 id: Ulid::from_bytes([0x03; 16]),
475 created_at: DateTime::default(),
476 finished_at: Some(DateTime::default()),
477 user_id: Ulid::from_bytes([0x04; 16]),
478 user_agent: Some("Mozilla/5.0".to_owned()),
479 last_active_at: Some(DateTime::default()),
480 last_active_ip: Some("127.0.0.1".parse().unwrap()),
481 },
482 ]
483 }
484}
485
486impl Resource for UserSession {
487 const KIND: &'static str = "user-session";
488 const PATH: &'static str = "/api/admin/v1/user-sessions";
489
490 fn id(&self) -> Ulid {
491 self.id
492 }
493}
494
495#[derive(Serialize, JsonSchema)]
497pub struct UpstreamOAuthLink {
498 #[serde(skip)]
499 id: Ulid,
500
501 created_at: DateTime<Utc>,
503
504 #[schemars(with = "super::schema::Ulid")]
506 provider_id: Ulid,
507
508 subject: String,
510
511 #[schemars(with = "Option<super::schema::Ulid>")]
513 user_id: Option<Ulid>,
514
515 human_account_name: Option<String>,
517}
518
519impl Resource for UpstreamOAuthLink {
520 const KIND: &'static str = "upstream-oauth-link";
521 const PATH: &'static str = "/api/admin/v1/upstream-oauth-links";
522
523 fn id(&self) -> Ulid {
524 self.id
525 }
526}
527
528impl From<mas_data_model::UpstreamOAuthLink> for UpstreamOAuthLink {
529 fn from(value: mas_data_model::UpstreamOAuthLink) -> Self {
530 Self {
531 id: value.id,
532 created_at: value.created_at,
533 provider_id: value.provider_id,
534 subject: value.subject,
535 user_id: value.user_id,
536 human_account_name: value.human_account_name,
537 }
538 }
539}
540
541impl UpstreamOAuthLink {
542 pub fn samples() -> [Self; 3] {
544 [
545 Self {
546 id: Ulid::from_bytes([0x01; 16]),
547 created_at: DateTime::default(),
548 provider_id: Ulid::from_bytes([0x02; 16]),
549 subject: "john-42".to_owned(),
550 user_id: Some(Ulid::from_bytes([0x03; 16])),
551 human_account_name: Some("john.doe@example.com".to_owned()),
552 },
553 Self {
554 id: Ulid::from_bytes([0x02; 16]),
555 created_at: DateTime::default(),
556 provider_id: Ulid::from_bytes([0x03; 16]),
557 subject: "jane-123".to_owned(),
558 user_id: None,
559 human_account_name: None,
560 },
561 Self {
562 id: Ulid::from_bytes([0x03; 16]),
563 created_at: DateTime::default(),
564 provider_id: Ulid::from_bytes([0x04; 16]),
565 subject: "bob@social.example.com".to_owned(),
566 user_id: Some(Ulid::from_bytes([0x05; 16])),
567 human_account_name: Some("bob".to_owned()),
568 },
569 ]
570 }
571}
572
573#[derive(Serialize, JsonSchema)]
575pub struct PolicyData {
576 #[serde(skip)]
577 id: Ulid,
578
579 created_at: DateTime<Utc>,
581
582 data: serde_json::Value,
584}
585
586impl From<mas_data_model::PolicyData> for PolicyData {
587 fn from(policy_data: mas_data_model::PolicyData) -> Self {
588 Self {
589 id: policy_data.id,
590 created_at: policy_data.created_at,
591 data: policy_data.data,
592 }
593 }
594}
595
596impl Resource for PolicyData {
597 const KIND: &'static str = "policy-data";
598 const PATH: &'static str = "/api/admin/v1/policy-data";
599
600 fn id(&self) -> Ulid {
601 self.id
602 }
603}
604
605impl PolicyData {
606 pub fn samples() -> [Self; 1] {
608 [Self {
609 id: Ulid::from_bytes([0x01; 16]),
610 created_at: DateTime::default(),
611 data: serde_json::json!({
612 "hello": "world",
613 "foo": 42,
614 "bar": true
615 }),
616 }]
617 }
618}
619
620#[derive(Serialize, JsonSchema)]
622pub struct UserRegistrationToken {
623 #[serde(skip)]
624 id: Ulid,
625
626 token: String,
628
629 valid: bool,
631
632 usage_limit: Option<u32>,
634
635 times_used: u32,
637
638 created_at: DateTime<Utc>,
640
641 last_used_at: Option<DateTime<Utc>>,
643
644 expires_at: Option<DateTime<Utc>>,
646
647 revoked_at: Option<DateTime<Utc>>,
649}
650
651impl UserRegistrationToken {
652 pub fn new(token: mas_data_model::UserRegistrationToken, now: DateTime<Utc>) -> Self {
653 Self {
654 id: token.id,
655 valid: token.is_valid(now),
656 token: token.token,
657 usage_limit: token.usage_limit,
658 times_used: token.times_used,
659 created_at: token.created_at,
660 last_used_at: token.last_used_at,
661 expires_at: token.expires_at,
662 revoked_at: token.revoked_at,
663 }
664 }
665}
666
667impl Resource for UserRegistrationToken {
668 const KIND: &'static str = "user-registration_token";
669 const PATH: &'static str = "/api/admin/v1/user-registration-tokens";
670
671 fn id(&self) -> Ulid {
672 self.id
673 }
674}
675
676impl UserRegistrationToken {
677 pub fn samples() -> [Self; 2] {
679 [
680 Self {
681 id: Ulid::from_bytes([0x01; 16]),
682 token: "abc123def456".to_owned(),
683 valid: true,
684 usage_limit: Some(10),
685 times_used: 5,
686 created_at: DateTime::default(),
687 last_used_at: Some(DateTime::default()),
688 expires_at: Some(DateTime::default() + chrono::Duration::days(30)),
689 revoked_at: None,
690 },
691 Self {
692 id: Ulid::from_bytes([0x02; 16]),
693 token: "xyz789abc012".to_owned(),
694 valid: false,
695 usage_limit: None,
696 times_used: 0,
697 created_at: DateTime::default(),
698 last_used_at: None,
699 expires_at: None,
700 revoked_at: Some(DateTime::default()),
701 },
702 ]
703 }
704}
705
706#[derive(Serialize, JsonSchema)]
708pub struct UpstreamOAuthProvider {
709 #[serde(skip)]
710 id: Ulid,
711
712 issuer: Option<String>,
714
715 human_name: Option<String>,
717
718 brand_name: Option<String>,
720
721 created_at: DateTime<Utc>,
723
724 disabled_at: Option<DateTime<Utc>>,
726}
727
728impl From<mas_data_model::UpstreamOAuthProvider> for UpstreamOAuthProvider {
729 fn from(provider: mas_data_model::UpstreamOAuthProvider) -> Self {
730 Self {
731 id: provider.id,
732 issuer: provider.issuer,
733 human_name: provider.human_name,
734 brand_name: provider.brand_name,
735 created_at: provider.created_at,
736 disabled_at: provider.disabled_at,
737 }
738 }
739}
740
741impl Resource for UpstreamOAuthProvider {
742 const KIND: &'static str = "upstream-oauth-provider";
743 const PATH: &'static str = "/api/admin/v1/upstream-oauth-providers";
744
745 fn id(&self) -> Ulid {
746 self.id
747 }
748}
749
750impl UpstreamOAuthProvider {
751 pub fn samples() -> [Self; 3] {
753 [
754 Self {
755 id: Ulid::from_bytes([0x01; 16]),
756 issuer: Some("https://accounts.google.com".to_owned()),
757 human_name: Some("Google".to_owned()),
758 brand_name: Some("google".to_owned()),
759 created_at: DateTime::default(),
760 disabled_at: None,
761 },
762 Self {
763 id: Ulid::from_bytes([0x02; 16]),
764 issuer: Some("https://appleid.apple.com".to_owned()),
765 human_name: Some("Apple ID".to_owned()),
766 brand_name: Some("apple".to_owned()),
767 created_at: DateTime::default(),
768 disabled_at: Some(DateTime::default()),
769 },
770 Self {
771 id: Ulid::from_bytes([0x03; 16]),
772 issuer: None,
773 human_name: Some("Custom OAuth Provider".to_owned()),
774 brand_name: None,
775 created_at: DateTime::default(),
776 disabled_at: None,
777 },
778 ]
779 }
780}
781
782#[derive(Debug, Error)]
785#[error(
786 "personal session {session_id} in inconsistent state: not revoked but no valid access token"
787)]
788pub struct InconsistentPersonalSession {
789 pub session_id: Ulid,
790}
791
792#[derive(Serialize, JsonSchema)]
796pub struct PersonalSession {
797 #[serde(skip)]
798 id: Ulid,
799
800 created_at: DateTime<Utc>,
802
803 revoked_at: Option<DateTime<Utc>>,
805
806 #[schemars(with = "Option<super::schema::Ulid>")]
808 owner_user_id: Option<Ulid>,
809
810 #[schemars(with = "Option<super::schema::Ulid>")]
812 owner_client_id: Option<Ulid>,
813
814 #[schemars(with = "super::schema::Ulid")]
816 actor_user_id: Ulid,
817
818 human_name: String,
820
821 scope: String,
823
824 last_active_at: Option<DateTime<Utc>>,
826
827 last_active_ip: Option<IpAddr>,
829
830 expires_at: Option<DateTime<Utc>>,
835
836 #[serde(skip_serializing_if = "Option::is_none")]
838 access_token: Option<String>,
839}
840
841impl
842 TryFrom<(
843 DataModelPersonalSession,
844 Option<DataModelPersonalAccessToken>,
845 )> for PersonalSession
846{
847 type Error = InconsistentPersonalSession;
848
849 fn try_from(
850 (session, token): (
851 DataModelPersonalSession,
852 Option<DataModelPersonalAccessToken>,
853 ),
854 ) -> Result<Self, InconsistentPersonalSession> {
855 let expires_at = if let Some(token) = token {
856 token.expires_at
857 } else {
858 if !session.is_revoked() {
859 return Err(InconsistentPersonalSession {
861 session_id: session.id,
862 });
863 }
864 None
865 };
866
867 let (owner_user_id, owner_client_id) = match session.owner {
868 PersonalSessionOwner::User(id) => (Some(id), None),
869 PersonalSessionOwner::OAuth2Client(id) => (None, Some(id)),
870 };
871
872 Ok(Self {
873 id: session.id,
874 created_at: session.created_at,
875 revoked_at: session.revoked_at(),
876 owner_user_id,
877 owner_client_id,
878 actor_user_id: session.actor_user_id,
879 human_name: session.human_name,
880 scope: session.scope.to_string(),
881 last_active_at: session.last_active_at,
882 last_active_ip: session.last_active_ip,
883 expires_at,
884 access_token: None,
886 })
887 }
888}
889
890impl Resource for PersonalSession {
891 const KIND: &'static str = "personal-session";
892 const PATH: &'static str = "/api/admin/v1/personal-sessions";
893
894 fn id(&self) -> Ulid {
895 self.id
896 }
897}
898
899impl PersonalSession {
900 pub fn samples() -> [Self; 3] {
902 [
903 Self {
904 id: Ulid::from_string("01FSHN9AG0AJ6AC5HQ9X6H4RP4").unwrap(),
905 created_at: DateTime::from_timestamp(1_642_338_000, 0).unwrap(), revoked_at: None,
908 owner_user_id: Some(Ulid::from_string("01FSHN9AG0MZAA6S4AF7CTV32E").unwrap()),
909 owner_client_id: None,
910 actor_user_id: Ulid::from_string("01FSHN9AG0MZAA6S4AF7CTV32E").unwrap(),
911 human_name: "Alice's Development Token".to_owned(),
912 scope: "openid urn:matrix:org.matrix.msc2967.client:api:*".to_owned(),
913 last_active_at: Some(DateTime::from_timestamp(1_642_347_000, 0).unwrap()), last_active_ip: Some("192.168.1.100".parse().unwrap()),
915 expires_at: None,
916 access_token: None,
917 },
918 Self {
919 id: Ulid::from_string("01FSHN9AG0BJ6AC5HQ9X6H4RP5").unwrap(),
920 created_at: DateTime::from_timestamp(1_642_338_060, 0).unwrap(), revoked_at: Some(DateTime::from_timestamp(1_642_350_000, 0).unwrap()), owner_user_id: Some(Ulid::from_string("01FSHN9AG0NZAA6S4AF7CTV32F").unwrap()),
924 owner_client_id: None,
925 actor_user_id: Ulid::from_string("01FSHN9AG0NZAA6S4AF7CTV32F").unwrap(),
926 human_name: "Bob's Mobile App".to_owned(),
927 scope: "openid".to_owned(),
928 last_active_at: Some(DateTime::from_timestamp(1_642_349_000, 0).unwrap()), last_active_ip: Some("10.0.0.50".parse().unwrap()),
930 expires_at: None,
931 access_token: None,
932 },
933 Self {
934 id: Ulid::from_string("01FSHN9AG0CJ6AC5HQ9X6H4RP6").unwrap(),
935 created_at: DateTime::from_timestamp(1_642_338_120, 0).unwrap(), revoked_at: None,
938 owner_user_id: None,
939 owner_client_id: Some(Ulid::from_string("01FSHN9AG0DJ6AC5HQ9X6H4RP7").unwrap()),
940 actor_user_id: Ulid::from_string("01FSHN9AG0MZAA6S4AF7CTV32E").unwrap(),
941 human_name: "CI/CD Pipeline Token".to_owned(),
942 scope: "openid urn:mas:admin".to_owned(),
943 last_active_at: Some(DateTime::from_timestamp(1_642_348_000, 0).unwrap()), last_active_ip: Some("203.0.113.10".parse().unwrap()),
945 expires_at: Some(DateTime::from_timestamp(1_642_999_000, 0).unwrap()),
946 access_token: None,
947 },
948 ]
949 }
950
951 pub fn with_token(mut self, access_token: String) -> Self {
953 self.access_token = Some(access_token);
954 self
955 }
956}