@@ -11,140 +11,146 @@ import { useNavigate } from 'react-router-dom';
1111import styles from '@/styles/registerPasskey.module.css' ;
1212import { isPasskeySupported , parseUserAgent } from './utils' ;
1313import { createFetchWithAuth } from './fetchWithAuth' ;
14+ import DeviceNameModal from './components/DeviceNameModal' ;
1415
1516const RegisterPasskey : React . FC = ( ) => {
1617 const { apiHost, mode } = useAuth ( ) ;
1718 const { validateToken } = useInternalAuth ( ) ;
1819 const navigate = useNavigate ( ) ;
20+
1921 const [ status , setStatus ] = useState < 'idle' | 'success' | 'error' | 'loading' > ( 'idle' ) ;
2022 const [ message , setMessage ] = useState ( '' ) ;
2123 const [ passkeyAvailable , setPasskeyAvailable ] = useState ( false ) ;
2224
25+ const [ showDeviceModal , setShowDeviceModal ] = useState ( false ) ;
26+ const [ pendingMetadata , setPendingMetadata ] = useState < {
27+ platform : string ;
28+ browser : string ;
29+ deviceInfo : string ;
30+ } | null > ( null ) ;
31+
2332 const fetchWithAuth = createFetchWithAuth ( {
2433 authMode : mode ,
2534 authHost : apiHost ,
2635 } ) ;
2736
28- const handlePasskeyRegister = async ( ) => {
29- setStatus ( 'loading' ) ;
30- const friendlyName = prompt (
31- "Name this device (e.g., 'MacBook Pro', 'iPhone', 'YubiKey')"
32- ) ;
37+ useEffect ( ( ) => {
38+ async function checkSupport ( ) {
39+ const supported = await isPasskeySupported ( ) ;
40+ setPasskeyAvailable ( supported ) ;
41+ }
42+
43+ checkSupport ( ) ;
44+ } , [ ] ) ;
3345
46+ const openDeviceModal = ( ) => {
3447 const { platform, browser, deviceInfo } = parseUserAgent ( ) ;
3548
49+ setPendingMetadata ( { platform, browser, deviceInfo } ) ;
50+ setShowDeviceModal ( true ) ;
51+ } ;
52+
53+ const continueRegistration = async ( friendlyName : string ) => {
54+ if ( ! pendingMetadata ) return ;
55+
56+ const { platform, browser, deviceInfo } = pendingMetadata ;
57+
58+ setStatus ( 'loading' ) ;
59+
3660 try {
3761 const challengeRes = await fetchWithAuth ( `/webAuthn/register/start` , {
3862 method : 'GET' ,
39- headers : {
40- 'Content-Type' : 'application/json' ,
41- } ,
63+ headers : { 'Content-Type' : 'application/json' } ,
4264 credentials : 'include' ,
4365 } ) ;
4466
4567 if ( ! challengeRes . ok ) {
46- setStatus ( 'error' ) ;
47- setMessage ( 'Something went wrong registering passkey.' ) ;
48- return ;
68+ throw new Error ( 'Failed to fetch challenge' ) ;
4969 }
5070
5171 const options = await challengeRes . json ( ) ;
5272
5373 let attResp : RegistrationResponseJSON ;
74+
5475 try {
5576 attResp = await startRegistration ( { optionsJSON : options } ) ;
56-
57- await verifyPassKey ( attResp , { friendlyName, platform, browser, deviceInfo } ) ;
5877 } catch ( error ) {
5978 if ( error instanceof WebAuthnError ) {
60- console . error (
61- `Error occurred with webAuthn, ${ error . name } - ${ error . code } -${ error . stack } `
62- ) ;
63- setStatus ( 'error' ) ;
64- setMessage ( `Error: ${ error . name } ` ) ;
65- } else {
66- console . error ( 'A problem happened.' , error ) ;
67- setStatus ( 'error' ) ;
68- setMessage ( `Error: ${ error } ` ) ;
79+ throw new Error ( error . name ) ;
6980 }
70- return ;
81+ throw error ;
7182 }
7283
84+ await verifyPassKey ( attResp , {
85+ friendlyName,
86+ platform,
87+ browser,
88+ deviceInfo,
89+ } ) ;
90+
7391 setStatus ( 'success' ) ;
7492 setMessage ( 'Passkey registered successfully.' ) ;
7593 navigate ( '/' ) ;
76- } catch ( err ) {
77- console . error ( err ) ;
94+ } catch ( error ) {
95+ console . error ( error ) ;
7896 setStatus ( 'error' ) ;
79- setMessage ( 'Something went wrong registering passkey.' ) ;
97+ setMessage ( 'Error registering passkey.' ) ;
98+ } finally {
99+ setShowDeviceModal ( false ) ;
100+ setPendingMetadata ( null ) ;
80101 }
81102 } ;
82103
83104 const verifyPassKey = async (
84105 attResp : RegistrationResponseJSON ,
85106 metadata : {
86- friendlyName : string | null ;
107+ friendlyName : string ;
87108 platform : string ;
88109 browser : string ;
89110 deviceInfo : string ;
90111 }
91112 ) => {
92- try {
93- const verificationResp = await fetchWithAuth ( `/webAuthn/register/finish` , {
94- method : 'POST' ,
95- headers : {
96- 'Content-Type' : 'application/json' ,
97- } ,
98- body : JSON . stringify ( {
99- attestationResponse : attResp ,
100- metadata,
101- } ) ,
102- credentials : 'include' ,
103- } ) ;
104-
105- if ( ! verificationResp . ok ) {
106- setStatus ( 'error' ) ;
107- setMessage ( 'Something went wrong registering passkey.' ) ;
108- return ;
109- }
110-
111- await validateToken ( ) ;
112- } catch ( error ) {
113- console . error ( `An error occurred: ${ error } ` ) ;
113+ const verificationResp = await fetchWithAuth ( `/webAuthn/register/finish` , {
114+ method : 'POST' ,
115+ headers : { 'Content-Type' : 'application/json' } ,
116+ body : JSON . stringify ( {
117+ attestationResponse : attResp ,
118+ metadata,
119+ } ) ,
120+ credentials : 'include' ,
121+ } ) ;
122+
123+ if ( ! verificationResp . ok ) {
124+ throw new Error ( 'Verification failed' ) ;
114125 }
115- } ;
116126
117- useEffect ( ( ) => {
118- async function checkSupport ( ) {
119- const supported = await isPasskeySupported ( ) ;
120- setPasskeyAvailable ( supported ) ;
121- }
122-
123- checkSupport ( ) ;
124- } , [ ] ) ;
127+ await validateToken ( ) ;
128+ } ;
125129
126130 return (
127- < div className = { styles . container } >
128- < div className = { styles . card } >
129- { ! passkeyAvailable ? (
130- < div className = { styles . loading } >
131- < div className = { styles . spinner } > </ div >
132- < span > Checking for Passkey Support... </ span >
133- </ div >
134- ) : (
135- passkeyAvailable && (
131+ < >
132+ < div className = { styles . container } >
133+ < div className = { styles . card } >
134+ { ! passkeyAvailable ? (
135+ < div className = { styles . loading } >
136+ < div className = { styles . spinner } > </ div >
137+ < span > Checking for Passkey Support... </ span >
138+ </ div >
139+ ) : (
136140 < div className = { styles . supported } >
137141 < h2 className = { styles . title } > Secure Your Account with a Passkey</ h2 >
138142 < p className = { styles . description } >
139143 Your device supports passkeys! Register one to skip passwords forever.
140144 </ p >
145+
141146 < button
142- onClick = { handlePasskeyRegister }
147+ onClick = { openDeviceModal }
143148 disabled = { status === 'loading' }
144149 className = { styles . button }
145150 >
146151 { status === 'loading' ? 'Registering...' : 'Register Passkey' }
147152 </ button >
153+
148154 { message && (
149155 < p
150156 className = { `${ styles . message } ${
@@ -155,10 +161,19 @@ const RegisterPasskey: React.FC = () => {
155161 </ p >
156162 ) }
157163 </ div >
158- )
159- ) }
164+ ) }
165+ </ div >
160166 </ div >
161- </ div >
167+
168+ < DeviceNameModal
169+ isOpen = { showDeviceModal }
170+ onCancel = { ( ) => {
171+ setShowDeviceModal ( false ) ;
172+ setPendingMetadata ( null ) ;
173+ } }
174+ onConfirm = { continueRegistration }
175+ />
176+ </ >
162177 ) ;
163178} ;
164179
0 commit comments