@@ -56,6 +56,8 @@ export class UserLoginComponent implements OnInit, OnDestroy {
5656loading = false ;
5757passwordVisible = false ;
5858qrexpire = false ;
59+ passkeyEnabled = false ;
60+ passkeyAllowedOrigins = [ ] ;
5961imageCaptcha = '' ;
6062captchaType = '' ;
6163state = '' ;
@@ -136,6 +138,25 @@ export class UserLoginComponent implements OnInit, OnDestroy {
136138this . socials = res . data . socials ;
137139this . state = res . data . state ;
138140this . captchaType = res . data . captcha ;
141+ this . passkeyEnabled = res . data . passkeyEnabled ;
142+ this . passkeyAllowedOrigins = res . data . passkeyAllowedOrigins ;
143+ let passkeyAllowedOriginsMatch = false ;
144+ for ( let allowedOrigin of this . passkeyAllowedOrigins ) {
145+ console . log ( `passkey allowedOrigin${ allowedOrigin } ` ) ;
146+ console . log ( `location${ window . location . href } ` ) ;
147+ if (
148+ window . location . href . startsWith ( 'http://localhost' ) ||
149+ ( window . location . href . startsWith ( 'https' ) && window . location . href . indexOf ( allowedOrigin ) > - 1 )
150+ ) {
151+ console . log ( window . location . href . indexOf ( allowedOrigin ) > - 1 ) ;
152+ passkeyAllowedOriginsMatch = true ;
153+ }
154+ }
155+ if ( window . PublicKeyCredential && this . passkeyEnabled && passkeyAllowedOriginsMatch ) {
156+ this . passkeyEnabled = true ;
157+ } else {
158+ this . passkeyEnabled = false ;
159+ }
139160if ( this . captchaType === 'NONE' ) {
140161//清除校验规则
141162this . form . get ( 'captcha' ) ?. clearValidators ( ) ;
@@ -508,7 +529,7 @@ export class UserLoginComponent implements OnInit, OnDestroy {
508529async passkeyLogin ( ) :Promise < void > {
509530console . log ( '=== PASSKEY LOGIN DEBUG START ===' ) ;
510531console . log ( 'Passkey usernameless login clicked at:' , new Date ( ) . toISOString ( ) ) ;
511-
532+
512533try {
513534// 检查浏览器是否支持 WebAuthn
514535if ( ! window . PublicKeyCredential ) {
@@ -520,7 +541,7 @@ export class UserLoginComponent implements OnInit, OnDestroy {
520541
521542this . loading = true ;
522543this . cdr . detectChanges ( ) ;
523-
544+
524545// 1. 调用后端 API 获取认证选项(不传递任何用户信息)
525546console . log ( 'Step 1: Requesting authentication options from backend...' ) ;
526547let authOptionsResponse ;
@@ -535,47 +556,51 @@ export class UserLoginComponent implements OnInit, OnDestroy {
535556} else if ( httpError . message ) {
536557errorMessage = httpError . message ;
537558}
538-
559+
539560// 检查是否是没有注册 Passkey 的错误
540- if ( errorMessage . includes ( '没有注册任何 Passkey' ) ||
541- errorMessage . includes ( 'No Passkeys registered' ) ||
542- errorMessage . includes ( '还没有注册任何 Passkey' ) ||
543- errorMessage . includes ( '系统中还没有注册任何 Passkey' ) ) {
561+ if (
562+ errorMessage . includes ( '没有注册任何 Passkey' ) ||
563+ errorMessage . includes ( 'No Passkeys registered' ) ||
564+ errorMessage . includes ( '还没有注册任何 Passkey' ) ||
565+ errorMessage . includes ( '系统中还没有注册任何 Passkey' )
566+ ) {
544567// 直接显示友好提示并返回,不抛出错误避免被全局拦截器捕获
545568this . msg . warning ( '还未注册 Passkey,请注册 Passkey' ) ;
546569console . log ( '=== PASSKEY LOGIN DEBUG END ===' ) ;
547570return ;
548571}
549572throw new Error ( errorMessage ) ;
550573}
551-
574+
552575console . log ( 'Backend auth options response:' , authOptionsResponse ) ;
553-
576+
554577if ( ! authOptionsResponse || authOptionsResponse . code !== 0 ) {
555578console . error ( 'Failed to get auth options:' , authOptionsResponse ) ;
556579// 检查是否是没有注册 Passkey 的错误
557580const errorMessage = authOptionsResponse ?. message || '获取认证选项失败' ;
558- if ( errorMessage . includes ( '没有注册任何 Passkey' ) ||
559- errorMessage . includes ( 'No Passkeys registered' ) ||
560- errorMessage . includes ( '还没有注册任何 Passkey' ) ||
561- errorMessage . includes ( '系统中还没有注册任何 Passkey' ) ) {
581+ if (
582+ errorMessage . includes ( '没有注册任何 Passkey' ) ||
583+ errorMessage . includes ( 'No Passkeys registered' ) ||
584+ errorMessage . includes ( '还没有注册任何 Passkey' ) ||
585+ errorMessage . includes ( '系统中还没有注册任何 Passkey' )
586+ ) {
562587// 直接显示友好提示并返回,不抛出错误避免被全局拦截器捕获
563588this . msg . warning ( '还未注册 Passkey,请注册 Passkey' ) ;
564589console . log ( '=== PASSKEY LOGIN DEBUG END ===' ) ;
565590return ;
566591}
567592throw new Error ( errorMessage ) ;
568593}
569-
594+
570595const authOptions = authOptionsResponse . data ;
571596console . log ( 'Auth options received:' , authOptions ) ;
572-
597+
573598// 检查返回的数据是否有效
574599if ( ! authOptions || ! authOptions . challenge ) {
575600console . error ( 'Invalid auth options:' , authOptions ) ;
576601throw new Error ( '服务器返回的认证选项无效' ) ;
577602}
578-
603+
579604// 2. 转换认证选项格式
580605console . log ( 'Step 2: Converting authentication options...' ) ;
581606const convertedOptions :PublicKeyCredentialRequestOptions = {
@@ -596,30 +621,30 @@ export class UserLoginComponent implements OnInit, OnDestroy {
596621// 3. 调用 WebAuthn API 进行认证
597622console . log ( 'Step 3: Calling WebAuthn API navigator.credentials.get()...' ) ;
598623console . log ( 'Available authenticators will be queried automatically' ) ;
599-
600- const credential = await navigator . credentials . get ( {
624+
625+ const credential = ( await navigator . credentials . get ( {
601626publicKey :convertedOptions
602- } ) as PublicKeyCredential ;
627+ } ) ) as PublicKeyCredential ;
603628
604629if ( ! credential ) {
605630console . error ( 'No credential returned from WebAuthn API' ) ;
606631throw new Error ( '认证失败' ) ;
607632}
608-
633+
609634console . log ( '=== CREDENTIAL DEBUG INFO ===' ) ;
610635console . log ( 'Credential ID:' , credential . id ) ;
611636console . log ( 'Credential ID length:' , credential . id . length ) ;
612637console . log ( 'Credential type:' , credential . type ) ;
613638console . log ( 'Credential rawId length:' , credential . rawId . byteLength ) ;
614639console . log ( 'Credential rawId as base64:' , this . arrayBufferToBase64 ( credential . rawId ) ) ;
615-
640+
616641// 验证 credential.id 和 rawId 的一致性
617642const rawIdBase64 = this . arrayBufferToBase64 ( credential . rawId ) ;
618643console . log ( 'ID consistency check:' ) ;
619644console . log ( ' credential.id:' , credential . id ) ;
620645console . log ( ' rawId as base64:' , rawIdBase64 ) ;
621646console . log ( ' IDs match:' , credential . id === rawIdBase64 ) ;
622-
647+
623648const credentialResponse = credential . response as AuthenticatorAssertionResponse ;
624649console . log ( 'Authenticator response type:' , credentialResponse . constructor . name ) ;
625650console . log ( 'User handle:' , credentialResponse . userHandle ?this . arrayBufferToBase64 ( credentialResponse . userHandle ) :'null' ) ;
@@ -644,10 +669,10 @@ export class UserLoginComponent implements OnInit, OnDestroy {
644669signatureLength :requestPayload . signature . length ,
645670userHandle :requestPayload . userHandle
646671} ) ;
647-
672+
648673const finishResponse = await this . http . post < any > ( '/passkey/authentication/finish?_allow_anonymous=true' , requestPayload ) . toPromise ( ) ;
649674console . log ( 'Backend finish response:' , finishResponse ) ;
650-
675+
651676if ( ! finishResponse || finishResponse . code !== 0 ) {
652677console . error ( 'Backend verification failed:' , finishResponse ) ;
653678throw new Error ( finishResponse ?. message || 'Passkey认证失败' ) ;
@@ -657,13 +682,13 @@ export class UserLoginComponent implements OnInit, OnDestroy {
657682console . log ( 'Step 5: Authentication successful, setting user info...' ) ;
658683const authResult = finishResponse . data ;
659684console . log ( 'Auth result received:' , authResult ) ;
660-
685+
661686this . msg . success ( `Passkey 登录成功!欢迎${ authResult . username || '用户' } ` ) ;
662-
687+
663688// 清空路由复用信息
664689console . log ( 'Clearing reuse tab service...' ) ;
665690this . reuseTabService . clear ( ) ;
666-
691+
667692// 设置用户Token信息
668693if ( authResult && authResult . userId ) {
669694console . log ( 'Valid auth result with userId:' , authResult . userId ) ;
@@ -684,12 +709,12 @@ export class UserLoginComponent implements OnInit, OnDestroy {
684709passwordSetType :authResult . passwordSetType || 'normal' ,
685710authorities :authResult . authorities || [ ]
686711} ;
687-
712+
688713console . log ( 'Setting auth info:' , userInfo ) ;
689-
714+
690715// 设置认证信息
691716this . authnService . auth ( userInfo ) ;
692-
717+
693718// 使用 navigate 方法进行跳转,它会处理 StartupService 的重新加载
694719console . log ( 'Navigating with auth result...' ) ;
695720this . authnService . navigate ( authResult ) ;
@@ -698,28 +723,30 @@ export class UserLoginComponent implements OnInit, OnDestroy {
698723console . error ( 'Invalid auth result - missing userId:' , authResult ) ;
699724throw new Error ( '认证成功但用户数据无效' ) ;
700725}
701-
702726} catch ( error :any ) {
703727console . error ( '=== PASSKEY LOGIN ERROR ===' ) ;
704728console . error ( 'Error type:' , error . constructor . name ) ;
705729console . error ( 'Error message:' , error . message ) ;
706730console . error ( 'Error stack:' , error . stack ) ;
707731console . error ( 'Full error object:' , error ) ;
708-
732+
709733// 检查是否是没有注册 Passkey 的错误
710- if ( error . message && ( error . message . includes ( 'PASSKEY_NOT_REGISTERED' ) ||
711- error . message . includes ( '没有找到可用的凭据' ) ||
712- error . message . includes ( 'No credentials available' ) ||
713- error . message . includes ( '用户未注册' ) ||
714- error . message . includes ( 'credential not found' ) ||
715- error . message . includes ( '没有注册任何 Passkey' ) ||
716- error . message . includes ( 'No Passkeys registered' ) ||
717- error . message . includes ( '还没有注册任何 Passkey' ) ) ) {
734+ if (
735+ error . message &&
736+ ( error . message . includes ( 'PASSKEY_NOT_REGISTERED' ) ||
737+ error . message . includes ( '没有找到可用的凭据' ) ||
738+ error . message . includes ( 'No credentials available' ) ||
739+ error . message . includes ( '用户未注册' ) ||
740+ error . message . includes ( 'credential not found' ) ||
741+ error . message . includes ( '没有注册任何 Passkey' ) ||
742+ error . message . includes ( 'No Passkeys registered' ) ||
743+ error . message . includes ( '还没有注册任何 Passkey' ) )
744+ ) {
718745this . msg . warning ( '还未注册 Passkey,请注册 Passkey' ) ;
719746console . log ( '=== PASSKEY LOGIN DEBUG END ===' ) ;
720747return ;
721748}
722-
749+
723750// 如果是 WebAuthn 相关错误,提供更详细的信息
724751if ( error . name ) {
725752console . error ( 'WebAuthn error name:' , error . name ) ;
@@ -747,10 +774,10 @@ export class UserLoginComponent implements OnInit, OnDestroy {
747774break ;
748775default :
749776console . error ( 'Unknown WebAuthn error' ) ;
750- this . msg . error ( ' Passkey 登录失败:' + ( error . message || '请重试或使用其他登录方式' ) ) ;
777+ this . msg . error ( ` Passkey 登录失败:${ error . message || '请重试或使用其他登录方式' } ` ) ;
751778}
752779} else {
753- this . msg . error ( ' Passkey 登录失败:' + ( error . message || '请重试或使用其他登录方式' ) ) ;
780+ this . msg . error ( ` Passkey 登录失败:${ error . message || '请重试或使用其他登录方式' } ` ) ;
754781}
755782console . log ( '=== PASSKEY LOGIN DEBUG END ===' ) ;
756783} finally {