@@ -17,7 +17,10 @@ use mas_storage::{
17
17
compat:: { CompatSessionFilter , CompatSsoLoginFilter , CompatSsoLoginRepository } ,
18
18
oauth2:: { OAuth2SessionFilter , OAuth2SessionRepository } ,
19
19
upstream_oauth2:: { UpstreamOAuthLinkFilter , UpstreamOAuthLinkRepository } ,
20
- user:: { BrowserSessionFilter , BrowserSessionRepository , UserEmailFilter , UserEmailRepository } ,
20
+ user:: {
21
+ BrowserSessionFilter , BrowserSessionRepository , UserEmailFilter , UserEmailRepository ,
22
+ UserPasskeyFilter ,
23
+ } ,
21
24
} ;
22
25
23
26
use super :: {
@@ -706,6 +709,66 @@ impl User {
706
709
. await
707
710
}
708
711
712
+ /// Get the list of passkeys, chronologically sorted
713
+ async fn passkeys (
714
+ & self ,
715
+ ctx : & Context < ' _ > ,
716
+
717
+ #[ graphql( desc = "Returns the elements in the list that come after the cursor." ) ]
718
+ after : Option < String > ,
719
+ #[ graphql( desc = "Returns the elements in the list that come before the cursor." ) ]
720
+ before : Option < String > ,
721
+ #[ graphql( desc = "Returns the first *n* elements from the list." ) ] first : Option < i32 > ,
722
+ #[ graphql( desc = "Returns the last *n* elements from the list." ) ] last : Option < i32 > ,
723
+ ) -> Result < Connection < Cursor , UserPasskey , PreloadedTotalCount > , async_graphql:: Error > {
724
+ let state = ctx. state ( ) ;
725
+ let mut repo = state. repository ( ) . await ?;
726
+
727
+ query (
728
+ after,
729
+ before,
730
+ first,
731
+ last,
732
+ async |after, before, first, last| {
733
+ let after_id = after
734
+ . map ( |x : OpaqueCursor < NodeCursor > | x. extract_for_type ( NodeType :: UserPasskey ) )
735
+ . transpose ( ) ?;
736
+ let before_id = before
737
+ . map ( |x : OpaqueCursor < NodeCursor > | x. extract_for_type ( NodeType :: UserPasskey ) )
738
+ . transpose ( ) ?;
739
+ let pagination = Pagination :: try_new ( before_id, after_id, first, last) ?;
740
+
741
+ let filter = UserPasskeyFilter :: new ( ) . for_user ( & self . 0 ) ;
742
+
743
+ let page = repo. user_passkey ( ) . list ( filter, pagination) . await ?;
744
+
745
+ // Preload the total count if requested
746
+ let count = if ctx. look_ahead ( ) . field ( "totalCount" ) . exists ( ) {
747
+ Some ( repo. user_passkey ( ) . count ( filter) . await ?)
748
+ } else {
749
+ None
750
+ } ;
751
+
752
+ repo. cancel ( ) . await ?;
753
+
754
+ let mut connection = Connection :: with_additional_fields (
755
+ page. has_previous_page ,
756
+ page. has_next_page ,
757
+ PreloadedTotalCount ( count) ,
758
+ ) ;
759
+ connection. edges . extend ( page. edges . into_iter ( ) . map ( |u| {
760
+ Edge :: new (
761
+ OpaqueCursor ( NodeCursor ( NodeType :: UserPasskey , u. id ) ) ,
762
+ UserPasskey ( u) ,
763
+ )
764
+ } ) ) ;
765
+
766
+ Ok :: < _ , async_graphql:: Error > ( connection)
767
+ } ,
768
+ )
769
+ . await
770
+ }
771
+
709
772
/// Check if the user has a password set.
710
773
async fn has_password ( & self , ctx : & Context < ' _ > ) -> Result < bool , async_graphql:: Error > {
711
774
let state = ctx. state ( ) ;
@@ -887,3 +950,30 @@ impl UserEmailAuthentication {
887
950
& self . 0 . email
888
951
}
889
952
}
953
+
954
+ /// A passkey
955
+ #[ derive( Description ) ]
956
+ pub struct UserPasskey ( pub mas_data_model:: UserPasskey ) ;
957
+
958
+ #[ Object ( use_type_description) ]
959
+ impl UserPasskey {
960
+ /// ID of the object
961
+ pub async fn id ( & self ) -> ID {
962
+ NodeType :: UserPasskey . id ( self . 0 . id )
963
+ }
964
+
965
+ /// Name of the passkey
966
+ pub async fn name ( & self ) -> & str {
967
+ & self . 0 . name
968
+ }
969
+
970
+ /// When the object was created.
971
+ pub async fn created_at ( & self ) -> DateTime < Utc > {
972
+ self . 0 . created_at
973
+ }
974
+
975
+ /// When the passkey was last used
976
+ pub async fn last_used_at ( & self ) -> Option < DateTime < Utc > > {
977
+ self . 0 . last_used_at
978
+ }
979
+ }
0 commit comments