@@ -44,6 +44,14 @@ export default class AuthnWidget {
4444 'ONE_TIME_DEVICE_OTP_INPUT_REQUIRED'
4545 ]
4646
47+ const idVerificationStates = [
48+ 'ID_VERIFICATION_FAILED' ,
49+ 'ID_VERIFICATION_REQUIRED' ,
50+ 'ID_VERIFICATION_TIMED_OUT' ,
51+ 'ID_VERIFICATION_DEVICE' ,
52+ 'ID_VERIFICATION_OPTIONS' ,
53+ ]
54+
4755 return [ 'USERNAME_PASSWORD_REQUIRED' , 'MUST_CHANGE_PASSWORD' , 'CHANGE_PASSWORD_EXTERNAL' , 'NEW_PASSWORD_RECOMMENDED' ,
4856 'NEW_PASSWORD_REQUIRED' , 'SUCCESSFUL_PASSWORD_CHANGE' , 'ACCOUNT_RECOVERY_USERNAME_REQUIRED' ,
4957 'ACCOUNT_RECOVERY_OTL_VERIFICATION_REQUIRED' , 'RECOVERY_CODE_REQUIRED' , 'PASSWORD_RESET_REQUIRED' ,
@@ -52,8 +60,7 @@ export default class AuthnWidget {
5260 'EXTERNAL_AUTHENTICATION_COMPLETED' , 'EXTERNAL_AUTHENTICATION_FAILED' , 'EXTERNAL_AUTHENTICATION_REQUIRED' ,
5361 'DEVICE_PROFILE_REQUIRED' , 'REGISTRATION_REQUIRED' , 'REFERENCE_ID_REQUIRED' , 'CURRENT_CREDENTIALS_REQUIRED' ,
5462 'DEVICE_SELECTION_REQUIRED' , 'MFA_COMPLETED' , 'MFA_FAILED' , 'OTP_REQUIRED' , 'ASSERTION_REQUIRED' ,
55- 'PUSH_CONFIRMATION_REJECTED' , 'PUSH_CONFIRMATION_TIMED_OUT' , 'PUSH_CONFIRMATION_WAITING' ,
56- 'ID_VERIFICATION_FAILED' , 'ID_VERIFICATION_REQUIRED' , 'ID_VERIFICATION_TIMED_OUT' , 'ACCOUNT_LINKING_FAILED' ,
63+ 'PUSH_CONFIRMATION_REJECTED' , 'PUSH_CONFIRMATION_TIMED_OUT' , 'PUSH_CONFIRMATION_WAITING' , 'ACCOUNT_LINKING_FAILED' ,
5764 'SECURID_CREDENTIAL_REQUIRED' , 'SECURID_NEXT_TOKENCODE_REQUIRED' , 'SECURID_REAUTHENTICATION_REQUIRED' ,
5865 'SECURID_SYSTEM_PIN_RESET_REQUIRED' , 'SECURID_USER_PIN_RESET_REQUIRED' , 'EMAIL_VERIFICATION_REQUIRED' , 'EMAIL_VERIFICATION_OTP_REQUIRED' ,
5966 'MFA_SETUP_REQUIRED' , 'DEVICE_PAIRING_METHOD_REQUIRED' , 'EMAIL_PAIRING_TARGET_REQUIRED' ,
@@ -64,7 +71,8 @@ export default class AuthnWidget {
6471 'USER_ID_REQUIRED' , 'AUTHENTICATOR_SELECTION_REQUIRED' , 'INPUT_REQUIRED' , 'ENTRUST_FAILED' , 'FRAUD_EVALUATION_CHECK_REQUIRED' , 'AUTHENTICATION_CODE_RESPONSE_REQUIRED'
6572 ]
6673 . concat ( oauthUserAuthorizationStates )
67- . concat ( oneTimeDeviceOtpStates ) ;
74+ . concat ( oneTimeDeviceOtpStates )
75+ . concat ( idVerificationStates ) ;
6876 }
6977
7078 static get COMMUNICATION_ERROR_MSG ( ) {
@@ -150,7 +158,13 @@ export default class AuthnWidget {
150158 this . registerIdVerificationRequiredEventHandler = this . registerIdVerificationRequiredEventHandler . bind ( this ) ;
151159 this . postIdVerificationRequired = this . postIdVerificationRequired . bind ( this ) ;
152160 this . handleIdVerificationInProgress = this . handleIdVerificationInProgress . bind ( this ) ;
153- this . handleIdVerificationFailed = this . handleIdVerificationFailed . bind ( this ) ;
161+ this . handleIdVerificationDevice = this . handleIdVerificationDevice . bind ( this ) ;
162+ this . deviceAuthentication = this . deviceAuthentication . bind ( this ) ;
163+ this . handleIdVerificationOptions = this . handleIdVerificationOptions . bind ( this ) ;
164+ this . optionsAuthentication = this . optionsAuthentication . bind ( this ) ;
165+ this . handleRetryVerification = this . handleRetryVerification . bind ( this ) ;
166+ this . handleCancelAuthentication = this . handleCancelAuthentication . bind ( this ) ;
167+ this . postIdVerificationTimedOut = this . postIdVerificationTimedOut . bind ( this ) ;
154168 this . checkSecurIdPinReset = this . checkSecurIdPinReset . bind ( this ) ;
155169 this . postAssertionRequired = this . postAssertionRequired . bind ( this ) ;
156170 this . postTOTPActivationRequired = this . postTOTPActivationRequired . bind ( this ) ;
@@ -219,7 +233,9 @@ export default class AuthnWidget {
219233 this . addPostRenderCallback ( 'ID_VERIFICATION_REQUIRED' , this . postIdVerificationRequired ) ;
220234 this . addPostRenderCallback ( 'ID_VERIFICATION_IN_PROGRESS' , this . handleIdVerificationInProgress ) ;
221235 this . addPostRenderCallback ( 'ID_VERIFICATION_COMPLETED' , this . postContinueAuthentication ) ;
222- this . addEventHandler ( 'ID_VERIFICATION_FAILED' , this . handleIdVerificationFailed ) ;
236+ this . addPostRenderCallback ( 'ID_VERIFICATION_TIMED_OUT' , this . postIdVerificationTimedOut ) ;
237+ this . addEventHandler ( 'ID_VERIFICATION_DEVICE' , this . handleIdVerificationDevice ) ;
238+ this . addEventHandler ( 'ID_VERIFICATION_OPTIONS' , this . handleIdVerificationOptions ) ;
223239 this . addEventHandler ( 'SECURID_USER_PIN_RESET_REQUIRED' , this . checkSecurIdPinReset ) ;
224240 this . addPostRenderCallback ( 'EMAIL_VERIFICATION_REQUIRED' , this . postEmailVerificationRequired ) ;
225241 this . addPostRenderCallback ( 'TOTP_ACTIVATION_REQUIRED' , this . postTOTPActivationRequired ) ;
@@ -1045,31 +1061,21 @@ export default class AuthnWidget {
10451061 async registerIdVerificationRequiredEventHandler ( ) {
10461062 let data = this . store . getStore ( ) ;
10471063
1048- if ( data . errorDetails !== undefined )
1049- {
1050- document . getElementById ( "requiredDescription" ) . style . display = "none" ;
1051- document . getElementById ( "errorDescription" ) . style . display = "block" ;
1052- } else {
1053- document . getElementById ( "requiredDescription" ) . style . display = "block" ;
1054- document . getElementById ( "errorDescription" ) . style . display = "none" ;
1055- }
1056-
1057- const options = {
1058- method : 'GET'
1059- }
1060- let qrUrlRespone = await fetch ( data . qrUrl , options ) ;
1061- let qrCode = await qrUrlRespone . text ( ) ;
1062- document . getElementById ( 'qrCode' ) . src = qrCode ;
1064+ document . getElementById ( 'qrCode' ) . src = data . qrUrl || "" ;
10631065 document . getElementById ( 'qrCodeBlock' ) . style . display = 'block' ;
10641066
1065- var verificationCodeToken = data . verificationCode . match ( / .{ 1 , 4 } / g) ;
1066- document . getElementById ( 'verificationCode' ) . innerHTML = verificationCodeToken . join ( ' ' ) ;
1067+ document . getElementById ( 'verificationCode' ) . innerHTML = data . verificationCode || "" ;
1068+
1069+ document . getElementById ( 'retryVerification' ) . addEventListener ( 'click' , this . handleRetryVerification ) ;
1070+
1071+ if ( data . newTab && data . webVerificationUrl !== undefined &&
1072+ ( this . store . getPreviousStore ( ) . verificationCode !== data . verificationCode ) ) {
1073+ console . log ( "web link opens newtab: " + data . webVerificationUrl ) ;
1074+ window . open ( data . webVerificationUrl , '_blank' ) ;
1075+ }
10671076 }
10681077
10691078 postIdVerificationRequired ( ) {
1070- document . getElementById ( 'copy' )
1071- . addEventListener ( 'click' , this . copyCode ) ;
1072-
10731079 this . pollCheckGet ( this . store . getStore ( ) . verificationCode , 5000 ) ;
10741080 }
10751081
@@ -1079,29 +1085,23 @@ export default class AuthnWidget {
10791085 } , 5000 )
10801086 }
10811087
1082- handleIdVerificationFailed ( ) {
1083- let data = this . store . getStore ( ) ;
1084-
1085- if ( data . errorDetails !== undefined ) {
1086- document . getElementById ( "errorMessage" ) . innerHTML = this . makeIdVerificationErrorMessage ( data . errorDetails ) ;
1087- }
1088- }
1089-
1090- copyCode ( event ) {
1091- event . preventDefault ( ) ;
1092- let range = document . createRange ( ) ;
1093- range . selectNode ( document . getElementById ( 'verificationCode' ) ) ;
1094- window . getSelection ( ) . removeAllRanges ( ) ;
1095- window . getSelection ( ) . addRange ( range ) ;
1096- document . execCommand ( 'copy' ) ;
1097- window . getSelection ( ) . removeAllRanges ( ) ;
1098- }
1099-
11001088 async pollCheckGet ( currentVerificationCode , timeout ) {
11011089 let fetchUtil = this . store . fetchUtil ;
11021090 let result = await fetchUtil . postFlow ( this . store . flowId , 'poll' , '{}' ) ;
11031091 let newState = await result . json ( ) ;
11041092
1093+ if ( newState . txStatus === 'INITIATED' )
1094+ {
1095+ document . getElementById ( "requiredHeader" ) . style . display = "none" ;
1096+ document . getElementById ( "requiredDescription" ) . style . display = "none" ;
1097+ document . getElementById ( "startedHeader" ) . style . display = "block" ;
1098+ document . getElementById ( "startedDescription" ) . style . display = "block" ;
1099+ }
1100+ if ( newState . code === 'RESOURCE_NOT_FOUND' ) {
1101+ console . log ( "resource not found, stop polling flow" ) ;
1102+ return ;
1103+ }
1104+
11051105 let pollAgain =
11061106 ( newState . status === 'ID_VERIFICATION_REQUIRED' ) &&
11071107 ( newState . verificationCode === currentVerificationCode ) ;
@@ -1112,27 +1112,128 @@ export default class AuthnWidget {
11121112 . catch ( ( ) => this . generalErrorRenderer ( AuthnWidget . COMMUNICATION_ERROR_MSG ) ) ;
11131113 }
11141114 else {
1115- setTimeout ( ( ) => {
1115+ this . pollCheckGetHandler = setTimeout ( ( ) => {
11161116 this . pollCheckGet ( currentVerificationCode , timeout ) ;
1117- } , timeout )
1117+ } , timeout ) ;
11181118 }
11191119 }
11201120
1121- makeIdVerificationErrorMessage ( errorDetails ) {
1122- var errorMessage = '' ;
1121+ async handleRetryVerification ( event , cnt ) {
1122+ const maxretries = 10 ;
1123+ if ( cnt === undefined ) cnt = 1 ;
1124+ else if ( cnt > maxretries ) {
1125+ console . log ( "exceeded max retries, stop sending retry request" ) ;
1126+ return ;
1127+ }
11231128
1124- errorDetails . forEach ( errorDetail =>
1125- {
1126- if ( errorMessage === '' ) {
1127- errorMessage = errorDetail . userMessage
1128- }
1129- else {
1130- var append = '<br>' + errorDetail . userMessage
1131- errorMessage += append
1132- }
1133- } )
1129+ clearTimeout ( this . pollCheckGetHandler ) ;
1130+ // make cancel button non-clickable after one click
1131+ event . target . style . pointerEvents = "none" ;
1132+
1133+ let actionId = event . target . id ;
1134+ let data = this . store ;
1135+ data . prevState = data . state ;
1136+ // store.reduce is used to prevent notifyListener from being called at the end of store.dispatch
1137+ // when polling is in progress and flow is not available so previous state is returned;
1138+ // don't start extra polling tasks during resubmission of retryVerification request.
1139+ data . state = await this . store . reduce ( 'POST_FLOW' , actionId ) ;
1140+ if ( data . prevState . username && ! data . state . username ) {
1141+ data . state . username = data . prevState . username ;
1142+ }
1143+ console . log ( 'dispatching retry: ' + actionId + " #" + cnt ) ;
1144+ console . log ( data . state ) ;
1145+
1146+ if ( data . state . status === 'ID_VERIFICATION_REQUIRED' ) {
1147+ console . log ( "not returning to first screen, resubmiting post request in 1 second" ) ;
1148+ await new Promise ( r => setTimeout ( r , 1000 ) ) ;
1149+ this . handleRetryVerification ( event , cnt + 1 ) ;
1150+ } else {
1151+ this . store . notifyListeners ( ) ;
1152+ }
1153+ }
11341154
1135- return errorMessage ;
1155+ async handleCancelAuthentication ( event ) {
1156+ await this . store . dispatch ( 'POST_FLOW' , event . target . id ) ;
1157+ const state = this . store . state ;
1158+ // cancel in login flow goes to login screen
1159+ // cancel in registration flow renders registration_failed error in a separate page
1160+ if ( state . status !== 'USERNAME_PASSWORD_REQUIRED' ) {
1161+ this . generalErrorRenderer ( state . userMessages ) ;
1162+ }
1163+ }
1164+
1165+ postIdVerificationTimedOut ( ) {
1166+ document . getElementById ( 'cancelAuthentication' ) . addEventListener ( 'click' , this . handleCancelAuthentication ) ;
1167+ }
1168+
1169+ handleIdVerificationDevice ( ) {
1170+ document . getElementById ( 'other' ) . addEventListener ( 'click' , this . deviceAuthentication ) ;
1171+ document . getElementById ( 'self' ) . addEventListener ( 'click' , this . deviceAuthentication ) ;
1172+ document . getElementById ( 'cancelAuthentication' ) . addEventListener ( 'click' , this . handleCancelAuthentication ) ;
1173+ }
1174+
1175+ deviceAuthentication ( event ) {
1176+ console . log ( "selected device: " + event . target . id ) ;
1177+ document . getElementById ( "AuthnWidgetForm" ) . style . pointerEvents = "none" ;
1178+ const data = { "deviceAuthentication" : event . target . id } ;
1179+ this . store . dispatch ( 'POST_FLOW' , "deviceAuthentication" , JSON . stringify ( data ) ) ;
1180+ }
1181+
1182+ handleIdVerificationOptions ( ) {
1183+ let data = this . store . getStore ( ) ;
1184+ if ( data . forcePolicy ) {
1185+ document . getElementById ( "description" ) . innerHTML = "Select a method to receive a web link on your mobile device to start the verification process." ;
1186+ document . getElementById ( "qrbtn" ) . style . display = "none" ;
1187+
1188+ const radios = document . getElementsByName ( "radioGroup" ) ;
1189+ for ( let i = 0 ; i < radios . length ; i ++ ) {
1190+ radios [ i ] . checked = true ;
1191+ }
1192+ document . getElementById ( "nextbtn" ) . disabled = false ;
1193+ document . getElementById ( "cancelAuthentication" ) . style . display = "block" ;
1194+ document . getElementById ( "retryoptions" ) . style . display = "none" ;
1195+ }
1196+ if ( data . errorMessage ) {
1197+ document . getElementById ( "nextbtn" ) . disabled = true ;
1198+ } else {
1199+ if ( data . emails . length ) {
1200+ document . getElementById ( 'emailRadio' ) . addEventListener ( 'click' , this . optionsRadioSelected ) ;
1201+ }
1202+ if ( data . phones . length ) {
1203+ document . getElementById ( 'mobileRadio' ) . addEventListener ( 'click' , this . optionsRadioSelected ) ;
1204+ }
1205+ document . getElementById ( 'qrbtn' ) . addEventListener ( 'click' , this . optionsAuthentication ) ;
1206+ document . getElementById ( 'nextbtn' ) . addEventListener ( 'click' , this . optionsAuthentication ) ;
1207+ }
1208+ document . getElementById ( 'cancelAuthentication' ) . addEventListener ( 'click' , this . handleCancelAuthentication ) ;
1209+ }
1210+
1211+ optionsRadioSelected ( ) {
1212+ document . getElementById ( "nextbtn" ) . disabled = false ;
1213+ }
1214+
1215+ optionsAuthentication ( event ) {
1216+ console . log ( "selected option: " + event . target . id ) ;
1217+ document . getElementById ( "AuthnWidgetForm" ) . style . pointerEvents = "none" ;
1218+ let email = null ;
1219+ let phone = null ;
1220+ const qrOnly = event . target . id === "qrbtn" ;
1221+ if ( qrOnly === true ) {
1222+ console . log ( "skip notification, show qr code only" ) ;
1223+ } else if ( document . querySelector ( 'input[name="radioGroup"]:checked' ) ) {
1224+ const radio = document . querySelector ( 'input[name="radioGroup"]:checked' ) . value ;
1225+ if ( radio === "emailRadio" ) {
1226+ const select = document . getElementById ( "emails" ) ;
1227+ email = select . options [ select . selectedIndex ] . value ;
1228+ console . log ( "selected email: " + email ) ;
1229+ } else if ( radio === "mobileRadio" ) {
1230+ const select = document . getElementById ( "mobiles" ) ;
1231+ phone = select . options [ select . selectedIndex ] . value ;
1232+ console . log ( "selected phone: " + phone ) ;
1233+ }
1234+ }
1235+ const data = { "email" : email , "phone" : phone , "optionsAuthentication" : true } ;
1236+ this . store . dispatch ( 'POST_FLOW' , "optionsAuthentication" , JSON . stringify ( data ) ) ;
11361237 }
11371238
11381239 checkSecurIdPinReset ( ) {
0 commit comments