1
- import { loc , createCallout } from '@okta/courage' ;
1
+ import { _ , loc , createCallout } from '@okta/courage' ;
2
2
import { FORMS as RemediationForms } from '../../ion/RemediationConstants' ;
3
3
import { BaseForm , BaseView , createIdpButtons , createCustomButtons } from '../internals' ;
4
+ import CryptoUtil from '../../../util/CryptoUtil' ;
4
5
import DeviceFingerprinting from '../utils/DeviceFingerprinting' ;
5
6
import IdentifierFooter from '../components/IdentifierFooter' ;
6
7
import Link from '../components/Link' ;
@@ -13,6 +14,7 @@ import { getForgotPasswordLink } from '../utils/LinksUtil';
13
14
import CookieUtil from 'util/CookieUtil' ;
14
15
import CustomAccessDeniedErrorMessage from './shared/CustomAccessDeniedErrorMessage' ;
15
16
import Util from 'util/Util' ;
17
+ import { getMessageFromBrowserError } from '../../ion/i18nTransformer' ;
16
18
17
19
const CUSTOM_ACCESS_DENIED_KEY = 'security.access_denied_custom_message' ;
18
20
@@ -115,6 +117,7 @@ const Body = BaseForm.extend({
115
117
// When a user enters invalid credentials, /introspect returns an error,
116
118
// along with a user object containing the identifier entered by the user.
117
119
this . $el . find ( '.identifier-container' ) . remove ( ) ;
120
+ this . getWebauthnAutofillUICredentialsAndSave ( ) ;
118
121
} ,
119
122
120
123
/**
@@ -144,7 +147,7 @@ const Body = BaseForm.extend({
144
147
// because we want to allow the user to choose from previously used identifiers.
145
148
newSchema = {
146
149
...newSchema ,
147
- autoComplete : Util . getAutocompleteValue ( this . options . settings , 'username' )
150
+ autoComplete : Util . getAutocompleteValue ( this . options . settings , 'username' ) + this . options . appState . get ( 'webauthnAutofillUIChallenge' ) ?. challengeData ? ' webauthn' : ''
148
151
} ;
149
152
} else if ( schema . name === 'credentials.passcode' ) {
150
153
newSchema = {
@@ -245,6 +248,56 @@ const Body = BaseForm.extend({
245
248
if ( cookieUsername ) {
246
249
this . model . set ( 'identifier' , cookieUsername ) ;
247
250
}
251
+ } ,
252
+
253
+ remove ( ) {
254
+ BaseForm . prototype . remove . apply ( this , arguments ) ;
255
+ if ( this . webauthnAbortController ) {
256
+ this . webauthnAbortController . abort ( ) ;
257
+ this . webauthnAbortController = null ;
258
+ }
259
+ } ,
260
+
261
+ getWebauthnAutofillUICredentialsAndSave ( ) {
262
+ const challengeData = this . options . appState . get ( 'webauthnAutofillUIChallenge' ) ?. challengeData ;
263
+ if ( ! challengeData ) return ;
264
+ const options = _ . extend ( { } , challengeData , {
265
+ challenge : CryptoUtil . strToBin ( challengeData . challenge ) ,
266
+ } ) ;
267
+
268
+ if ( typeof AbortController !== 'undefined' ) {
269
+ this . webauthnAbortController = new AbortController ( ) ;
270
+ }
271
+
272
+ navigator . credentials . get ( {
273
+ mediation : 'conditional' ,
274
+ publicKey : options ,
275
+ signal : this . webauthnAbortController && this . webauthnAbortController . signal
276
+ } ) . then ( ( assertion ) => {
277
+ const userHandle = CryptoUtil . binToStr ( assertion . response . userHandle ?? '' ) ;
278
+ if ( _ . isEmpty ( userHandle ) ) {
279
+ this . model . trigger ( 'error' , this . model , { responseJSON : { errorSummary : 'Invalid Passkey' } } ) ;
280
+ return ;
281
+ }
282
+ const credentials = {
283
+ clientData : CryptoUtil . binToStr ( assertion . response . clientDataJSON ) ,
284
+ authenticatorData : CryptoUtil . binToStr ( assertion . response . authenticatorData ) ,
285
+ signatureData : CryptoUtil . binToStr ( assertion . response . signature ) ,
286
+ userHandle
287
+ } ;
288
+
289
+ //TODO, is there a better way for this?
290
+ this . options . appState . trigger ( 'invokeAction' , RemediationForms . CHALLENGE_AUTHENTICATOR , { 'credentials' : credentials } ) ;
291
+ } , ( error ) => {
292
+ // Do not display if it is abort error triggered by code when switching.
293
+ // this.webauthnAbortController would be null if abort was triggered by code.
294
+ if ( this . webauthnAbortController ) {
295
+ this . model . trigger ( 'error' , this . model , { responseJSON : { errorSummary : getMessageFromBrowserError ( error ) } } ) ;
296
+ }
297
+ } ) . finally ( ( ) => {
298
+ // unset webauthnAbortController on successful authentication or error
299
+ this . webauthnAbortController = null ;
300
+ } ) ;
248
301
}
249
302
} ) ;
250
303
0 commit comments