@@ -95,12 +95,20 @@ router.post(
9595 const { user } = ctx . state . auth ;
9696 authorize ( user , "createUserPasskey" , user . team ) ;
9797
98+ // Fetch existing passkeys to exclude them from registration
99+ const existingPasskeys = await UserPasskey . findAll ( {
100+ where : { userId : user . id } ,
101+ } ) ;
102+
98103 const options = await generateRegistrationOptions ( {
99104 rpName,
100105 rpID : getRpID ( ctx ) ,
101106 userID : isoBase64URL . toBuffer ( user . id ) ,
102107 userName : user . email || user . name ,
103- // Don't exclude credentials, so we can detect if one is already registered (optional)
108+ excludeCredentials : existingPasskeys . map ( ( pk ) => ( {
109+ id : pk . credentialId ,
110+ transports : pk . transports as AuthenticatorTransportFuture [ ] ,
111+ } ) ) ,
104112 authenticatorSelection : {
105113 residentKey : "preferred" ,
106114 userVerification : "preferred" ,
@@ -154,6 +162,7 @@ router.post(
154162 }
155163
156164 const { verified, registrationInfo } = verification ;
165+ const ZERO_AAGUID = "00000000-0000-0000-0000-000000000000" ;
157166
158167 if ( verified && registrationInfo ) {
159168 const { credential, aaguid } = registrationInfo ;
@@ -166,7 +175,7 @@ router.post(
166175 const userAgent = ctx . request . get ( "user-agent" ) ;
167176 const transports = body . response . transports || [ ] ;
168177
169- // Check if already exists
178+ // Check if already exists by credential ID
170179 const existing = await UserPasskey . findOne ( {
171180 where : { credentialId : credentialIdBase64 } ,
172181 } ) ;
@@ -183,6 +192,17 @@ router.post(
183192 aaguid,
184193 } ) ;
185194 } else {
195+ // Check if user already has a passkey from the same authenticator
196+ if ( aaguid && aaguid !== ZERO_AAGUID ) {
197+ const duplicateDevice = await UserPasskey . findOne ( {
198+ where : { userId : user . id , aaguid } ,
199+ } ) ;
200+
201+ if ( duplicateDevice ) {
202+ throw ValidationError ( "You already have a passkey on this device" ) ;
203+ }
204+ }
205+
186206 await UserPasskey . createWithCtx ( ctx , {
187207 userId : user . id ,
188208 credentialId : credentialIdBase64 ,
0 commit comments