From fe3086549c2996a65ecb15f95750746ed20cd0a2 Mon Sep 17 00:00:00 2001 From: Lakshmi Ramadoss Date: Tue, 30 Aug 2022 19:32:59 +0530 Subject: [PATCH] added kmsi ui --- .../public/css/style.css | 13 + .../public/js/idcsAuthnSDK.js | 969 +++++---- .../public/js/loginApp.js | 1746 ++++++++--------- idcs-authn-api-signin-app/run.sh | 1 + 4 files changed, 1279 insertions(+), 1450 deletions(-) diff --git a/idcs-authn-api-signin-app/public/css/style.css b/idcs-authn-api-signin-app/public/css/style.css index 24ca913..151f104 100644 --- a/idcs-authn-api-signin-app/public/css/style.css +++ b/idcs-authn-api-signin-app/public/css/style.css @@ -228,6 +228,19 @@ input.on__error { transform: translate3d(-640px, 0, 0); } +.externalIdpBtn{ + display: flex; + width: 200px; + height: 36px; + border-radius: 30px; + align-items: center; + justify-content: center; + font-size: 15px; + border : solid; + border-width: 1.0px; + cursor: pointer; +} + .qrcode-text { font-weight: normal; } diff --git a/idcs-authn-api-signin-app/public/js/idcsAuthnSDK.js b/idcs-authn-api-signin-app/public/js/idcsAuthnSDK.js index 011ab71..907bbbb 100644 --- a/idcs-authn-api-signin-app/public/js/idcsAuthnSDK.js +++ b/idcs-authn-api-signin-app/public/js/idcsAuthnSDK.js @@ -1,177 +1,155 @@ function IdcsAuthnSDK(app) { - this.app = app; - this.sdkErrors = { // 9000 series: Errors coming from server-side initialization - error9000 : {code: 'SDK-AUTH-9000', msg : 'Stored Access Token not found.'}, - error9001 : {code: 'SDK-AUTH-9001', msg : 'Initial state not found.'}, + error9000: { code: 'SDK-AUTH-9000', msg: 'Stored Access Token not found.' }, + error9001: { code: 'SDK-AUTH-9001', msg: 'Initial state not found.' }, // 9010 series: Unknown Errors during authentication - error9010 : {code: 'SDK-AUTH-9010', msg : 'Unknown error occurred.'}, - error9011 : {code: 'SDK-AUTH-9011', msg : 'Unrecognized status returned by authenticate.'}, - error9012 : {code: 'SDK-AUTH-9012', msg : 'Unrecognized error returned by password reset.'}, + error9010: { code: 'SDK-AUTH-9010', msg: 'Unknown error occurred.' }, + error9011: { code: 'SDK-AUTH-9011', msg: 'Unrecognized status returned by authenticate.' }, // 9020 series: Password reset errors - error9020 : {code: 'SDK-AUTH-9020', msg : 'Validation failed. Your reset password link might have expired.'}, - error9021 : {code: 'SDK-AUTH-9021', msg : 'Chosen password violates one or more policies.'}, - error9022 : {code: 'SDK-AUTH-9022', msg : 'Invalid Token'}, - error9023 : {code: 'SDK-AUTH-9023', msg : 'Invalid Passcode.'}, - error9024 : {code: 'SDK-AUTH-9024', msg : 'Your answer didn\'t match. Please try again.'}, + error9020: { code: 'SDK-AUTH-9020', msg: 'Validation failed. Your reset password link might have expired.' }, + error9021: { code: 'SDK-AUTH-9021', msg: 'Chosen password violates one or more policies.' }, // Invalid payload - error9999 : {code: 'SDK-AUTH-9999', msg : 'System error: invalid data. Please contact the administrator.'} + error9999: { code: 'SDK-AUTH-9999', msg: 'System error: invalid data. Please contact the administrator.' } }; - this.initAuthentication = function() { + this.initAuthentication = function () { this.app.logMsg('[IdcsAuthnSDK] Init authentication...'); var error = this.app.getBackendErrorMsg(); - if ( !this.app.getAccessToken() ) { - this.app.logMsg(this.sdkErrors.error9000.msg); - this.app.setLoginErrorMessage(this.sdkErrors.error9000); + if (!this.app.getAccessToken()) { + this.app.logMsg(this.sdkErrors.error9000.msg); + this.app.setLoginErrorMessage(this.sdkErrors.error9000); } //ResetPassword does not need initial state else if (this.app.getOperation() === 'resetpwd') { - this.app.logMsg("Password Reset Flow..") - this.validateUserToken(this.app.getToken()); - } + this.app.logMsg("Password Reset Flow..") + this.validateUserToken(this.app.getToken()); + } //user successfully authenticated in external IDP and is already provisioned in IDCS - else if (this.app.getIDPAuthnToken()){ - this.app.logMsg('[IdcsAuthnSDK] inside handover from IDP'); - this.app.logMsg('[IdcsAuthnSDK] idpauthntoken: '+this.app.getIDPAuthnToken()); - var payload = {'authnToken': this.app.getIDPAuthnToken()}; - this.createSession(payload); + else if (this.app.getIDPAuthnToken()) { + this.app.logMsg('[IdcsAuthnSDK] inside handover from IDP'); + this.app.logMsg('[IdcsAuthnSDK] idpauthntoken: ' + this.app.getIDPAuthnToken()); + var payload = { 'authnToken': this.app.getIDPAuthnToken() }; + this.createSession(payload); } //user successfully authenticated in external IDP and is NOT provisioned in IDCS - else if (this.app.isSocialRegistrationRequired()){ - var socialData = this.app.getSocialData(); - this.app.setRequestState(socialData.requestState); - this.app.displaySocialRegistrationForm(socialData); + else if (this.app.isSocialRegistrationRequired()) { + var socialData = this.app.getSocialData(); + this.app.setRequestState(socialData.requestState); + this.app.displaySocialRegistrationForm(socialData); } else if (!this.app.getInitialState()) { - this.app.logMsg('[IdcsAuthnSDK] Error: ' + this.sdkErrors.error9001.msg); - if (error != null) { - this.app.logMsg('[IdcsAuthnSDK] Error: ' + error); - this.app.setLoginErrorMessage(JSON.parse(error)); - } - else { - this.app.setLoginErrorMessage(this.sdkErrors.error9001); - } + this.app.logMsg('[IdcsAuthnSDK] Error: ' + this.sdkErrors.error9001.msg); + if (error != null) { + this.app.logMsg('[IdcsAuthnSDK] Error: ' + error); + this.app.setLoginErrorMessage(JSON.parse(error)); + } + else { + this.app.setLoginErrorMessage(this.sdkErrors.error9001); + } } else { - this.app.logMsg('[IdcsAuthnSDK] Initializing authentication with existing initial state from IDCS.'); - var initialData = JSON.parse( this.app.getInitialState() ); - this.app.logMsg('[IdcsAuthnSDK] InitialData: ' + this.app.getInitialState()); - - if (initialData.status === 'success') { - this.app.setRequestState(initialData.requestState); - this.app.logMsg('[IdcsAuthnSDK] status==success'); - this.app.logMsg(initialData.requestState); - this.app.logMsg(initialData.nextOp); - this.app.logMsg(initialData.nextAuthFactors); - - // Check if the typical user name + pwd UI needs to be displayed or - // just the user name (if "user name first" feature is turned on in IDCS) - if ((initialData.requestState) && - (initialData.nextOp.indexOf('credSubmit') >= 0) && - ((initialData.nextAuthFactors.indexOf('USERNAME_PASSWORD') >= 0) || (initialData.nextAuthFactors.indexOf('USERNAME') >= 0) )) { - - if (initialData.nextAuthFactors.indexOf('USERNAME_PASSWORD') >= 0) { - this.app.logMsg('[IdcsAuthnSDK] Displaying signin form'); - this.app.displayForm("USERNAME_PASSWORD","submitCreds",initialData.IDP); - } - else { - if (initialData.nextAuthFactors.indexOf('USERNAME') >= 0) { - this.app.logMsg('[IdcsAuthnSDK] Displaying user name first form'); - this.app.displayForm("USERNAME","submitCreds",initialData.IDP); - } - } - - //inject error coming back from social/callback endpoint if IDCS is not able to match - //external user to locally deactivated user.... plus other errors like eg if user cancels - //external idp authentication... etc... this error is relayed to this page via /v1/error endpoint - //which injects it into sessionStorage... - if (error != null){ - this.app.setLoginErrorMessage(JSON.parse(error)); - } - } - // we can also come back here for a social login with MFA enrollment up next - else - if (initialData.nextOp.indexOf('enrollment') >= 0) { - this.app.displayEnrollmentOptionsForm(initialData); - } - else { - this.app.logMsg('[IdcsAuthnSDK] NOT showing signin form'); + this.app.logMsg('[IdcsAuthnSDK] Initializing authentication with existing initial state from IDCS.'); + var initialData = JSON.parse(this.app.getInitialState()); + this.app.logMsg('[IdcsAuthnSDK] InitialData: ' + this.app.getInitialState()); + + if (initialData.status === 'success') { + this.app.setRequestState(initialData.requestState); + this.app.logMsg('[IdcsAuthnSDK] status==success'); + this.app.logMsg(initialData.requestState); + this.app.logMsg(initialData.nextOp); + this.app.logMsg(initialData.nextAuthFactors); + if ((initialData.requestState) && + (initialData.nextOp.indexOf('credSubmit') >= 0) && + (initialData.nextAuthFactors.indexOf('USERNAME_PASSWORD') >= 0)) { + this.app.logMsg('[IdcsAuthnSDK] Displaying signin form'); + this.app.displayForm("USERNAME_PASSWORD", "submitCreds", initialData.IDP); + //inject error coming back from social/callback endpoint if IDCS is not able to match + //external user to locally deactivated user.... plus other errors like eg if user cancels + //external idp authentication... etc... this error is relayed to this page via /v1/error endpoint + //which injects it into sessionStorage... + if (error != null) { + this.app.setLoginErrorMessage(JSON.parse(error)); + } + } + // we can also come back here for a social login with MFA enrollment up next + else + if (initialData.nextOp.indexOf('enrollment') >= 0) { + this.app.displayEnrollmentOptionsForm(initialData); + } + else { + this.app.logMsg('[IdcsAuthnSDK] NOT showing signin form'); + this.app.nextOperation(initialData); + } + } else if (initialData.status === 'pending') { + // pending means one of two things: + if (initialData.cause) { + if (initialData.cause[0].code) { + var code = initialData.cause[0].code; + + // then we're waiting for the user to say "Allow" + // so call back to the app to let them know it's OK to proceed + if (code == "AUTH-1108") { this.app.nextOperation(initialData); } - } else if (initialData.status === 'pending') { - // pending means one of two things: - if ( initialData.cause ) { - if ( initialData.cause[0].code ) { - var code = initialData.cause[0].code; - - // then we're waiting for the user to say "Allow" - // so call back to the app to let them know it's OK to proceed - if ( code == "AUTH-1108" ) { - this.app.nextOperation(initialData); - } - } - } - } else if (initialData.status === 'failed') { - if (initialData.cause) { - this.app.setLoginErrorMessage({code:initialData.cause[0].code, msg:initialData.cause[0].message}); - } - else { - this.app.setLoginErrorMessage(this.sdkErrors.error9010); - } - - if (initialData.nextOp && initialData.nextOp.indexOf("submitCreds") >= 0) { - // do nothing - this.app.logMsg('[IdcsAuthnSDK] Nothing to do here.'); - } + } + } + } else if (initialData.status === 'failed') { + if (initialData.cause) { + this.app.setLoginErrorMessage({ code: initialData.cause[0].code, msg: initialData.cause[0].message }); } else { - this.app.logMsg('[IdcsAuthnSDK] Oops, something went wrong when initiating authentication.'); - this.app.logMsg('[IdcsAuthnSDK] Response status: ' + initialData.status); + this.app.setLoginErrorMessage(this.sdkErrors.error9010); } + + if (initialData.nextOp && initialData.nextOp.indexOf("submitCreds") >= 0) { + // do nothing + this.app.logMsg('[IdcsAuthnSDK] Nothing to do here.'); + } + } + else { + this.app.logMsg('[IdcsAuthnSDK] Oops, something went wrong when initiating authentication.'); + this.app.logMsg('[IdcsAuthnSDK] Response status: ' + initialData.status); + } } }; // this.initAuthentication // Replays the authenticate call with a newly acquired access token - this.replayAuthenticate = function(data) { - let self = this; + this.replayAuthenticate = function (data) { + let self = this; - let newATUri = "/newAccessToken"; - this.app.logMsg('[IdcsAuthnSDK] Obtaining wew access token from ' + newATUri); + let newATUri = "/newAccessToken"; + this.app.logMsg('[IdcsAuthnSDK] Obtaining wew access token from ' + newATUri); - var xhr = new XMLHttpRequest(); - xhr.open("POST", newATUri); - xhr.setRequestHeader("Content-Type", "application/json"); - xhr.setRequestHeader("Authorization", "Bearer " + this.app.getAccessToken()); + var xhr = new XMLHttpRequest(); + xhr.open("POST", newATUri); + xhr.setRequestHeader("Content-Type", "application/json"); + xhr.setRequestHeader("Authorization", "Bearer " + this.app.getAccessToken()); - xhr.send(); + xhr.send(); - xhr.addEventListener("readystatechange", function () { - if (this.readyState == 4) { - self.app.logMsg('[IdcsAuthnSDK] New access token: ' + this.responseText); - self.app.setAccessToken(this.responseText); - // Once we have the new access token we can replay the authenticate request. - self.authenticate(data); - } - }); + xhr.addEventListener("readystatechange", function () { + if (this.readyState == 4) { + self.app.logMsg('[IdcsAuthnSDK] New access token: ' + this.responseText); + self.app.setAccessToken(this.responseText); + // Once we have the new access token we can replay the authenticate request. + self.authenticate(data); + } + }); } - this.authenticate = function(data) { - + this.authenticate = function (data) { this.app.logMsg('[IdcsAuthnSDK] Authenticating with: ' + this.app.mask(data)); let self = this; - try { let jsonData = JSON.parse(data); //Verifying input data if (typeof jsonData.op === 'undefined' || typeof jsonData.requestState === 'undefined') { throw "System Error"; } - var xhr = new XMLHttpRequest(); xhr.addEventListener("readystatechange", function () { @@ -180,58 +158,161 @@ function IdcsAuthnSDK(app) { // The operation is complete if (this.readyState == 4) { - self.app.logMsg ('[IdcsAuthnSDK] Authenticate response: ' + self.app.mask(this.responseText)); + self.app.logMsg('[IdcsAuthnSDK] Authenticate response: ' + self.app.mask(this.responseText)); // IDCS sends 401 status with HTML content when the access token expires. // This response MUST be distinct of every other 401 status scenario, or this check won't behave as intended // The ability to extend the access token is only applicable to USERNAME_PASSWORD authFactor. - if (jsonData.authFactor === 'USERNAME_PASSWORD' && this.status == 401 && this.getResponseHeader("Content-type") === 'text/html') { + if ((jsonData.authFactor === 'USERNAME_PASSWORD' || jsonData.authFactor === 'USERNAME') && this.status == 401 && this.getResponseHeader("Content-type") === 'text/html') { self.replayAuthenticate(data); } else { let jsonResponse = JSON.parse(this.responseText); if (jsonResponse.status === 'success') { - if (jsonResponse.authnToken) { // User is successfully authenticated! - self.app.logMsg('[IdcsAuthnSDK] Credentials successfully validated.'); - self.createSession(jsonResponse); + if (jsonData["authFactor"] !== "USERNAME") { + self.app.setRequestState(jsonResponse.requestState) + if (jsonResponse.authnToken) { // User is successfully authenticated! + self.app.logMsg('[IdcsAuthnSDK] Credentials successfully validated.'); + self.createSession(jsonResponse); + } + else { + self.app.nextOperation(jsonResponse); + } } - else { - self.app.nextOperation(jsonResponse); + else if (jsonData["authFactor"] === "USERNAME") { + var buildPayloadUserNameFirst = function (nextOp) { + return { + "username": jsonData.credentials.username, + "deviceId": jsonResponse[nextOp]["enrolledDevices"][0].deviceId + } + } + var getDisplayName = function (nextOp) { + return jsonResponse[nextOp]["enrolledDevices"][0].displayName + } + self.app.setRequestState(jsonResponse.requestState) + if (jsonData.op === "credSubmit") {//condition to display normal form + self.app.displayuserNameFirstNextAuth({ "res": jsonResponse, "email": jsonData.credentials.username }); + } + if (jsonData.op === "getBackupFactors") {//condition to use query selector and add buttons + let subdiv = document.querySelector("#signin-div"); + if (jsonResponse.nextAuthFactors.length === 1) { + subdiv.innerHTML += '
No alternative factors are configured for this user.
'; + switch (jsonResponse.nextAuthFactors[0]) { + case "EMAIL": + self.app.setSelectedDevice({ "deviceId": jsonResponse["EMAIL"]["enrolledDevices"][0].deviceId }); + self.initAuthnOtpEmail(buildPayloadUserNameFirst("EMAIL")); + subdiv.innerHTML = ""; + subdiv.whichDisplayName = getDisplayName("EMAIL"); + self.app.buildEmailOtpForm( + subdiv, + buildPayloadUserNameFirst("EMAIL") + ); + break; + case "SMS": + self.app.setSelectedDevice({ "deviceId": jsonResponse["SMS"]["enrolledDevices"][0].deviceId }); + self.initAuthnMobileNumber(buildPayloadUserNameFirst("SMS")); + subdiv.innerHTML = ""; + subdiv.whichDisplayName = getDisplayName("SMS"); + self.app.buildSmsOtpForm( + subdiv, + buildPayloadUserNameFirst("SMS") + ); + break; + case "TOTP": + self.app.setSelectedDevice({ "deviceId": jsonResponse["TOTP"]["enrolledDevices"][0].deviceId }); + subdiv.innerHTML = ""; + subdiv.whichDisplayName = getDisplayName("TOTP"); + // build TOTP Form + self.app.buildTOTPForm( + subdiv, + { + "deviceId": jsonResponse["TOTP"]["enrolledDevices"][0].deviceId, + "offlineTotp": jsonResponse["TOTP"]["enrolledDevices"][0], + } + ); + break; + default: + self.app.setLoginErrorMessage(self.sdkErrors.error9010); + } + } + //create buttons for factors available + else { + jsonResponse.nextAuthFactors.forEach(function (factor) { + if (self.app.AuthenticationFactorInfo[factor] && factor !== "USERNAME_PASSWORD") { + subdiv.innerHTML += + '
' + + '' + + '' + self.app.AuthenticationFactorInfo[factor].description + '' + + '

'; + + let btn = document.getElementById(factor); + btn.onclick = () => { + let state = JSON.parse(self.app.getInitialState()) + self.app.setSelectedDevice({ "deviceId": jsonResponse[factor]["enrolledDevices"][0].deviceId }); + switch (factor) { + case "EMAIL": + self.initAuthnOtpEmail(buildPayloadUserNameFirst("EMAIL")); + subdiv.innerHTML = ""; + subdiv.whichDisplayName = getDisplayName("EMAIL"); + self.app.buildEmailOtpForm(subdiv, buildPayloadUserNameFirst("EMAIL")); + break; + case "SMS": + self.initAuthnOtpSms(buildPayloadUserNameFirst("SMS")); + subdiv.innerHTML = ""; + subdiv.whichDisplayName = getDisplayName("SMS"); + self.app.buildSMSMobileEnrollmentForm(subdiv, buildPayloadUserNameFirst("SMS")); + self.app.buildSmsOtpForm(subdiv, buildPayloadUserNameFirst("SMS")); + break; + case "TOTP": + subdiv.innerHTML = ""; + subdiv.whichDisplayName = getDisplayName("TOTP"); + self.initEnrollOtpTotp() + self.app.buildTOTPForm(subdiv, { "deviceId": jsonResponse["TOTP"]["enrolledDevices"][0].deviceId, "offlineTotp": jsonResponse["TOTP"]["enrolledDevices"][0], }); + break; + default: + self.app.logMsg("No factors enabled for authentication"); + break; + } + } + } + }); + } + } } } else - if (jsonResponse.status === 'failed') { - if (jsonResponse.cause) { - self.app.setLoginErrorMessage({code:jsonResponse.cause[0].code, msg:jsonResponse.cause[0].message}); - } - else { - self.app.setLoginErrorMessage(self.sdkErrors.error9010); - } + if (jsonResponse.status === 'failed') { + if (jsonResponse.cause) { + self.app.setLoginErrorMessage({ code: jsonResponse.cause[0].code, msg: jsonResponse.cause[0].message }); + } + else { + self.app.setLoginErrorMessage(self.sdkErrors.error9010); + } - if (jsonResponse.nextOp && jsonResponse.nextOp.indexOf("submitCreds") >= 0) { - // do nothing - self.app.logMsg("Nothing to do here."); + if (jsonResponse.nextOp && jsonResponse.nextOp.indexOf("submitCreds") >= 0) { + // do nothing + self.app.logMsg("Nothing to do here."); + } } - } - else - if (jsonResponse.status === 'pending') { - // pending means one of two things: - if ( jsonResponse.cause ) { - if ( jsonResponse.cause[0].code ) { - let code = jsonResponse.cause[0].code; - - // then we're waiting for the user to say "Allow" - // so call back to the app to let them know it's OK to proceed - if ( code == "AUTH-1108" ) { - self.app.nextOperation(jsonResponse); + else + if (jsonResponse.status === 'pending') { + // pending means one of two things: + if (jsonResponse.cause) { + if (jsonResponse.cause[0].code) { + let code = jsonResponse.cause[0].code; + + // then we're waiting for the user to say "Allow" + // so call back to the app to let them know it's OK to proceed + if (code == "AUTH-1108") { + self.app.nextOperation(jsonResponse); + } + } } } - } - } - else { - self.app.setLoginErrorMessage(self.sdkErrors.error9011); - } + else { + self.app.setLoginErrorMessage(self.sdkErrors.error9011); + } } } }); @@ -241,55 +322,56 @@ function IdcsAuthnSDK(app) { xhr.setRequestHeader("Accept", "application/json"); this.app.logMsg('[IdcsAuthnSDK] Using access token: ' + this.app.getAccessToken()); xhr.setRequestHeader("Authorization", "Bearer " + this.app.getAccessToken()); - xhr.send(data); } - catch(e) { //this should never happen + catch (e) { //this should never happen self.app.logMsg(e); self.app.setLoginErrorMessage(self.sdkErrors.error9999); } } //this.authenticate - this.postCreds = function(credentials) { - - this.app.logMsg('[IdcsAuthnSDK] Posting credentials (u/p)...'); - + this.getEnrollmentFactorsuserNameFirst = function (data) { var data = JSON.stringify({ - "op": "credSubmit", - "authFactor": "USERNAME_PASSWORD", - "credentials": credentials, + "op": "getBackupFactors", + "authFactor": "USERNAME", + "credentials": data.credentials, "requestState": this.app.getRequestState() }); - this.authenticate(data); - }; // this.postCreds - - // Post just the user name to Idcs - // This is for the "user name first" feature - this.postUserName = function(credentials) { - - this.app.logMsg('[IdcsAuthnSDK] Posting user name ...'); + } + this.postUserNameFirst = function (data) { var data = JSON.stringify({ "op": "credSubmit", "authFactor": "USERNAME", - "credentials": credentials, - "requestState": this.app.getRequestState() + "credentials": data.credentials, + "requestState": this.app.getRequestState(), }); - + sessionStorage.setItem("userNameFirst", true) this.authenticate(data); - }; // this.postUserName + }//this.postUserNameFirst - this.postEmailOtp = function(credentials) { + this.postCreds = function (data) { + this.app.logMsg('[IdcsAuthnSDK] Posting credentials (u/p)...'); + var data = JSON.stringify({ + "op": "credSubmit", + "authFactor": "USERNAME_PASSWORD", + "credentials": data.credentials, + "requestState": this.app.getRequestState(), + }); + + this.authenticate(data); + }; // this.postCreds + this.postEmailOtp = function (credentials) { this.app.logMsg('[IdcsAuthnSDK] Posting Email OTP...'); var data = JSON.stringify({ "op": "credSubmit", "authFactor": "EMAIL", "credentials": credentials, - "trustedDevice": JSON.parse(this.app.getTrustedDeviceOption()), // Value here MUST be Boolean!! + "trustedDevice": JSON.parse(this.app.getTrustedDeviceOption()) ? true : false, // Value here MUST be Boolean!! "trustedDeviceDisplayName": this.clientFingerprint.browser + ' on ' + this.clientFingerprint.OS + ' ' + this.clientFingerprint.OSVersion, "requestState": this.app.getRequestState() }); @@ -297,7 +379,7 @@ function IdcsAuthnSDK(app) { this.authenticate(data); }; // this.postEmailOtp - this.postSmsOtp = function(credentials) { + this.postSmsOtp = function (credentials) { this.app.logMsg('[IdcsAuthnSDK] Posting SMS OTP...'); @@ -305,7 +387,7 @@ function IdcsAuthnSDK(app) { "op": "credSubmit", "authFactor": "SMS", "credentials": credentials, - "trustedDevice": JSON.parse(this.app.getTrustedDeviceOption()), // Value here MUST be Boolean!! + "trustedDevice": JSON.parse(this.app.getTrustedDeviceOption()) ? true : false, // Value here MUST be Boolean!! "trustedDeviceDisplayName": this.clientFingerprint.browser + ' on ' + this.clientFingerprint.OS + ' ' + this.clientFingerprint.OSVersion, "requestState": this.app.getRequestState() }); @@ -313,7 +395,7 @@ function IdcsAuthnSDK(app) { this.authenticate(data); }; // this.postSmsOtp - this.enrollSecurityQuestions = function(credentials) { + this.enrollSecurityQuestions = function (credentials) { this.app.logMsg('[IdcsAuthnSDK] Enrolling Security Questions and Answers...'); @@ -323,17 +405,17 @@ function IdcsAuthnSDK(app) { "requestState": this.app.getRequestState() }); this.authenticate(data); - } + } - this.postSecQuestions = function(credentials) { + this.postSecQuestions = function (credentials) { this.app.logMsg('[IdcsAuthnSDK] Posting Security Questions...'); var questions = []; const inputElements = document.getElementsByTagName("INPUT"); - for (i=0; i < inputElements.length; i++) { + for (i = 0; i < inputElements.length; i++) { if (inputElements[i].type === "password") { - questions[i] = {'questionId':inputElements[i].name, 'answer':inputElements[i].value}; + questions[i] = { 'questionId': inputElements[i].name, 'answer': inputElements[i].value }; } } @@ -341,7 +423,7 @@ function IdcsAuthnSDK(app) { "op": "credSubmit", "authFactor": "SECURITY_QUESTIONS", "credentials": credentials, - "trustedDevice": JSON.parse(this.app.getTrustedDeviceOption()), // Value here MUST be Boolean!! + "trustedDevice": JSON.parse(this.app.getTrustedDeviceOption()) ? true : false, // Value here MUST be Boolean!! "trustedDeviceDisplayName": this.clientFingerprint.browser + ' on ' + this.clientFingerprint.OS + ' ' + this.clientFingerprint.OSVersion, "requestState": this.app.getRequestState() }); @@ -350,7 +432,22 @@ function IdcsAuthnSDK(app) { }; // this.postSecQuestions - this.initEnrollOtpEmail = function() { + this.initEnrollFido = function () { + + this.app.logMsg('[IdcsAuthnSDK] Initiating Fido enrollment...'); + + var data = JSON.stringify({ + "op": "enrollment", + "authFactor": "FIDO_AUTHENTICATOR", + "origin": window.location.origin, + "requestState": this.app.getRequestState() + }); + + this.authenticate(data); + + }; // this.initEnrollFido + + this.initEnrollOtpEmail = function () { this.app.logMsg('[IdcsAuthnSDK] Initiating OTP over email enrollment...'); @@ -364,7 +461,7 @@ function IdcsAuthnSDK(app) { }; // this.initEnrollOtpEmail - this.enrollOtpEmail = function(credentials) { + this.enrollOtpEmail = function (credentials) { this.app.logMsg('[IdcsAuthnSDK] Enrolling OTP over email...'); @@ -377,7 +474,7 @@ function IdcsAuthnSDK(app) { this.authenticate(data); }; // this.enrollOtpEmail - this.initAuthnOtpEmail = function(credentials) { + this.initAuthnOtpEmail = function (credentials) { this.app.logMsg('[IdcsAuthnSDK] Initiating OTP over email authentication...'); @@ -387,12 +484,12 @@ function IdcsAuthnSDK(app) { "credentials": credentials, "requestState": this.app.getRequestState() }); - this.authenticate(data); + }; // this.initAuthnOtpEmail - this.initEnrollMobileNumber = function(credentials) { + this.initEnrollMobileNumber = function (credentials) { this.app.logMsg('Initiating OTP over SMS: Mobile enrollment...'); var data = JSON.stringify({ @@ -404,8 +501,7 @@ function IdcsAuthnSDK(app) { this.authenticate(data); }; // this.initEnrollMobileNumber - - this.initAuthnMobileNumber = function(credentials) { + this.initAuthnMobileNumber = function (credentials) { this.app.logMsg('Initiating OTP over SMS: Authenticating...'); var data = JSON.stringify({ @@ -418,7 +514,7 @@ function IdcsAuthnSDK(app) { }; // this.initAuthnMobileNumber - this.resendOtp = function() { + this.resendOtp = function () { this.app.logMsg('Resending OTP ...'); var data = JSON.stringify({ "op": "resendCode", @@ -427,7 +523,7 @@ function IdcsAuthnSDK(app) { this.authenticate(data); }; // this.initEnrollMobileNumber - this.enrollMobileNumber = function() { + this.enrollMobileNumber = function () { this.app.logMsg('[IdcsAuthnSDK] Send OTP over SMS...'); var data = JSON.stringify({ "op": "credSubmit", @@ -439,7 +535,7 @@ function IdcsAuthnSDK(app) { this.authenticate(data); }; // this.enrollMobileNumber - this.initEnrollSecurityQuestions = function() { + this.initEnrollSecurityQuestions = function () { this.app.logMsg('[IdcsAuthnSDK] Enrolling Security Questions...'); var data = JSON.stringify({ "op": "enrollment", @@ -450,9 +546,8 @@ function IdcsAuthnSDK(app) { this.authenticate(data); }; // this.initEnrollSecurityQuestions - this.createToken = function() { + this.createToken = function () { this.app.logMsg('[IdcsAuthnSDK] Creating token...'); - var data = JSON.stringify({ "op": "createToken", "requestState": this.app.getRequestState() @@ -461,270 +556,100 @@ function IdcsAuthnSDK(app) { this.authenticate(data); }; // this.initEnrollSecurityQuestions - this.initEnrollOtpTotp = function() { - var data = JSON.stringify({ - "op":"enrollment", - "authFactor":"TOTP", - "credentials":{ - "offlineTotp":true - }, - "requestState":this.app.getRequestState() + this.initEnrollOtpTotp = function () { + var data = JSON.stringify({ + "op": "enrollment", + "authFactor": "TOTP", + "credentials": { + "offlineTotp": true + }, + "requestState": this.app.getRequestState() }); this.authenticate(data); } // this.initEnrollOtpTotp - this.submitTOTP = function(credentials, includeAuthnFactor) { + this.submitTOTP = function (credentials, includeAuthnFactor) { + var data = JSON.stringify({ + "op": "credSubmit", + "credentials": credentials, + "trustedDevice": JSON.parse(this.app.getTrustedDeviceOption()) ? true : false, // Value here MUST be Boolean!! + "trustedDeviceDisplayName": this.clientFingerprint.browser + ' on ' + this.clientFingerprint.OS + ' ' + this.clientFingerprint.OSVersion, + "requestState": this.app.getRequestState() + }); - var data = JSON.stringify({ - "op":"credSubmit", + if (typeof includeAuthnFactor !== 'undefined') { + data = JSON.stringify({ + "op": "credSubmit", + "authFactor": "TOTP", "credentials": credentials, - "trustedDevice": JSON.parse(this.app.getTrustedDeviceOption()), // Value here MUST be Boolean!! + "trustedDevice": JSON.parse(this.app.getTrustedDeviceOption()) ? true : false, // Value here MUST be Boolean!! "trustedDeviceDisplayName": this.clientFingerprint.browser + ' on ' + this.clientFingerprint.OS + ' ' + this.clientFingerprint.OSVersion, - "requestState":this.app.getRequestState() + "requestState": this.app.getRequestState() }); - - if (typeof includeAuthnFactor !== 'undefined') { - data = JSON.stringify({ - "op":"credSubmit", - "authFactor": "TOTP", - "credentials": credentials, - "trustedDevice": JSON.parse(this.app.getTrustedDeviceOption()), // Value here MUST be Boolean!! - "trustedDeviceDisplayName": this.clientFingerprint.browser + ' on ' + this.clientFingerprint.OS + ' ' + this.clientFingerprint.OSVersion, - "requestState":this.app.getRequestState() - }); - } - this.authenticate(data); + } + this.authenticate(data); } - this.submitBypasscode = function(credentials) { + this.submitBypasscode = function (credentials) { var data = JSON.stringify({ - "op":"credSubmit", - "authFactor":"BYPASSCODE", - "credentials":credentials, - "requestState":this.app.getRequestState() + "op": "credSubmit", + "authFactor": "BYPASSCODE", + "credentials": credentials, + "requestState": this.app.getRequestState() }); this.authenticate(data); } - this.submitAcceptTerms = function() { + this.submitAcceptTerms = function () { var data = JSON.stringify({ - "op": "acceptTOU", - "credentials": { - "consent": true - }, - "requestState": this.app.getRequestState() + "op": "acceptTOU", + "credentials": { + "consent": true + }, + "requestState": this.app.getRequestState() }); this.authenticate(data); } - this.forgotPassword = function(payload) { + this.forgotPassword = function (username) { const self = this; - this.app.logMsg("[IdcsAuthnSDK] Username is :" + payload.username); - - var xhr = new XMLHttpRequest(); - xhr.addEventListener("readystatechange", function () { - if (this.readyState === 4) { - self.app.logMsg("[IdcsAuthnSDK] PasswordResetRequestor Response: "+self.app.mask(this.responseText)); - const jsonResponse = JSON.parse(this.responseText); - - if (jsonResponse.hasOwnProperty('userName')&& jsonResponse.notificationType === 'email') { - // Uncomment the line below if you want the OOTB Password Reset form - //self.app.displayForgotPassWordSuccess(jsonResponse, payload.username); - - // Uncomment the line below to use a custom e-mail form to enter a token. - // The ${userToken} must be set in the E-mail template for this flow to work. - self.app.displayForgotPassWordEmailForm(jsonResponse, payload.username); - } - if (jsonResponse.hasOwnProperty('userName')&& jsonResponse.notificationType === 'sms') { - self.app.displayForgotPassWordSmsForm(jsonResponse, payload.username); - } - if (jsonResponse.hasOwnProperty('userName')&& jsonResponse.notificationType === 'secquestions') { - self.app.displayForgotPassWordSecquestionForm(jsonResponse, payload.username); - } - } - }); - - xhr.open("POST", app.baseUri + "/admin/v1/MePasswordResetRequestor"); - xhr.setRequestHeader("Content-Type", "application/json"); - xhr.setRequestHeader("Authorization", "Bearer " + this.app.getAccessToken()); - if (payload.method === 'email') { - var emailData = JSON.stringify({ - "userName": payload.username, - "notificationType": "email", - "notificationEmailAddress" : payload.email, - "schemas": [ - "urn:ietf:params:scim:schemas:oracle:idcs:MePasswordResetRequestor" - ] - }); - xhr.send(emailData); - } - else if (payload.method === 'sms') { - var smsData = JSON.stringify({ - "userName": payload.username, - "notificationType": "sms", - "schemas": [ - "urn:ietf:params:scim:schemas:oracle:idcs:MePasswordResetRequestor" - ] - }); - xhr.send(smsData); - } - else if (payload.method === 'secquestions') { - var secData = JSON.stringify({ - "userName": payload.username, - "notificationType": "secquestions", - "schemas": [ - "urn:ietf:params:scim:schemas:oracle:idcs:MePasswordResetRequestor" - ] - }); - xhr.send(secData); - } - - }; //this.forgotPassword - - this.getPasswordMethod = function(username) { - const self = this; - this.app.logMsg("[IdcsAuthnSDK] Username is :" + username); + this.app.logMsg("[IdcsAuthnSDK] Username is :" + this.app.mask(username)); var data = JSON.stringify({ "userName": username, + "notificationType": "email", "schemas": [ - "urn:ietf:params:scim:schemas:oracle:idcs:MePasswordRecoveryOptionRetriever" - ] - }); - - var xhr = new XMLHttpRequest(); - xhr.addEventListener("readystatechange", function () { - if (this.readyState === 4) { - self.app.logMsg("[IdcsAuthnSDK] PasswordRecoveryOptionRetriever Response: "+self.app.mask(this.responseText)); - const jsonResponse = JSON.parse(this.responseText); - // Check what options the user can use for password flow (email, sms, secquestion) - // If there is only one option it must be email, check anyways. - if (jsonResponse.hasOwnProperty('options') ) - { - if (jsonResponse.options.length == 1 ) - { - if (jsonResponse.options[0].type === 'email') - { - self.forgotPassword({"username": username, "email": jsonResponse.options[0].value, "method": "email"}); - } - else if (jsonResponse.options[0].type === 'sms') - { - self.forgotPassword({"username": username, "method": "sms"}); - } - else if (jsonResponse.options[0].type === 'secquestions') - { - self.forgotPassword({"username": username, "method": "secquestions"}); - } - else - { - // Error - No options found. This "should" never happen. - self.app.setLoginErrorMessage(self.sdkErrors.error9010); - } - } - else - { - self.app.displayForgotPassWordMethodForm(jsonResponse, username); - } - } - else // Error - No options found. This "should" never happen. - { - self.app.setLoginErrorMessage(self.sdkErrors.error9010); - } - } - }); - - xhr.open("POST", app.baseUri + "/admin/v1/MePasswordRecoveryOptionRetriever"); - xhr.setRequestHeader("Content-Type", "application/json"); - xhr.setRequestHeader("Authorization", "Bearer " + this.app.getAccessToken()); - xhr.send(data); - }; //this.getPasswordMethod + "urn:ietf:params:scim:schemas:oracle:idcs:MePasswordResetRequestor" + ] + }); - this.processPasswordMethod = function(payload) { - const self = this; var xhr = new XMLHttpRequest(); xhr.addEventListener("readystatechange", function () { - if (this.readyState === 4) { - self.app.logMsg("[IdcsAuthnSDK] PasswordRecoveryFactorValidator Response: "+self.app.mask(this.responseText)); - const jsonResponse = JSON.parse(this.responseText); + if (this.readyState === 4) { + self.app.logMsg("[IdcsAuthnSDK] PasswordResetRequestor Response: " + self.app.mask(this.responseText)); + const jsonResponse = JSON.parse(this.responseText); - if (jsonResponse.hasOwnProperty('token')) { - self.app.displayResetPassWordForm(jsonResponse.token); - } - else { - // Checking for Status 400 - No Token - if (jsonResponse.hasOwnProperty('status') && jsonResponse.status === '400') { - var error = jsonResponse['urn:ietf:params:scim:api:oracle:idcs:extension:messages:Error']; - self.app.logMsg(error.messageId); - if (error.messageId === "error.identity.passwordmgmt.invalidToken") { - self.app.setLoginErrorMessage(self.sdkErrors.error9022); - } - else if (error.messageId === "error.ssocommon.auth.invalidPasscode" || - error.messageId === "error.identity.accrec.invalidOTP") { - self.app.setLoginErrorMessage(self.sdkErrors.error9023); - } - else if (error.messageId === "error.identity.passwordmgmt.invalidSecurityQuestionAnswer") { - self.app.setLoginErrorMessage(self.sdkErrors.error9024); - } - else // Unrecognized error message - { - self.app.setLoginErrorMessage(self.sdkErrors.error9012); - } - } - } - } - }); + if (jsonResponse.hasOwnProperty('userName') && jsonResponse.notificationType === 'email') { + self.app.displayForgotPassWordSuccess(jsonResponse); + } + } + }); - xhr.open("POST", app.baseUri + "/admin/v1/MePasswordRecoveryFactorValidator"); + xhr.open("POST", app.baseUri + "/admin/v1/MePasswordResetRequestor"); xhr.setRequestHeader("Content-Type", "application/json"); xhr.setRequestHeader("Authorization", "Bearer " + this.app.getAccessToken()); + xhr.send(data); + }; //this.forgotPassword - if (payload.method === 'email') { - var emailData = JSON.stringify({ - "schemas": [ - "urn:ietf:params:scim:schemas:oracle:idcs:MePasswordRecoveryFactorValidator" - ], - "type": "email", - "emailToken": decodeURIComponent(payload.emailToken) - }); - xhr.send(emailData); - } - else if (payload.method === 'sms') { - var smsData = JSON.stringify({ - "schemas": [ - "urn:ietf:params:scim:schemas:oracle:idcs:MePasswordRecoveryFactorValidator" - ], - "type": "sms", - "userName": payload.username, - "deviceId": payload.deviceId, - "requestId": payload.requestId, - "otpCode": payload.smsCode - }); - xhr.send(smsData); - } - else if (payload.method === 'secquestions') { - var secData = JSON.stringify({ - "schemas": [ - "urn:ietf:params:scim:schemas:oracle:idcs:MePasswordRecoveryFactorValidator" - ], - "userName": payload.username, - "type": "secquestions", - "securityQuestions": [{ - "questionId": payload.questionId, - "securityAnswer": payload.secAnswer - }] }); - xhr.send(secData); - } - }; //this.processPasswordMethod - - this.initEnrollPush = function() { + this.initEnrollPush = function () { var data = JSON.stringify({ - "op":"enrollment", - "authFactor":"PUSH", - "requestState":this.app.getRequestState() + "op": "enrollment", + "authFactor": "PUSH", + "requestState": this.app.getRequestState() }); this.authenticate(data); } - this.initAuthnPush = function(credentials) { + this.initAuthnPush = function (credentials) { this.app.logMsg('[IdcsAuthnSDK] Initiating Push...'); var data = JSON.stringify({ @@ -736,81 +661,94 @@ function IdcsAuthnSDK(app) { this.authenticate(data); }; // this.initAuthnPush - this.submitPushPoll = function(credentials) { + this.initAuthnFido = function (credentials) { + this.app.logMsg('[IdcsAuthnSDK] Initiating Fido...'); + var data = JSON.stringify({ - "op":"credSubmit", + "op": "credSubmit", + "authFactor": "FIDO_AUTHENTICATOR", + "credentials": credentials, + "requestState": this.app.getRequestState() + }); + this.authenticate(data); + }; // this.initAuthnFido + + this.submitPushPoll = function (credentials) { + var data = JSON.stringify({ + "op": "credSubmit", "authFactor": "PUSH", "credentials": credentials, - "trustedDevice": JSON.parse(this.app.getTrustedDeviceOption()), // Value here MUST be Boolean!! + "trustedDevice": JSON.parse(this.app.getTrustedDeviceOption()) ? true : false, // Value here MUST be Boolean!! "trustedDeviceDisplayName": this.clientFingerprint.browser + ' on ' + this.clientFingerprint.OS + ' ' + this.clientFingerprint.OSVersion, - "requestState":this.app.getRequestState() + "requestState": this.app.getRequestState() }); this.authenticate(data); } - this.submitBackupFactors = function() { + this.submitBackupFactors = function () { var data = JSON.stringify({ - "op":"getBackupFactors", - "requestState":this.app.getRequestState() + "op": "getBackupFactors", + "origin": window.location.origin, + "requestState": this.app.getRequestState() }); this.authenticate(data); } - this.getEnrollmentFactors = function() { + this.getEnrollmentFactors = function () { var data = JSON.stringify({ - "op":"enrollment", - "requestState":this.app.getRequestState() + "op": "enrollment", + "requestState": this.app.getRequestState() }); this.authenticate(data); } - this.validateToken = function(token, callback) { - this.app.logMsg('[IdcsAuthnSDK] In Validate Token'); - - var data = JSON.stringify({ - "token": token, - "schemas": [ - "urn:ietf:params:scim:schemas:oracle:idcs:UserTokenValidator" - ] - }); - - var xhr = new XMLHttpRequest(); - const self = this; - - xhr.addEventListener("readystatechange", function () { - if (this.readyState === 4) { - self.app.logMsg('[IdcsAuthnSDK] Validate User Token:' + self.app.mask(this.responseText)); - const jsonResponse = JSON.parse(this.responseText); - callback(jsonResponse); - } - }); - - xhr.open("POST", app.baseUri + "/admin/v1/UserTokenValidator"); - xhr.setRequestHeader("Content-Type", "application/json"); - xhr.setRequestHeader("Authorization", "Bearer " + this.app.getAccessToken()); - xhr.send(data); - }; // this.validateToken - - this.validateUserToken = function(token) { + this.validateToken = function (token, callback) { + this.app.logMsg('[IdcsAuthnSDK] In Validate Token'); + + var data = JSON.stringify({ + "token": token, + "schemas": [ + "urn:ietf:params:scim:schemas:oracle:idcs:UserTokenValidator" + ] + }); + + var xhr = new XMLHttpRequest(); + const self = this; + + xhr.addEventListener("readystatechange", function () { + if (this.readyState === 4) { + self.app.logMsg('[IdcsAuthnSDK] Validate User Token:' + self.app.mask(this.responseText)); + const jsonResponse = JSON.parse(this.responseText); + callback(jsonResponse); + } + }); + + xhr.open("POST", app.baseUri + "/admin/v1/UserTokenValidator"); + xhr.setRequestHeader("Content-Type", "application/json"); + xhr.setRequestHeader("Authorization", "Bearer " + this.app.getAccessToken()); + xhr.send(data); + }; // this.validateToken + + this.validateUserToken = function (token) { this.app.logMsg('[IdcsAuthnSDK] In ValidateUserToken...'); this.app.logMsg('[IdcsAuthnSDK] Token: ' + this.app.mask(token)); const self = this; - this.validateToken(token , function(returnVal1) { + this.validateToken(token, function (returnVal1) { var id = returnVal1.userId; if (returnVal1.hasOwnProperty('userId')) { - self.app.logMsg('[IdcsAuthnSDK] UserTokenValidator Repsonse , Success for Id -->' + self.app.mask(returnVal1.userId)); - self.app.displayResetPassWordForm(token); + self.app.logMsg('[IdcsAuthnSDK] UserTokenValidator Repsonse , Success for Id -->' + self.app.mask(returnVal1.userId)); + self.app.displayResetPassWordForm(token); } else { - self.app.setLoginErrorMessage(self.sdkErrors.error9020); + self.app.setLoginErrorMessage(self.sdkErrors.error9020); } }); }; // this.validateUserToken - this.evaluatePasswordPolicies = function(resetpaswddata ,callback) { + this.evaluatePasswordPolicies = function (resetpaswddata, callback) { const self = this; var data = JSON.stringify({ "userName": "dummydata@example.com", @@ -839,7 +777,7 @@ function IdcsAuthnSDK(app) { }; //this.evaluatePasswordPolicies - this.resetUserPassword = function(token,resetpaswddata,callback){ + this.resetUserPassword = function (token, resetpaswddata, callback) { const self = this; var data = JSON.stringify({ "schemas": [ @@ -855,7 +793,7 @@ function IdcsAuthnSDK(app) { xhr.addEventListener("readystatechange", function () { if (this.readyState === 4) { const jsonResponse = JSON.parse(this.responseText); - self.app.logMsg('[IdcsAuthnSDK] Reset user Password Response: ' + self.app.mask(this.responseText)); + self.app.logMsg('[IdcsAuthnSDK] Reset user Password Response: ' + self.app.mask(this.responseText)); callback(jsonResponse); } }); @@ -866,11 +804,11 @@ function IdcsAuthnSDK(app) { xhr.send(data); }; //this.resetUserPassword - this.resetPassword = function(credentials){ + this.resetPassword = function (credentials) { const self = this; self.app.logMsg('[IdcsAuthnSDK] Resetting password...'); - this.evaluatePasswordPolicies(credentials.password ,function(returnValue1) { + this.evaluatePasswordPolicies(credentials.password, function (returnValue1) { if (returnValue1.hasOwnProperty('failedPasswordPolicyRules')) { self.sdkErrors.error9021.details = []; @@ -887,29 +825,29 @@ function IdcsAuthnSDK(app) { else { self.app.logMsg('[IdcsAuthnSDK] Password policies came back fine. Proceeding with reset password...'); - self.resetUserPassword(credentials.token, credentials.password,function(returnValue2) { - if(returnValue2.hasOwnProperty('id')) { + self.resetUserPassword(credentials.token, credentials.password, function (returnValue2) { + if (returnValue2.hasOwnProperty('id')) { self.app.logMsg('[IdcsAuthnSDK] ResetPassword was successful.'); self.app.displayResetPassWordSuccess(); } else { self.app.logMsg(JSON.stringify(returnValue2)); self.app.logMsg('[IdcsAuthnSDK] ResetPassword failed: ' + returnValue2.detail); - self.app.setLoginErrorMessage({code:'', msg:returnValue2.detail}); + self.app.setLoginErrorMessage({ code: '', msg: returnValue2.detail }); } }); } }); }; // this.resetPassword - this.createSession = function(payload) { + this.createSession = function (payload) { - var addParam = function(myform, paramName, paramValue) { - param = document.createElement("input"); - param.value = paramValue; - param.name = paramName; - param.hidden=true; - myform.appendChild(param); + var addParam = function (myform, paramName, paramValue) { + param = document.createElement("input"); + param.value = paramValue; + param.name = paramName; + param.hidden = true; + myform.appendChild(param); }; this.app.logMsg('[IdcsAuthnSDK] Creating session with authnToken:' + this.app.mask(payload)); @@ -923,6 +861,11 @@ function IdcsAuthnSDK(app) { this.app.logMsg('[IdcsAuthnSDK] trustToken added.'); addParam(myform, "trustToken", payload.trustToken); } + if (payload.kmsiToken) { + this.app.logMsg('[IdcsAuthnSDK] kmsiToken added.'); + addParam(myform, "kmsiToken", payload.kmsiToken); + console.log("KMSI") + } document.body.appendChild(myform); //adding this to flush session after successful login... sessionStorage.clear(); @@ -955,40 +898,40 @@ function IdcsAuthnSDK(app) { this.clientFingerprint = { clients: [ - {searchIn:navigator.userAgent, forString:"Edge", identity:"Microsoft Edge"}, - {searchIn:navigator.userAgent, forString:"OPR", identity:"Opera"}, - {searchIn:navigator.userAgent, forString:"Chrome", identity:"Chrome"}, - {searchIn:navigator.vendor, forString:"Apple", identity:"Safari"}, - {searchIn:navigator.userAgent, forString:"Firefox", identity:"Firefox"}, - {searchIn:navigator.userAgent, forString:"Netscape", identity:"Netscape"}, - {searchIn:navigator.userAgent, forString:".NET", identity:"Internet Explorer"}, - {searchIn:navigator.userAgent, forString:"Gecko", identity:"Mozilla"}, - {searchIn:navigator.userAgent, forString:"Mozilla", identity:"Netscape"} + { searchIn: navigator.userAgent, forString: "Edge", identity: "Microsoft Edge" }, + { searchIn: navigator.userAgent, forString: "OPR", identity: "Opera" }, + { searchIn: navigator.userAgent, forString: "Chrome", identity: "Chrome" }, + { searchIn: navigator.vendor, forString: "Apple", identity: "Safari" }, + { searchIn: navigator.userAgent, forString: "Firefox", identity: "Firefox" }, + { searchIn: navigator.userAgent, forString: "Netscape", identity: "Netscape" }, + { searchIn: navigator.userAgent, forString: ".NET", identity: "Internet Explorer" }, + { searchIn: navigator.userAgent, forString: "Gecko", identity: "Mozilla" }, + { searchIn: navigator.userAgent, forString: "Mozilla", identity: "Netscape" } ], operatingSystems: [ - {searchIn:navigator.platform, forString:"Win", identity: "Windows"}, - {searchIn:navigator.userAgent, forString:"iPhone", identity: "iPhone OS"}, - {searchIn:navigator.platform, forString:"Mac", identity: "Mac OS"}, - {searchIn:navigator.userAgent, forString:"Android", identity: "Android"}, - {searchIn:navigator.platform, forString:"Linux", identity: "Linux"} + { searchIn: navigator.platform, forString: "Win", identity: "Windows" }, + { searchIn: navigator.userAgent, forString: "iPhone", identity: "iPhone OS" }, + { searchIn: navigator.platform, forString: "Mac", identity: "Mac OS" }, + { searchIn: navigator.userAgent, forString: "Android", identity: "Android" }, + { searchIn: navigator.platform, forString: "Linux", identity: "Linux" } ], operatingSystemsVersions: [ - {searchString: "Windows NT",delimiter: "."}, - {searchString: "iPhone OS"}, - {searchString: "Android"}, - {searchString: "Mac OS",delimiter: " "}, - {searchString: "Linux"} + { searchString: "Windows NT", delimiter: "." }, + { searchString: "iPhone OS" }, + { searchString: "Android" }, + { searchString: "Mac OS", delimiter: " " }, + { searchString: "Linux" } ], - init: function() { + init: function () { this.browser = this.searchString(this.clients) || "Unknown browser"; this.OS = this.searchString(this.operatingSystems) || "Unknown OS"; this.OSVersion = this.searchOSVersion(navigator.userAgent) || ""; }, - searchString: function(data) { + searchString: function (data) { for (var i = 0; i < data.length; i++) { var dataString = data[i].searchIn; if (dataString) { @@ -999,14 +942,14 @@ function IdcsAuthnSDK(app) { } }, - searchOSVersion: function(dataString) { + searchOSVersion: function (dataString) { var stringIndex, delimIndex, osVersionString; - for (var i=0; i < this.operatingSystemsVersions.length; i++) { + for (var i = 0; i < this.operatingSystemsVersions.length; i++) { stringIndex = dataString.indexOf(this.operatingSystemsVersions[i].searchString); if (stringIndex != -1) { if (typeof this.operatingSystemsVersions[i].delimiter !== 'undefined') { // Returns no version if there's no delimiter. Linux/Android case. osVersionString = dataString.substring(stringIndex + this.operatingSystemsVersions[i].searchString.length + 1); - return osVersionString.substring(0,osVersionString.indexOf(this.operatingSystemsVersions[i].delimiter)); + return osVersionString.substring(0, osVersionString.indexOf(this.operatingSystemsVersions[i].delimiter)); } else { return; diff --git a/idcs-authn-api-signin-app/public/js/loginApp.js b/idcs-authn-api-signin-app/public/js/loginApp.js index db97263..7a23655 100644 --- a/idcs-authn-api-signin-app/public/js/loginApp.js +++ b/idcs-authn-api-signin-app/public/js/loginApp.js @@ -13,49 +13,49 @@ function LoginApp() { -------------------------------------------- HELPER METHODS ---------------------------------------------------- ---------------------------------------------------------------------------------------------------------------- */ -// Removes the spinner from DOM tree. -// Used, for instance, when an error comes and we have to stop spinning. -this.removeSpinner = function() { - let spinner = document.querySelector("div.loader"); - if (spinner != null) { - spinner.parentNode.removeChild(spinner); + // Removes the spinner from DOM tree. + // Used, for instance, when an error comes and we have to stop spinning. + this.removeSpinner = function () { + let spinner = document.querySelector("div.loader"); + if (spinner != null) { + spinner.parentNode.removeChild(spinner); + } + } + + // Removes button and show spinner. + // Mainly used on form submission, where submit button is swapped by spinner. + this.removeBtnAndShowSpinner = function (btn) { + btn.style.display = 'none'; + var spinnerDiv = document.createElement('div'); + spinnerDiv.classList.add('loader'); + spinnerDiv.data_res = 'loading-msg'; + spinnerDiv.innerHTML = 'Loading...'; + // Showing the spinner after btn + btn.parentNode.insertBefore(spinnerDiv, btn.nextSibling); } -} - -// Removes button and show spinner. -// Mainly used on form submission, where submit button is swapped by spinner. -this.removeBtnAndShowSpinner = function(btn) { - btn.style.display = 'none'; - var spinnerDiv = document.createElement('div'); - spinnerDiv.classList.add('loader'); - spinnerDiv.data_res = 'loading-msg'; - spinnerDiv.innerHTML = 'Loading...'; - // Showing the spinner after btn - btn.parentNode.insertBefore(spinnerDiv, btn.nextSibling); -} // Keeps polling for push notifications at the specified interval. - this.submitPushPoll = function(timeInMillis) { + this.submitPushPoll = function (timeInMillis) { const self = this; this.logMsg('submitPushPoll'); - this.pushPollInterval = setInterval(function() { - self.logMsg('timer'); - let signinDiv = document.getElementById("signin-div"); - if ( ( signinDiv ) && - ( signinDiv.whichFactorForm === "PUSH" ) ) { - self.logMsg( signinDiv.whichFactorForm + " is active"); - - // Issue #1 fix. Change introduced to channel the call to submitPushPoll through buildPayload for adding credentials - const payload = self.buildPayload("submitPushPoll", signinDiv); - self.logMsg("Invoking submitPushPoll with payload " + self.mask(payload)); - self.sdk.submitPushPoll(payload); - // End of fix. - } + this.pushPollInterval = setInterval(function () { + self.logMsg('timer'); + let signinDiv = document.getElementById("signin-div"); + if ((signinDiv) && + (signinDiv.whichFactorForm === "PUSH")) { + self.logMsg(signinDiv.whichFactorForm + " is active"); + + // Issue #1 fix. Change introduced to channel the call to submitPushPoll through buildPayload for adding credentials + const payload = self.buildPayload("submitPushPoll", signinDiv); + self.logMsg("Invoking submitPushPoll with payload " + self.mask(payload)); + self.sdk.submitPushPoll(payload); + // End of fix. + } }, timeInMillis); } // Stops polling for push notifications - this.stopPushPoll = function() { + this.stopPushPoll = function () { if (this.pushPollInterval != null) { this.removeSpinner(); this.logMsg('Stopping push poll...'); @@ -65,20 +65,20 @@ this.removeBtnAndShowSpinner = function(btn) { // Displays an HTML snippet allowing users to request a new OTP code. // Used in EMAIL and SMS. - this.showResendCodeOption = function(formDiv, obj, timeInMilis) { + this.showResendCodeOption = function (formDiv, obj, timeInMilis) { - if (formDiv && (formDiv.whichFactorForm === "EMAIL" || formDiv.whichFactorForm === "SMS" ) && formDiv.noResend == null) { + if (formDiv && (formDiv.whichFactorForm === "EMAIL" || formDiv.whichFactorForm === "SMS") && formDiv.noResend == null) { let didNotGetMsg = this.localizeMsg(formDiv.whichFactorForm.toLowerCase() + '-did-not-get-msg', 'Did not get the message?'); let resendMsg = this.localizeMsg(formDiv.whichFactorForm.toLowerCase() + '-resend-btn', 'Resend message'); - setTimeout(function() { + setTimeout(function () { self.logMsg(formDiv.whichFactorForm + " factor detected for showing Resend Message option."); var resendDivElem = document.createElement('div'); resendDivElem.classList.add('sameline'); resendDivElem.innerHTML = '' + didNotGetMsg + ' ' + - '' + resendMsg + ''; + '' + resendMsg + ''; // Adding the Resend Option preferably right after the submit button. let submitBtnElem = formDiv.querySelector("#submit-btn"); @@ -89,7 +89,7 @@ this.removeBtnAndShowSpinner = function(btn) { formDiv.appendChild(resendDivElem); } - formDiv.querySelector("#resend-btn").onclick = function() { + formDiv.querySelector("#resend-btn").onclick = function () { obj.sdk.resendOtp(); }; }, timeInMilis); @@ -98,18 +98,18 @@ this.removeBtnAndShowSpinner = function(btn) { // Displays an HTML snippet allowing users to request a new email. // Mainly used by Forgot Password flow. - this.showResendEmail = function(formDiv, obj, timeInMilis) { + this.showResendEmail = function (formDiv, obj, timeInMilis) { if (formDiv && formDiv.whichForm != null && formDiv.whichForm === "FORGOT_PASSWORD_FORM") { var usernameLocal = formDiv.querySelector("#forgotUserName").value; - let didNotGetMsg = this.localizeMsg('forgot-pw-did-not-get-msg','Did not get the email?'); + let didNotGetMsg = this.localizeMsg('forgot-pw-did-not-get-msg', 'Did not get the email?'); let resendMsg = this.localizeMsg('forgot-pw-resend-btn', 'Resend email'); - setTimeout(function() { + setTimeout(function () { var resendDivElem = document.createElement('div'); resendDivElem.classList.add('sameline'); resendDivElem.innerHTML = '' + didNotGetMsg + ' ' + - '' + resendMsg + ''; + '' + resendMsg + ''; // Adding the Resend Option preferably right after the submit button. let submitBtnElem = formDiv.querySelector("#submit-btn"); @@ -120,7 +120,7 @@ this.removeBtnAndShowSpinner = function(btn) { formDiv.appendChild(resendDivElem); } - formDiv.querySelector("#resend-btn").onclick = function() { + formDiv.querySelector("#resend-btn").onclick = function () { obj.sdk.forgotPassword(usernameLocal); }; @@ -128,20 +128,20 @@ this.removeBtnAndShowSpinner = function(btn) { } } - this.showReinputUserName = function(formDiv, obj, timeInMilis) { + this.showReinputUserName = function (formDiv, obj, timeInMilis) { if (formDiv && formDiv.whichForm != null && formDiv.whichForm === "FORGOT_PASSWORD_FORM") { var usernameLocal = formDiv.querySelector("#forgotUserName").value; - let didNotGetMsg = this.localizeMsg('forgot-pw-incorrect-username-msg','Incorrect UserName?'); - let resendMsg = this.localizeMsg('forgot-pw-incorrect-username-btn','Fix UserName'); + let didNotGetMsg = this.localizeMsg('forgot-pw-incorrect-username-msg', 'Incorrect UserName?'); + let resendMsg = this.localizeMsg('forgot-pw-incorrect-username-btn', 'Fix UserName'); - setTimeout(function() { + setTimeout(function () { formDiv.appendChild(document.createElement('hr')); var resendDivElem = document.createElement('div'); resendDivElem.classList.add('sameline'); resendDivElem.innerHTML = '' + didNotGetMsg + ' ' + - '' + resendMsg + ''; + '' + resendMsg + ''; // Adding the Resend Option preferably right after the submit button. let submitBtnElem = formDiv.querySelector("#submit-btn"); @@ -152,8 +152,8 @@ this.removeBtnAndShowSpinner = function(btn) { formDiv.appendChild(resendDivElem); } - formDiv.querySelector("#resend-username-btn").onclick = function() { - obj.displayForgotPassWordForm( ); + formDiv.querySelector("#resend-username-btn").onclick = function () { + obj.displayForgotPassWordForm(); }; }, timeInMilis); @@ -161,18 +161,18 @@ this.removeBtnAndShowSpinner = function(btn) { } // Localizes all labels inside formDiv - this.localize = function(formDiv) { + this.localize = function (formDiv) { if (resources) { var resElms = formDiv.querySelectorAll('[data-res]'); for (var n = 0; n < resElms.length; n++) { var elem = resElms[n]; var resKey = elem.getAttribute('data-res'); if (resKey) { - if ( resources[resKey] ) { + if (resources[resKey]) { elem.innerHTML = resources[resKey]; } else { - this.logWarning( "Translation missing for resource key '" + resKey + "'"); + this.logWarning("Translation missing for resource key '" + resKey + "'"); } } } @@ -180,18 +180,18 @@ this.removeBtnAndShowSpinner = function(btn) { } // this.localize // Returns the message associated with a given key. If the key isn't found, the message (msg) as passed is returned. - this.localizeMsg = function(resKey, msg) { + this.localizeMsg = function (resKey, msg) { if (resources && resources[resKey]) { return resources[resKey]; } else { - this.logWarning( "Translation missing for resource key '" + resKey + "'"); + this.logWarning("Translation missing for resource key '" + resKey + "'"); return msg; } } - this.mask = function(msg) { - let propsToMask = ['username','password','bypasscode','otpcode','questions','deviceid','requeststate','phonenumber','token','authntoken','trusttoken','userid']; + this.mask = function (msg) { + let propsToMask = ['username', 'password', 'bypasscode', 'otpcode', 'questions', 'deviceid', 'requeststate', 'phonenumber', 'token', 'authntoken', 'trusttoken', 'userid']; var stars = '***'; var temp; @@ -223,66 +223,66 @@ this.removeBtnAndShowSpinner = function(btn) { } return JSON.stringify(temp); } - catch(e) { + catch (e) { return stars; } } //this.mask - this.logMsg = function(msg) { + this.logMsg = function (msg) { if (window.console && this.debugEnabled) { console.log('LoginApp: ' + msg); } } // this.logMsg - this.logWarning = function(msg) { + this.logWarning = function (msg) { console.log('LoginApp (WARNING): ' + msg); } - this.removeSignupFunction = function() { - Array.prototype.slice.call(document.querySelectorAll('.hidelater')).forEach(function(e) { // Making MS family (IE and Edge) happy + this.removeSignupFunction = function () { + Array.prototype.slice.call(document.querySelectorAll('.hidelater')).forEach(function (e) { // Making MS family (IE and Edge) happy e.style.visibility = "hidden"; }); } - this.replaceDiv = function(divid,replacement,dofocus) { - // divname is the ID of the div to replace - // replacement is the Element to replace it with - // dofocus says "set the focus to the first text input" + this.replaceDiv = function (divid, replacement, dofocus) { + // divname is the ID of the div to replace + // replacement is the Element to replace it with + // dofocus says "set the focus to the first text input" - // Note: for the signin-div the replacement div SHOULD havr a .id prop - // matching the one that's being replacing - if ( replacement.id != divid ) { - this.logMsg( "WARNING: replacement div id=" + replacement.id + " does not match expected value of " + divid ); - } + // Note: for the signin-div the replacement div SHOULD havr a .id prop + // matching the one that's being replacing + if (replacement.id != divid) { + this.logMsg("WARNING: replacement div id=" + replacement.id + " does not match expected value of " + divid); + } - // Localizing while replacement div still not visible. - this.localize(replacement); + // Localizing while replacement div still not visible. + this.localize(replacement); - var oldForm = document.getElementById(divid); - oldForm.parentNode.replaceChild(replacement, oldForm); + var oldForm = document.getElementById(divid); + oldForm.parentNode.replaceChild(replacement, oldForm); - this.showResendCodeOption(replacement, this, 10000); - this.showResendEmail(replacement, this, 10000); - this.showReinputUserName(replacement, this, 2000); - this.showSwitchEnrollFactorOption(replacement, this); + this.showResendCodeOption(replacement, this, 10000); + this.showResendEmail(replacement, this, 10000); + this.showReinputUserName(replacement, this, 2000); + this.showSwitchEnrollFactorOption(replacement, this); - // find the first text input field and put the focus there - if ( dofocus ) { - div = document.getElementById(divid); - if ( div ) { - let firstInput = div.querySelector('input[type="text"]'); - if (firstInput) firstInput.focus(); - } + // find the first text input field and put the focus there + if (dofocus) { + div = document.getElementById(divid); + if (div) { + let firstInput = div.querySelector('input[type="text"]'); + if (firstInput) firstInput.focus(); } + } } // Performs form data validation and style form elements accordingly - this.validateForm = function(formDiv) { + this.validateForm = function (formDiv) { formDiv.querySelector("#submit-btn").disabled = true; // Looking for input fields marked as required and empty. const inputFields = formDiv.getElementsByTagName("INPUT"); var isError = false; - for (i=0; i < inputFields.length; i++) { + for (i = 0; i < inputFields.length; i++) { this.logMsg('Validating field ' + inputFields[i].id); if (inputFields[i].required && inputFields[i].value.trim() === '') { isError = true; @@ -291,8 +291,8 @@ this.removeBtnAndShowSpinner = function(btn) { } } if (isError) { - let errorMessage = this.localizeMsg('error-required-fld','Required field empty'); - this.setLoginErrorMessage({code:'', msg:errorMessage}); + let errorMessage = this.localizeMsg('error-required-fld', 'Required field empty'); + this.setLoginErrorMessage({ code: '', msg: errorMessage }); formDiv.querySelector("#submit-btn").disabled = false; return false; } @@ -302,8 +302,8 @@ this.removeBtnAndShowSpinner = function(btn) { } // Handles focusout event on input fields for styling the field - this.handleFocusOutEvent = function(elem) { - elem.addEventListener('focusout', function() { + this.handleFocusOutEvent = function (elem) { + elem.addEventListener('focusout', function () { if (elem.value.trim().length == 0) { elem.classList.add('on__error'); } @@ -314,9 +314,9 @@ this.removeBtnAndShowSpinner = function(btn) { } // Handles onClick event for submiting form data. - this.handleClickToSubmitEvent = function(formDiv, obj, methodName, includeAuthnFactor) { + this.handleClickToSubmitEvent = function (formDiv, obj, methodName, includeAuthnFactor) { const self = this; - formDiv.querySelector("#submit-btn").onclick = function() { + formDiv.querySelector("#submit-btn").onclick = function () { if (obj.validateForm(formDiv)) { const payload = obj.buildPayload(methodName, formDiv); if (payload) { // Giving a chance for buildPayload to fail. @@ -331,9 +331,9 @@ this.removeBtnAndShowSpinner = function(btn) { } // Handles onKeyPress event for submiting form data. - this.handleKeyPressToSubmitEvent = function(formDiv, elem, obj, methodName, includeAuthnFactor) { + this.handleKeyPressToSubmitEvent = function (formDiv, elem, obj, methodName, includeAuthnFactor) { const self = this; - elem.onkeypress = function(event) { + elem.onkeypress = function (event) { if (event.keyCode == 13) { if (obj.validateForm(formDiv)) { const payload = obj.buildPayload(methodName, formDiv); @@ -350,14 +350,14 @@ this.removeBtnAndShowSpinner = function(btn) { } // Handles onClick event for handling forgotPassword. - this.handleClickEvent = function(formDiv, obj) { - formDiv.querySelector("#signin-forgot-pass").onclick = function() { - obj.displayForgotPassWordForm(formDiv); + this.handleClickEvent = function (formDiv, obj) { + formDiv.querySelector("#signin-forgot-pass").onclick = function () { + obj.displayForgotPassWordForm(formDiv); } } // Builds the expected credentials payload to the respective API in the SDK, here identified by methodName. - this.buildPayload = function(methodName, formDiv) { + this.buildPayload = function (methodName, formDiv) { let preferredFactorElem = document.getElementById("preferredFactorOption"); @@ -365,45 +365,67 @@ this.removeBtnAndShowSpinner = function(btn) { case 'postCreds': // ER #1. Saving the request origin. This is read later for determining the user preferred factor. this.setUnPwOrigin("true"); - return {"username": document.getElementById("userid").value, "password": document.getElementById("password").value}; + var data = { + "credentials": { + "username": document.getElementById("userid").value, + "password": document.getElementById("password").value, + "origin": window.location.origin, + } + } + if (document.getElementById("kmsi") != null) { + data.credentials["keepMeSignedIn"] = document.getElementById("kmsi").checked; + return data; + } + return data; - case 'postUserName': - this.setUnPwOrigin("true"); - return {"username": document.getElementById("userid").value}; + case 'postUserNameFirst': + this.setUnPwOrigin("true"); + var dataUserNameFirst = { + "backUpFactors": true, + "credentials": { + "username": document.getElementById("userid").value, + "origin": window.location.origin, + } + }; + if (document.getElementById("kmsi") != null) { + dataUserNameFirst.credentials["keepMeSignedIn"] = document.getElementById("kmsi").checked; + return dataUserNameFirst; + } + return dataUserNameFirst; case 'enrollSecurityQuestions': - var secQuestions=[]; - var qID=[]; + var secQuestions = []; + var qID = []; numOfQuestions = formDiv.getElementsByTagName("SELECT"); - for(qi=0;qi' + + ) + ) { + var preferredFactorDiv = document.createElement('div'); + preferredFactorDiv.innerHTML = + ''; - - formDiv.insertBefore(preferredFactorDiv, formDiv.querySelector("#submit-btn")); - } - // End of Issue #1 + 'Set this factor as preferred' + + ''; - if (payload.trustedDeviceSettings) { - var trustedDeviceDiv = document.createElement('div'); - trustedDeviceDiv.innerHTML = - ''; + + // if we're rendering the checkbox it should be unchecked + // and the session data should be set to false + this.setTrustedDeviceOption(false); + // then add a listener when the value changes + trustedDeviceDiv.querySelector("#trustedDevice").addEventListener('change', function () { + self.setTrustedDeviceOption(this.checked); + }); + formDiv.insertBefore(trustedDeviceDiv, formDiv.querySelector("#submit-btn")); + } + if (payload.nextOp.indexOf("getBackupFactors") > 0) { + // add the alternative button + var divHr = document.createElement('div'); + divHr.classList.add('hr'); + divHr.innerHTML = '
OR
'; + formDiv.appendChild(divHr); + + let altFactorsMsg = this.localizeMsg('backup-btn', 'Use an Alternative Factor'); + + var altFactorsDivElem = document.createElement('div'); + altFactorsDivElem.classList.add('sameline'); + altFactorsDivElem.innerHTML = '' + altFactorsMsg + ''; + formDiv.appendChild(altFactorsDivElem); + + var div = document.createElement("div"); + div.id = "backupFactorChooser"; + div.class = "backupFactorChooser"; + div.className = "backupFactorChooser hidden"; + formDiv.appendChild(div); + + formDiv.querySelector("#backupfactors-btn").addEventListener('click', function () { + this.style.display = "none"; + var backupchooser = document.getElementById("backupFactorChooser"); + backupchooser.style.display = "block"; + backupchooser.innerHTML = '
Loading...
'; + self.sdk.submitBackupFactors(); + }); + } } - } - this.replaceDiv("signin-div",formDiv,true); - } - else - if (step === "enrollment") { - // SMS special case where we need to 'submitCreds' during enrollment - if (which === 'SMS' && payload.SMS && payload.SMS.credentials[0] === "otpCode") { - (this.AuthenticationFactorInfo[which]["loginFormFunction"])(formDiv,payload); - } - else { - (this.AuthenticationFactorInfo[which]["enrollFormFunction"])(formDiv,payload); + this.replaceDiv("signin-div", formDiv, true); } - this.replaceDiv("signin-div",formDiv,true); + else + if (step === "enrollment") { + // SMS special case where we need to 'submitCreds' during enrollment + if (which === 'SMS' && payload.SMS && payload.SMS.credentials[0] === "otpCode") { + (this.AuthenticationFactorInfo[which]["loginFormFunction"])(formDiv, payload); + } + else { + (this.AuthenticationFactorInfo[which]["enrollFormFunction"])(formDiv, payload); + } + this.replaceDiv("signin-div", formDiv, true); + } } - } } // Builds the main form, allowing username/password posting + IDP selection - // Logic has been moved into buildForm - this.buildUidPwForm = function(formDiv,IDPdata) { - this.buildForm(formDiv,"showUidPw",IDPdata,true); + // Logic has been moved into buildFirstForm + this.buildUidPwForm = function (formDiv, IDPdata) { + this.buildFirstForm(formDiv, true, IDPdata); } // Builds the main form, allowing IDP selection - this.buildIdpChooserForm = function(formDiv,IDPdata,isFirstForm) { - this.buildForm(formDiv,"showIdp",IDPdata,isFirstForm); - } - - // builds the user name first form - this.buildUidForm = function(formDiv,IDPdata) { - this.buildForm(formDiv,"showUid",IDPdata,true); + this.buildIdpChooserForm = function (formDiv, IDPdata) { + this.buildFirstForm(formDiv, false, IDPdata); } // this function builds both the UID + PW and/or the IDP chooser form // this is all in one function to avoid duplicating code or comments // the boolean showUidPw determines whether to show the uid+pw portion - this.buildForm = function(formDiv,showField,IDPdata,isFirstForm) { + this.buildFirstForm = function (formDiv, showUidPw, IDPdata) { const self = this; - var showUidOrUidPwFields = true; // always show the header message - if (isFirstForm) { - formDiv.innerHTML = - '

Welcome

'; + formDiv.innerHTML = + '

Welcome

' + let userNameFirst = IDPdata ? IDPdata.userNameFirst : false; + if (userNameFirst) { + // IDPdata=IDPdata.payload; + IDPdata = IDPdata.payload.IDP; } - // then show the UID + PW form if needed - switch(showField) { - - case "showUidPw": - formDiv.innerHTML += - '' + - ''; - break; - - case "showUid": - formDiv.innerHTML += - ''; - break; - - default: - showUidOrUidPwFields = false; - } - - if (showUidOrUidPwFields) { + if (showUidPw) { + let keepMeSignedIn = JSON.parse(this.getLoginCtx())["keepMeSignedInEnabled"]; + let password = ""; + let checkbox = ""; + if (keepMeSignedIn) { + checkbox = ''; + } + if (!userNameFirst) { + password = '' + } formDiv.innerHTML += - '' + - '' + - ''; + '' + password + + + '' + checkbox + + '' + + ''; } // if both UID+PW and an IDP list are being shown then add the "OR" bit - if ( showUidOrUidPwFields && IDPdata ) { - formDiv.innerHTML += '

OR sign in with
'; + if (showUidPw && IDPdata) { + formDiv.innerHTML += '

OR
'; } // and finally build the IDP list - if ( IDPdata ) { + if (IDPdata) { formDiv.innerHTML += ''; var idpDiv = document.createElement('div'); idpDiv.align = 'center'; - IDPdata.configuredIDPs.forEach(function(idp) { + if (IDPdata.configuredIDPs) { + IDPdata.configuredIDPs.forEach(function (idp) { - // Bug #28769680 - if ( idp.iconUrl === "null" ) { - idp.iconUrl = undefined; - } - - this.logMsg("Adding IDP to login page:"); - this.logMsg(JSON.stringify(idp)) + // Bug #28769680 + if (idp.iconUrl === "null") { + idp.iconUrl = undefined; + } - var btn = document.createElement('img'); - btn.title = idp.idpName; - btn.src = idp.iconUrl; - btn.className = 'external-idp-btn'; + this.logMsg("Adding IDP to login page:"); + this.logMsg(JSON.stringify(idp)) - if (!idp.iconUrl) { + var btn = document.createElement('img'); + btn.title = idp.idpName; + btn.src = idp.iconUrl; + btn.className = 'external-idp-btn'; + var txt = document.createElement('span'); + txt.className = 'external-idp-btn'; + if (!idp.iconUrl) { var xhttp = new XMLHttpRequest(); xhttp.onreadystatechange = function () { - if (this.readyState == 4 && this.status == 200) { - btn.src = '../images/custom-external-idp-icons/' + idp.idpName.toLowerCase() + '.png'; - } - else { - xhr_ = new XMLHttpRequest(); - xhr_.onreadystatechange = function () { - if (this.readyState == 4 && this.status == 200) { - btn.src = '../images/default-external-idp-icons/idcs-' + idp.idpName.toLowerCase() + '-icon.png'; - } - else { - if ( idp.idpType === "Saml") { - btn.src='../images/default-external-idp-icons/idcs-saml-icon.png'; - } - else if ( idp.idpType === "Social") { - btn.src='../images/default-external-idp-icons/idcs-oidc-icon.png'; - } - else { - btn.src='../images/default-external-idp-icons/external-identity-provider-large-gray-82.png'; - } - } + if (this.readyState == 4 && this.status == 200) { + btn.src = '../images/custom-external-idp-icons/' + idp.idpName.toLowerCase() + '.png'; + } + else { + xhr_ = new XMLHttpRequest(); + xhr_.onreadystatechange = function () { + if (this.readyState == 4 && this.status == 200) { + btn.src = '../images/default-external-idp-icons/idcs-' + idp.idpName.toLowerCase() + '-icon.png'; + } + + else { + txt.innerText = idp.idpName; + if (idp.idpType === "Saml") { + btn.src = '../images/default-external-idp-icons/idcs-saml-icon.png'; + } + else if (idp.idpType === "Social") { + btn.src = '../images/default-external-idp-icons/idcs-oidc-icon.png'; } - xhr_.open('HEAD', '../images/default-external-idp-icons/idcs-' + idp.idpName.toLowerCase() + '-icon.png'); - xhr_.send(); + else if (idp.idpType === "X509") { + btn.src = '../images/default-external-idp-icons/external-identity-provider-large-gray-82.png'; + } + else { + btn.src = '../images/default-external-idp-icons/external-identity-provider-large-gray-82.png'; + } + } } + xhr_.open('HEAD', '../images/default-external-idp-icons/idcs-' + idp.idpName.toLowerCase() + '-icon.png'); + xhr_.send(); + } }; xhttp.open("HEAD", '../images/custom-external-idp-icons/' + idp.idpName.toLowerCase() + '.png', true); xhttp.send(); - } + } - btn.addEventListener('click', function (event) { + btn.addEventListener('click', function (event) { var name = idp.idpName; self.logMsg('IDP clicked: ' + name); var payload = { - 'requestState': self.getRequestState(), - 'idpName': idp.idpName, - 'idpId': idp.idpId, - 'clientId': self.getClientId(), - 'idpType': idp.idpType + 'requestState': self.getRequestState(), + 'idpName': idp.idpName, + 'idpId': idp.idpId, + 'clientId': self.getClientId(), + 'idpType': idp.idpType }; self.sdk.chooseIDP(payload); + }); + var displaybox = document.createElement("div"); + displaybox.className = "externalIdpBtn"; + displaybox.appendChild(btn); + displaybox.appendChild(txt); + idpDiv.appendChild(displaybox); }); - idpDiv.appendChild(btn); - }); + } + formDiv.appendChild(idpDiv); } // and now that we're done updating the HTML of that div we can // attach the event handlers for clicking or hitting enter - if ( showUidOrUidPwFields ) { - if (showField == "showUid"){ - this.handleClickToSubmitEvent(formDiv,this,'postUserName'); - this.handleKeyPressToSubmitEvent(formDiv,formDiv.querySelector("#userid"),this,'postUserName'); - } - else { - this.handleClickToSubmitEvent(formDiv,this,'postCreds'); - this.handleKeyPressToSubmitEvent(formDiv,formDiv.querySelector("#password"),this,'postCreds'); - } - this.handleClickEvent(formDiv,this); + if (showUidPw && !userNameFirst) { + this.handleClickToSubmitEvent(formDiv, this, 'postCreds'); + this.handleKeyPressToSubmitEvent(formDiv, formDiv.querySelector("#password"), this, 'postCreds'); + this.handleClickEvent(formDiv, this); + } + if (showUidPw && userNameFirst) { + this.handleClickToSubmitEvent(formDiv, this, 'postUserNameFirst'); + this.handleKeyPressToSubmitEvent(formDiv, formDiv.querySelector("#userid"), this, 'postUserNameFirst'); + this.handleClickEvent(formDiv, this); } return formDiv; - }; // this.buildForm + }; // this.buildFirstForm // Builds the Email OTP form - this.buildEmailOtpForm = function(formDiv,payload) { + this.buildEmailOtpForm = function (formDiv, payload) { formDiv.innerHTML += - '

Verifying OTP

' + - '
Please enter OTP code sent to ' + formDiv.whichDisplayName + '
' + - '' + - '' + - ''; + '

Verifying OTP

' + + '
Please enter OTP code sent to ' + formDiv.whichDisplayName + '
' + + '' + + '' + + ''; - this.handleClickToSubmitEvent(formDiv,this,'postEmailOtp'); - this.handleKeyPressToSubmitEvent(formDiv,formDiv.querySelector("#otpCode"),this,'postEmailOtp'); + this.handleClickToSubmitEvent(formDiv, this, 'postEmailOtp'); + this.handleKeyPressToSubmitEvent(formDiv, formDiv.querySelector("#otpCode"), this, 'postEmailOtp'); } // this.buildEmailOtpForm - // Builds the Email OTP form - this.buildSmsOtpForm = function(formDiv,payload) { + // Builds the SMS OTP form + this.buildSmsOtpForm = function (formDiv, payload) { formDiv.innerHTML += - '

Verifying OTP

' + - '
Please enter OTP code sent to ' + formDiv.whichDisplayName + '
' + - '' + - '' + - ''; + '

Verifying OTP

' + + '
Please enter OTP code sent to ' + formDiv.whichDisplayName + '
' + + '' + + '' + + ''; - this.handleClickToSubmitEvent(formDiv,this,'postSmsOtp'); - this.handleKeyPressToSubmitEvent(formDiv,formDiv.querySelector("#otpCode"),this,'postSmsOtp'); + this.handleClickToSubmitEvent(formDiv, this, 'postSmsOtp'); + this.handleKeyPressToSubmitEvent(formDiv, formDiv.querySelector("#otpCode"), this, 'postSmsOtp'); } // this.buildSmsOtpForm // Builds the Security Questions form. - this.buildSecQuestionsForm = function(formDiv,payload) { + this.buildSecQuestionsForm = function (formDiv, payload) { var secQuestionsInput = ''; var elemIds = []; - for (i=0; i < payload.SECURITY_QUESTIONS.questions.length; i++) { - secQuestionsInput += ''; + for (i = 0; i < payload.SECURITY_QUESTIONS.questions.length; i++) { + secQuestionsInput += ''; elemIds[i] = payload.SECURITY_QUESTIONS.questions[i].questionId; } @@ -909,29 +903,28 @@ this.removeBtnAndShowSpinner = function(btn) { '

Verifying Security Questions

' + '
Please provide the answers
' + secQuestionsInput + - ''+ + '' + ''; - this.handleClickToSubmitEvent(formDiv,this,'postSecQuestions'); - this.handleKeyPressToSubmitEvent(formDiv,formDiv.querySelector('#'+elemIds[elemIds.length-1]),this,'postSecQuestions'); + this.handleClickToSubmitEvent(formDiv, this, 'postSecQuestions'); + this.handleKeyPressToSubmitEvent(formDiv, formDiv.querySelector('#' + elemIds[elemIds.length - 1]), this, 'postSecQuestions'); } // this.buildSecQuestionsForm - // Displays the form where the user can choose which factor in enroll in. - this.displayEnrollmentOptionsForm = function(payload) { + this.displayEnrollmentOptionsForm = function (payload) { this.removeSignupFunction(); const self = this; var enrollmentOptions = ''; var buttonLabel; - for (i=0; i < payload.nextAuthFactors.length; i++) { + for (i = 0; i < payload.nextAuthFactors.length; i++) { let factor = payload.nextAuthFactors[i]; - if ( this.AuthenticationFactorInfo[factor] ) { + if (this.AuthenticationFactorInfo[factor]) { enrollmentOptions += - '
' + - '' + - '' + this.AuthenticationFactorInfo[factor].description + '' + + '
' + + '' + + '' + this.AuthenticationFactorInfo[factor].description + '' + '

'; } }; @@ -941,146 +934,236 @@ this.removeBtnAndShowSpinner = function(btn) { formDiv.classList.add("sign-in"); formDiv.id = 'signin-div'; formDiv.innerHTML = - '

Enabling 2-Step Verification

' + - '
Select a Method
' + - enrollmentOptions; + '

Enabling 2-Step Verification

' + + '
Select a Method
' + + enrollmentOptions; // this code iterates through the buttons and attaches the right function from AuthenticationFactorInfo to it var buttons = formDiv.getElementsByClassName("submit"); - for( i = 0; i 0) { + enrollmentOptions = '
Alternative login methods
'; + } + + let subdiv = document.createElement('div'); + subdiv.style.position = "relative"; + subdiv.style.right = "140px" + subdiv.classList.add("alt-factor-pane"); + subdiv.id = "signin-div"; + if (payload.nextAuthFactors.includes("USERNAME_PASSWORD")) { + passwordInput += '' + + '' + + ''; + + subdiv.innerHTML = + passwordInput + enrollmentOptions; + subdiv.querySelector("#submitbtn").onclick = () => { + let data = JSON.stringify({ + "op": "credSubmit", + "authFactor": "USERNAME_PASSWORD", + "credentials": { + "username": email, + "password": document.getElementById("password").value + }, + "requestState": this.getRequestState(), + }); + this.sdk.authenticate(data) + } + subdiv.querySelector("#altfac").style.cursor = "pointer"; + subdiv.querySelector("#altfac").onclick = () => { + // subdiv.innerHTML += enrollmentOptions; + this.sdk.getEnrollmentFactorsuserNameFirst({ + "credentials": { + "username": email + } + }); + } + this.replaceDiv("signin-div", subdiv, false); + } + else { + this.sdk.getEnrollmentFactorsuserNameFirst({ + "credentials": { + "username": email + } + }); + } + } // this.displayuserNameFirstNextAuth + // Builds the OTP enrollment form, where OTPs are sent to the user's email. - this.buildOtpEmailEnrollmentForm = function(formDiv,payload) { + this.buildOtpEmailEnrollmentForm = function (formDiv, payload) { this.logMsg("Building OTP Email Enrollment Form"); formDiv.innerHTML += - '

Enrolling in OTP over E-Mail

' + - '
Please, enter OTP sent to ' + payload.displayName + '
' + - '' + - '' + - ''; + '

Enrolling in OTP over E-Mail

' + + '
Please, enter OTP sent to ' + payload.displayName + '
' + + '' + + '' + + ''; - this.handleClickToSubmitEvent(formDiv,this,'enrollOtpEmail'); - this.handleKeyPressToSubmitEvent(formDiv,formDiv.querySelector("#otpCode"),this,'enrollOtpEmail'); + this.handleClickToSubmitEvent(formDiv, this, 'enrollOtpEmail'); + this.handleKeyPressToSubmitEvent(formDiv, formDiv.querySelector("#otpCode"), this, 'enrollOtpEmail'); } // this.buildOtpEmailEnrollmentForm + this.buildFidoEnrollmentForm = function (formDiv, payload) { + this.setRequestState(payload.requestState); + this.logMsg("Building FIDO Enrollment Form"); + register(payload).then(function (credential) { + self.fidoCallback(credential); + }); + } // this.buildFidoEnrollmentForm + + this.buildFidoAuthenticationForm = function (formDiv, payload) { + '

Verifying Fido Authentication

'; + formDiv.innerHTML += + '
' + + 'Complete the Fido authentication ' + + '
'; + + this.setRequestState(payload.requestState); + this.logMsg("Building FIDO Enrollment Form"); + authenticate(payload).then(function (credential) { + self.fidoCallback(credential); + }); + } // this.buildFidoAuthenticationForm + + this.fidoCallback = function (fidoAssertionResponse) { + var details = {}; + var credentials = {}; + details.op = "credSubmit"; + details.origin = window.location.origin; + credentials.fidoAssertion = encodeJson(fidoAssertionResponse); + details.credentials = credentials; + details.requestState = this.getRequestState(); + self.sdk.authenticate(JSON.stringify(details)); + } + // Displays the form for SMS enrollment, where the user provides his mobile phone number and receives an OTP in it. - this.buildSMSMobileEnrollmentForm = function(formDiv,payload) { + this.buildSMSMobileEnrollmentForm = function (formDiv, payload) { this.logMsg("Building the SMS Mobile Enrollment Form "); formDiv.noResend = true; formDiv.innerHTML = - '

Enrolling in OTP over SMS

' + - '
Please, enter mobile number to send SMS
' + - '' + - '' + - ''; + '

Enrolling in OTP over SMS

' + + '
Please, enter mobile number to send SMS
' + + '' + + '' + + ''; - this.handleClickToSubmitEvent(formDiv,this,'initEnrollMobileNumber'); - this.handleKeyPressToSubmitEvent(formDiv,formDiv.querySelector("#mobileNumber"),this,'initEnrollMobileNumber'); + this.handleClickToSubmitEvent(formDiv, this, 'initEnrollMobileNumber'); + this.handleKeyPressToSubmitEvent(formDiv, formDiv.querySelector("#mobileNumber"), this, 'initEnrollMobileNumber'); } // this.buildSMSMobileEnrollmentForm // social registration page - this.displaySocialRegistrationForm = function(socialData) { + this.displaySocialRegistrationForm = function (socialData) { - const self = this; + const self = this; - var formDiv = document.createElement('div'); - formDiv.classList.add("form"); - formDiv.classList.add("sign-in"); - formDiv.id = 'signin-div'; + var formDiv = document.createElement('div'); + formDiv.classList.add("form"); + formDiv.classList.add("sign-in"); + formDiv.id = 'signin-div'; - formDiv.innerHTML = - '

Social Registration

' + - '' + - '' + - '' + - '' + - '' + - ''+ - ''; - - // prepopulate using values from ID TOKEN... - formDiv.querySelector("#social-email").value = socialData.userData.userName; - formDiv.querySelector("#social-givenName").value = socialData.userData.givenName; - formDiv.querySelector("#social-familyName").value = socialData.userData.familyName; - - formDiv.querySelector("#social-submit-btn").onclick = function() { - var data = {}; - data.op="socialRegister"; - data.socialSCIMAttrs = {}; - data.socialSCIMAttrs.userName = socialData.userData.userName; - data.socialSCIMAttrs.email = socialData.userData.email ; - data.socialSCIMAttrs.givenName = formDiv.querySelector("#social-givenName").value; - data.socialSCIMAttrs.familyName = formDiv.querySelector("#social-familyName").value; - data.socialSCIMAttrs.phoneNo = formDiv.querySelector("#social-mobileNo").value; - data.userMappingAttr = "email"; - data.callbackUrl = "http://www.google.com"; //dummy! api will break without it... - data.requestState = self.getRequestState(); - self.sdk.authenticate(JSON.stringify(data)); - }; + formDiv.innerHTML = + '

Social Registration

' + + '' + + '' + + '' + + '' + + '' + + '' + + ''; + + // prepopulate using values from ID TOKEN... + formDiv.querySelector("#social-email").value = socialData.userData.userName; + formDiv.querySelector("#social-givenName").value = socialData.userData.givenName; + formDiv.querySelector("#social-familyName").value = socialData.userData.familyName; + + formDiv.querySelector("#social-submit-btn").onclick = function () { + var data = {}; + data.op = "socialRegister"; + data.socialSCIMAttrs = {}; + data.socialSCIMAttrs.userName = socialData.userData.userName; + data.socialSCIMAttrs.email = socialData.userData.email; + data.socialSCIMAttrs.givenName = formDiv.querySelector("#social-givenName").value; + data.socialSCIMAttrs.familyName = formDiv.querySelector("#social-familyName").value; + data.socialSCIMAttrs.phoneNo = formDiv.querySelector("#social-mobileNo").value; + data.userMappingAttr = "email"; + data.callbackUrl = "http://www.google.com"; //dummy! api will break without it... + data.requestState = self.getRequestState(); + self.sdk.authenticate(JSON.stringify(data)); + }; - formDiv.querySelector("#social-cancel-btn").onclick = function() { - self.removeSocialData(); - window.location.href='./signin.html'; - } + formDiv.querySelector("#social-cancel-btn").onclick = function () { + self.removeSocialData(); + window.location.href = './signin.html'; + } - self.replaceDiv("signin-div", formDiv,true); - // remove the 'Sign-Up' button - var button = document.querySelector('.img__btn'); - button.parentNode.removeChild(button); + self.replaceDiv("signin-div", formDiv, true); + // remove the 'Sign-Up' button + var button = document.querySelector('.img__btn'); + button.parentNode.removeChild(button); } // Builds the form for security questions enrollment. - this.buildSecurityQuestionsEnrollmentForm = function(formDiv,payload) { + this.buildSecurityQuestionsEnrollmentForm = function (formDiv, payload) { this.logMsg("Display Security Questions Setup Form"); this.logMsg("Payload: " + JSON.stringify(payload)); formDiv.innerHTML = - '

Enrolling in Security Questions

' + - '
Enter an answer for each chosen question
'; + '

Enrolling in Security Questions

' + + '
Enter an answer for each chosen question
'; - var numOfQuestions=payload.SECURITY_QUESTIONS.secQuesSettings.numQuestionsToSetup; - this.logMsg("numOfQuestions="+numOfQuestions); + var numOfQuestions = payload.SECURITY_QUESTIONS.secQuesSettings.numQuestionsToSetup; + this.logMsg("numOfQuestions=" + numOfQuestions); - var questions= payload.SECURITY_QUESTIONS.questions; - this.logMsg("Questions "+JSON.stringify(questions)); + var questions = payload.SECURITY_QUESTIONS.questions; + this.logMsg("Questions " + JSON.stringify(questions)); - var listCount=questions.length; + var listCount = questions.length; var qIndex; - for(qIndex=0;qIndex'; var hintLabel = document.createElement('label'); - hintLabel.innerHTML = 'Hint'; + hintLabel.innerHTML = 'Hint'; - for(i=0; i < listCount; i++) { + for (i = 0; i < listCount; i++) { var opt = document.createElement('option'); opt.innerHTML = questions[i].text; opt.value = questions[i].text; @@ -1096,14 +1179,13 @@ this.removeBtnAndShowSpinner = function(btn) { '' + ''; - this.handleClickToSubmitEvent(formDiv,self,'enrollSecurityQuestions'); - this.handleKeyPressToSubmitEvent(formDiv,formDiv.querySelector('#ans-'+(qIndex-1)),self,'enrollSecurityQuestions'); + this.handleClickToSubmitEvent(formDiv, self, 'enrollSecurityQuestions'); + this.handleKeyPressToSubmitEvent(formDiv, formDiv.querySelector('#ans-' + (qIndex - 1)), self, 'enrollSecurityQuestions'); } // this.displaySecurityQuestionsEnrollmentForm // Displays enrollment success message and allows the user to enroll in another factor. - this.displayEnrollmentSuccess = function(payload) { - + this.displayEnrollmentSuccess = function (payload) { this.removeSignupFunction(); const self = this; @@ -1112,24 +1194,29 @@ this.removeBtnAndShowSpinner = function(btn) { formDiv.classList.add("sign-in"); formDiv.id = 'signin-div'; formDiv.innerHTML = - '

Enabling 2-Step Verification

' + - '
' + - '2-Step verification method has been set successfully.' + - '
' + - ''; + '

Enabling 2-Step Verification

' + + '
' + + '2-Step verification method has been set successfully.' + + '
' + + ''; if (payload.nextOp.indexOf("enrollment") >= 0) { formDiv.innerHTML += - '

OR
' + - '
' + - 'Enroll Another Factor' + - '
'; + '

OR
' + + '
' + + 'Enroll Another Factor' + + '
'; } - this.replaceDiv("signin-div",formDiv,true); + this.replaceDiv("signin-div", formDiv, true); document.getElementById("submit-btn").onclick = function () { - self.sdk.createToken(); + if (payload.nextOp.indexOf("createToken") >= 0) { + self.sdk.createToken(); + } + else { + self.sdk.getEnrollmentFactors(); + } }; // Button event listener for enroll in another factors should only be available if enrollment is in nextOp array. @@ -1139,12 +1226,10 @@ this.removeBtnAndShowSpinner = function(btn) { self.sdk.getEnrollmentFactors(); }; } - - } // this.displayEnrollmentSuccess // Initiates enrollment in Time-Based OTP. The user will be sent a QR code to enroll his device (via OMA App) and asked to enter the TOTP. - this.initEnrollOtpTotp = function(payload) { + this.initEnrollOtpTotp = function (payload) { this.removeSignupFunction(); var formDiv = document.createElement('div'); @@ -1153,21 +1238,21 @@ this.removeBtnAndShowSpinner = function(btn) { formDiv.id = 'signin-div'; formDiv.innerHTML = '
Loading...
'; - this.replaceDiv("signin-div",formDiv,true); + this.replaceDiv("signin-div", formDiv, true); this.sdk.initEnrollOtpTotp(); } // this.initEnrollOtpTotp - this.showAppLink = function() { + this.showAppLink = function () { var IS_IPAD = navigator.userAgent.match(/iPad/i) != null, - IS_IPHONE = !IS_IPAD && ((navigator.userAgent.match(/iPhone/i) != null) || (navigator.userAgent.match(/iPod/i) != null)), - IS_IOS = IS_IPAD || IS_IPHONE, - IS_ANDROID = !IS_IOS && navigator.userAgent.match(/android/i) != null, - IS_MOBILE = IS_IOS || IS_ANDROID; + IS_IPHONE = !IS_IPAD && ((navigator.userAgent.match(/iPhone/i) != null) || (navigator.userAgent.match(/iPod/i) != null)), + IS_IOS = IS_IPAD || IS_IPHONE, + IS_ANDROID = !IS_IOS && navigator.userAgent.match(/android/i) != null, + IS_MOBILE = IS_IOS || IS_ANDROID; // we're mimicking the behavior of the OOTB form here // iPhones (but not iPads) and android devices will show the link that opens the OMA app - if ( IS_IPHONE || IS_ANDROID ) { + if (IS_IPHONE || IS_ANDROID) { return true; } else { @@ -1176,31 +1261,34 @@ this.removeBtnAndShowSpinner = function(btn) { } // this.showAppLink() // Builds the form for Time-Based OTP, where the user is asked to enter the TOTP that shows up in his/her enrolled device (via OMA App) - this.buildTOTPForm = function(formDiv,payload) { - + this.buildTOTPForm = function (formDiv, payload) { // Enrolling - var qrCode=payload.TOTP.qrCode; + var qrCode = payload.TOTP ? payload.TOTP.qrCode : false; if (qrCode) { + if (qrCode.content) { + let url = new URL(qrCode.content); + this.setSelectedDevice(url.searchParams.get("Deviceid")); + } buttonText = "Enroll"; dataResKey = "enroll-totp-submit-btn"; formDiv.innerHTML += '

Enrolling in time-based OTP

'; - if ( this.showAppLink() ) { + if (this.showAppLink()) { formDiv.innerHTML += - '
' + - 'Tap to enroll your phone.' + - 'Then enter the code in the field below.>' + - '
'; + '
' + + 'Tap to enroll your phone.' + + 'Then enter the code in the field below.>' + + '
'; } else { formDiv.innerHTML += '
' + - 'Scan the QR code with the Oracle Mobile Authenticator App.' + - 'Then enter the code in the field below.' + + 'Scan the QR code with the Oracle Mobile Authenticator App.' + + 'Then enter the code in the field below.' + '
' + '
' + - '' + + '' + '
'; } } @@ -1219,25 +1307,25 @@ this.removeBtnAndShowSpinner = function(btn) { '' + ''; - this.handleClickToSubmitEvent(formDiv,this,'submitTOTP',payload.alternate); - this.handleKeyPressToSubmitEvent(formDiv,formDiv.querySelector("#otpCode"),this,'submitTOTP',payload.alternate); + this.handleClickToSubmitEvent(formDiv, this, 'submitTOTP', payload.alternate); + this.handleKeyPressToSubmitEvent(formDiv, formDiv.querySelector("#otpCode"), this, 'submitTOTP', payload.alternate); } //this.buildTOTPForm // Builds the form for push notifications enrollment via OMA App. - this.buildPushEnrollForm = function(formDiv,payload) { + this.buildPushEnrollForm = function (formDiv, payload) { qrCode = payload.PUSH.qrCode; formDiv.innerHTML += '

Enrolling in Push Notifications

'; - if ( this.showAppLink() ) { + if (this.showAppLink()) { formDiv.innerHTML += - ''; + ''; } else { formDiv.innerHTML += - '
Scan the QR code with the Oracle Mobile Authenticator App.
' + + '
Scan the QR code with the Oracle Mobile Authenticator App.
' + '
' + - '' + + '' + '
'; } @@ -1246,34 +1334,34 @@ this.removeBtnAndShowSpinner = function(btn) { this.submitPushPoll(3000); } // this.buildPushEnrollForm - this.buildPushLoginForm = function(formDiv,payload) { + this.buildPushLoginForm = function (formDiv, payload) { formDiv.innerHTML = '

Verifying Push Notification

'; - if (formDiv.whichDisplayName) { - formDiv.innerHTML += + if (formDiv.whichDisplayName) { + formDiv.innerHTML += '
' + - 'Notification sent to the Authenticator App on the following mobile device:' + - '' + formDiv.whichDisplayName + '' + + 'Notification sent to the Authenticator App on the following mobile device:' + + '' + formDiv.whichDisplayName + '' + '
'; - } - else { - formDiv.innerHTML += + } + else { + formDiv.innerHTML += '
' + - 'Notification sent to the Authenticator App on your mobile device' + + 'Notification sent to the Authenticator App on your mobile device' + '
'; - } - formDiv.innerHTML += + } + formDiv.innerHTML += '
' + - 'You must approve it for moving forward.'+ + 'You must approve it for moving forward.' + '
' + '
Loading...
' + '' + '
'; - this.submitPushPoll(3000); + this.submitPushPoll(3000); } //this.buildPushLoginForm // Builds a form for entering a bypass code. - this.buildBypasscodeLoginForm = function(formDiv,payload) { + this.buildBypasscodeLoginForm = function (formDiv, payload) { formDiv.innerHTML += '

Bypass Code

' + '
Provide your bypass code
' + @@ -1281,29 +1369,29 @@ this.removeBtnAndShowSpinner = function(btn) { '' + ''; - this.handleClickToSubmitEvent(formDiv,this,'submitBypasscode'); - this.handleKeyPressToSubmitEvent(formDiv,formDiv.querySelector("#bypassCode"),this,'submitBypasscode'); + this.handleClickToSubmitEvent(formDiv, this, 'submitBypasscode'); + this.handleKeyPressToSubmitEvent(formDiv, formDiv.querySelector("#bypassCode"), this, 'submitBypasscode'); } // this.buildBypasscodeLoginForm // Build the Terms of Use form - this.buildTermsForm = function(formDiv,payload) { + this.buildTermsForm = function (formDiv, payload) { formDiv.innerHTML += '' + ''; - this.handleClickToSubmitEvent(formDiv,this,'submitAcceptTerms'); + this.handleClickToSubmitEvent(formDiv, this, 'submitAcceptTerms'); } // buildTermsForm // Displays a form with backup factors the user can choose from - this.displayAltFactorsSubform = function(payload) { + this.displayAltFactorsSubform = function (payload) { const self = this; let otherFactorsDiv = document.createElement('div'); otherFactorsDiv.classList.add("alt-factor-pane"); @@ -1311,23 +1399,24 @@ this.removeBtnAndShowSpinner = function(btn) { // which factor is currently on screen? let currentFactor = document.getElementById("signin-div").whichFactorForm; + // which device displayName is currently being used? let displayNameOnScreen = document.getElementById("signin-div").whichDisplayName; - payload.nextAuthFactors.forEach(function(factor) { - if ( self.AuthenticationFactorInfo[factor] ) { + payload.nextAuthFactors.forEach(function (factor) { + if (self.AuthenticationFactorInfo[factor]) { // Case when user is enrolled with the same factor via multiples devices. if (payload[factor] && payload[factor].enrolledDevices) { - for (i=0; i < payload[factor].enrolledDevices.length; i++) { + for (i = 0; i < payload[factor].enrolledDevices.length; i++) { if (displayNameOnScreen != payload[factor].enrolledDevices[i].displayName || currentFactor !== factor) { let div = document.createElement('div'); //div.classList.add("tooltip"); div.classList.add("alt-factor-row"); div.innerHTML += '
' + self.AuthenticationFactorInfo[factor].label + ' ' + payload[factor].enrolledDevices[i].displayName + '
'; -// '' + self.AuthenticationFactorInfo[factor].description + ''; + //'' + self.AuthenticationFactorInfo[factor].description + ''; let currentDevice = payload[factor].enrolledDevices[i]; - div.querySelector('.alt-factor-button').addEventListener('click', function(event) { + div.querySelector('.alt-factor-button').addEventListener('click', function (event) { self.logMsg(currentDevice.displayName + ':' + currentDevice.deviceId); self.initiateAlternativeFactor(factor, payload, currentDevice); }); @@ -1343,20 +1432,19 @@ this.removeBtnAndShowSpinner = function(btn) { div.classList.add("alt-factor-row"); div.innerHTML += '
' + self.AuthenticationFactorInfo[factor].label + '
'; - // '' + self.AuthenticationFactorInfo[factor].description + ''; - div.querySelector('.alt-factor-button').addEventListener('click', function(event) { - self.initiateAlternativeFactor(factor, payload); - }); + //'' + self.AuthenticationFactorInfo[factor].description + ''; + div.querySelector('.alt-factor-button').addEventListener('click', function (event) { + self.initiateAlternativeFactor(factor, payload); + }); otherFactorsDiv.appendChild(div); } } }; }); - - this.replaceDiv("backupFactorChooser",otherFactorsDiv,false); + this.replaceDiv("signin-div", otherFactorsDiv, false); }; - this.initiateAlternativeFactor = function(factor, payload, device) { + this.initiateAlternativeFactor = function (factor, payload, device) { this.logMsg('Alternative factor selected: ' + factor); // Need to save selected device here, so it can be retrieved back later in the flow, // as notice that a roundtrip to IDCS is needed for factors that require some initiation. @@ -1367,22 +1455,25 @@ this.removeBtnAndShowSpinner = function(btn) { this.stopPushPoll(); // we must stop polling for push because a new factor has been chosen. switch (factor) { case 'EMAIL': - this.sdk.initAuthnOtpEmail({"deviceId":device.deviceId}); + this.sdk.initAuthnOtpEmail({ "deviceId": device.deviceId }); break; case 'SMS': - this.sdk.initAuthnMobileNumber({"deviceId":device.deviceId}); + this.sdk.initAuthnMobileNumber({ "deviceId": device.deviceId }); break; case 'TOTP': - this.displayForm(factor,"submitCreds",payload); + this.displayForm(factor, "submitCreds", payload); break; case 'PUSH': - this.sdk.initAuthnPush({"deviceId":device.deviceId}); + this.sdk.initAuthnPush({ "deviceId": device.deviceId }); break; case 'SECURITY_QUESTIONS': - this.displayForm(factor,"submitCreds",payload); + this.displayForm(factor, "submitCreds", payload); break; case 'BYPASSCODE': - this.displayForm(factor,"submitCreds",payload); + this.displayForm(factor, "submitCreds", payload); + break; + case 'FIDO_AUTHENTICATOR': + this.sdk.initAuthnFido({ "deviceId": device.deviceId }); break; default: this.logMsg('Unrecognized alternative factor: ' + factor); @@ -1390,82 +1481,8 @@ this.removeBtnAndShowSpinner = function(btn) { } - // Displays a form for just for password. This is displayed during the user name first flow. - this.displayPasswordForm = function(payload) { - - const self = this; - var formDiv = document.createElement('div'); - formDiv.classList.add("form"); - formDiv.classList.add("sign-in"); - formDiv.id = 'signin-div'; - formDiv.innerHTML = - '

Enter your Password

' + - '
Please enter your for password.
' + - '' + - '' + - '' + - '' + - ''; - - this.handleClickToSubmitEvent(formDiv,this,'postCreds'); - this.handleKeyPressToSubmitEvent(formDiv,formDiv.querySelector("#password"),this,'postCreds'); - this.handleClickEvent(formDiv,this); - - var backToLoginDivElem = document.createElement('div'); - backToLoginDivElem.classList.add('newline'); - backToLoginDivElem.innerHTML = '

OR
'; - backToLoginDivElem.innerHTML += 'Back to Login'; - formDiv.appendChild(backToLoginDivElem); - - if ((payload.IDP) && (payload.IDP.configuredIDPs.length > 0)) { - var formIDPDiv = document.createElement('div'); - formIDPDiv.id = 'idp-div'; - formIDPDiv.innerHTML = - '

Choose your IDP

'; - this.buildIdpChooserForm(formIDPDiv,payload.IDP,false); - formDiv.appendChild(formIDPDiv); - } - - this.replaceDiv("signin-div",formDiv,true); - - document.getElementById("back-to-login-btn").onclick = function () { - self.sdk.initAuthentication(); - }; - - } // this.displayPassWordForm - - // This form is displayed for user name first flow. - this.displayIDPChooserForm = function(payload) { - const self = this; - - // if a single IDP exists, then redirect to that IDP - if (payload.IDP.configuredIDPs.length == 1) { - var idp = payload.IDP.configuredIDPs[0]; - var singleIDPPayload = { - 'requestState': self.getRequestState(), - 'idpName': idp.idpName, - 'idpId': idp.idpId, - 'clientId': self.getClientId(), - 'idpType': idp.idpType - }; - self.sdk.chooseIDP(singleIDPPayload); - } - else { - var formDiv = document.createElement('div'); - formDiv.classList.add("form"); - formDiv.classList.add("sign-in"); - formDiv.id = 'signin-div'; - formDiv.innerHTML = - '

Choose your IDP

'; - - this.replaceDiv("signin-div",formDiv,true); - - this.buildIdpChooserForm(formDiv,payload.IDP,false); - } - } - // Displays a form for forgotPassword flow with username as input. - this.displayForgotPassWordForm = function(payload) { + this.displayForgotPassWordForm = function (payload) { const self = this; var formDiv = document.createElement('div'); @@ -1473,14 +1490,14 @@ this.removeBtnAndShowSpinner = function(btn) { formDiv.classList.add("sign-in"); formDiv.id = 'signin-div'; formDiv.innerHTML = - '

Forgot your Password?

' + - '
Please, enter username for password reset.
' + - '' + - '' + - ''; + '

Forgot your Password?

' + + '
Please, enter username for password reset.
' + + '' + + '' + + ''; - this.handleClickToSubmitEvent(formDiv,this,'getPasswordMethod'); - this.handleKeyPressToSubmitEvent(formDiv,formDiv.querySelector("#forgotUserName"),this,'getPasswordMethod'); + this.handleClickToSubmitEvent(formDiv, this, 'forgotPassword'); + this.handleKeyPressToSubmitEvent(formDiv, formDiv.querySelector("#forgotUserName"), this, 'forgotPassword'); var backToLoginDivElem = document.createElement('div'); backToLoginDivElem.classList.add('newline'); @@ -1488,7 +1505,7 @@ this.removeBtnAndShowSpinner = function(btn) { backToLoginDivElem.innerHTML += 'Back to Login'; formDiv.appendChild(backToLoginDivElem); - this.replaceDiv("signin-div",formDiv,true); + this.replaceDiv("signin-div", formDiv, true); document.getElementById("back-to-login-btn").onclick = function () { self.sdk.initAuthentication(); @@ -1496,179 +1513,24 @@ this.removeBtnAndShowSpinner = function(btn) { } // this.displayForgotPassWordForm - // Displays a form for notification type in forgot password flow - email or sms. - this.displayForgotPassWordMethodForm = function(payload, username) { - - const self = this; - var formDiv = document.createElement('div'); - formDiv.classList.add("form"); - formDiv.classList.add("sign-in"); - formDiv.id = 'signin-div'; - formDiv.innerHTML = - '

Forgot your Password?

' + - '
Please select the method for password reset:
' + - ''; - - for (i=0; i < payload.options.length; i++) { // TODO: Fix the radio button layout - var currentType = payload.options[i].type; - if (currentType === "email" ) { - formDiv.innerHTML += - ''; - } - if (i == 0) { - formDiv.innerHTML += - '' + - ''; - } - else { - formDiv.innerHTML += - '' + - ''; - } - } - - formDiv.innerHTML += - '' + - ''; - - this.handleClickToSubmitEvent(formDiv,this,'forgotPassword'); - - for (i=0; i < payload.options.length; i++) { - var currentType = payload.options[i].type; - this.handleKeyPressToSubmitEvent(formDiv,formDiv.querySelector("#" + currentType),this,'forgotPassword'); - } - - var backToLoginDivElem = document.createElement('div'); - backToLoginDivElem.classList.add('newline'); - backToLoginDivElem.innerHTML = '

OR
'; - backToLoginDivElem.innerHTML += 'Back to Login'; - formDiv.appendChild(backToLoginDivElem); - - this.replaceDiv("signin-div",formDiv,true); - - document.getElementById("back-to-login-btn").onclick = function () { - self.sdk.initAuthentication(); - }; - - } // this.displayForgotPassWordMethodForm - - // Displays a form for notification type in forgot password flow - sms. - this.displayForgotPassWordSmsForm = function(payload, username) { - this.logMsg("Building Forgot Password SMS Form"); - const self = this; - var formDiv = document.createElement('div'); - formDiv.classList.add("form"); - formDiv.classList.add("sign-in"); - formDiv.id = 'signin-div'; - formDiv.innerHTML = - '

Forgot your Password?

' + - '' + - '' + - '' + - '' + - '' + - ''; - - this.handleClickToSubmitEvent(formDiv,this,'processPasswordMethod'); - this.handleKeyPressToSubmitEvent(formDiv,formDiv.querySelector("#smsCode"),this,'processPasswordMethod'); - - var backToLoginDivElem = document.createElement('div'); - backToLoginDivElem.classList.add('newline'); - backToLoginDivElem.innerHTML = '

OR
'; - backToLoginDivElem.innerHTML += 'Back to Login'; - formDiv.appendChild(backToLoginDivElem); - - this.replaceDiv("signin-div",formDiv,true); - - document.getElementById("back-to-login-btn").onclick = function () { - self.sdk.initAuthentication(); - }; - } // this.displayForgotPassWordSmsForm - - // Displays a form for notification type in forgot password flow - secquestsion. - this.displayForgotPassWordSecquestionForm = function(payload, username) { - this.logMsg("Building Forgot Password Secquestion Form"); - const self = this; - var formDiv = document.createElement('div'); - formDiv.classList.add("form"); - formDiv.classList.add("sign-in"); - formDiv.id = 'signin-div'; - formDiv.innerHTML = - '

Forgot your Password?

' + - '
' + payload.secquestions[0].localizedQuestionText + '
' + - '' + - '' + - '' + - '' + - ''; - - this.handleClickToSubmitEvent(formDiv,this,'processPasswordMethod'); - this.handleKeyPressToSubmitEvent(formDiv,formDiv.querySelector("#secAnswer"),this,'processPasswordMethod'); - - var backToLoginDivElem = document.createElement('div'); - backToLoginDivElem.classList.add('newline'); - backToLoginDivElem.innerHTML = '

OR
'; - backToLoginDivElem.innerHTML += 'Back to Login'; - formDiv.appendChild(backToLoginDivElem); - - this.replaceDiv("signin-div",formDiv,true); - - document.getElementById("back-to-login-btn").onclick = function () { - self.sdk.initAuthentication(); - }; - } // this.displayForgotPassWordSecquestionForm - - // Displays a form for notification type in forgot password flow - email. - this.displayForgotPassWordEmailForm = function(payload, username) { - this.logMsg("Building Forgot Password EMail Form"); - const self = this; - var formDiv = document.createElement('div'); - formDiv.classList.add("form"); - formDiv.classList.add("sign-in"); - formDiv.id = 'signin-div'; - formDiv.innerHTML = - '

Forgot your Password?

' + - '
Please enter the token you recieved via E-Mail:
' + - '' + - '' + - '' + - ''; - - this.handleClickToSubmitEvent(formDiv,this,'processPasswordMethod'); - this.handleKeyPressToSubmitEvent(formDiv,formDiv.querySelector("#emailToken"),this,'processPasswordMethod'); - - var backToLoginDivElem = document.createElement('div'); - backToLoginDivElem.classList.add('newline'); - backToLoginDivElem.innerHTML = '

OR
'; - backToLoginDivElem.innerHTML += 'Back to Login'; - formDiv.appendChild(backToLoginDivElem); - - this.replaceDiv("signin-div",formDiv,true); - - document.getElementById("back-to-login-btn").onclick = function () { - self.sdk.initAuthentication(); - }; - - } // this.displayForgotPassWordEmailForm - // Displays forgotPassword Success - this.displayForgotPassWordSuccess = function(payload, username) { + this.displayForgotPassWordSuccess = function (payload) { var formDiv = document.createElement('div'); formDiv.classList.add("form"); formDiv.classList.add("sign-in"); formDiv.id = 'signin-div'; formDiv.whichForm = "FORGOT_PASSWORD_FORM"; formDiv.innerHTML = - '

Forgot your Password?

' + - '
An email with reset password instructions has been sent for username: '+username+'
' + - '' + - ''; + '

Forgot your Password?

' + + '
An email with reset password instructions has been sent for username: ' + payload.userName + '
' + + '' + + ''; - this.replaceDiv("signin-div",formDiv,true); - } // this.displayForgotPassWordSuccess + this.replaceDiv("signin-div", formDiv, true); + } // this.displayForgotPassWordForm // Builds the Reset Password form, Post validation of usertoken - this.displayResetPassWordForm = function(usertoken) { + this.displayResetPassWordForm = function (usertoken) { this.logMsg("Building Reset Password Form"); this.shortlived_token = usertoken; @@ -1682,17 +1544,17 @@ this.removeBtnAndShowSpinner = function(btn) { '' + '' + '' + - '' ; + ''; - this.handleClickToSubmitEvent(formDiv,this,'resetPassword'); - this.handleKeyPressToSubmitEvent(formDiv,formDiv.querySelector("#confresetPassword"),this,'resetPassword'); + this.handleClickToSubmitEvent(formDiv, this, 'resetPassword'); + this.handleKeyPressToSubmitEvent(formDiv, formDiv.querySelector("#confresetPassword"), this, 'resetPassword'); - this.replaceDiv("signin-div",formDiv,true); + this.replaceDiv("signin-div", formDiv, true); } // this.displayResetPassWordForm // Builds the Reset Password Success Page, Post validation of password policies and resetting user password - this.displayResetPassWordSuccess = function() { + this.displayResetPassWordSuccess = function () { this.logMsg("Building Reset Password Success page"); var formDiv = document.createElement('div'); @@ -1702,110 +1564,110 @@ this.removeBtnAndShowSpinner = function(btn) { formDiv.innerHTML += '

Reset Password

' + '
' + - 'Your password has been successfully reset.' + - 'You can close this window.' + + 'Your password has been successfully reset.' + + 'You can close this window.' + '
' + - '' ; + ''; - this.replaceDiv("signin-div",formDiv,true); + this.replaceDiv("signin-div", formDiv, true); } // this.displayResetPassWordSuccess // This method works as the app main controller, directing requests to the appropriate methods based on the received payload from IDCS. - this.nextOperation = function(payload) { - + this.nextOperation = function (payload) { this.logMsg("nextOperation: " + this.mask(payload)); if (payload.requestState && payload.nextOp) { - this.setRequestState(payload.requestState); if (payload.nextOp[0] === 'credSubmit') { if (payload.nextAuthFactors) { - - if (payload.nextAuthFactors.includes('USERNAME_PASSWORD')) { - this.displayPasswordForm(payload); + var sameFactorMultipleDevices = false; + payload.nextAuthFactors.forEach(function (factor) { + if (payload[factor] && payload[factor].enrolledDevices && payload[factor].enrolledDevices.length > 0) { + sameFactorMultipleDevices = true; + } + }); + // Fix on bug reported by Pulkit Agarwal on 12/04/18. Used to happen when MFA is active for a Social User that isn't registered in IDCS. + // We must send the user to enrollment where enrollment is also in nextOp array. + if (payload.nextOp[1] === "enrollment") { + this.sdk.getEnrollmentFactors(); + } + // End of fix. + // If there's more than one nextAuthFactor or multiple devices for the same factor, we go to alternative factors flow. + else if (payload.nextAuthFactors.length > 1 && payload.nextAuthFactors.includes("USERNAME") && payload.nextOp.includes("credSubmit")) { + if (!this.getUnPwOrigin() || this.getUnPwOrigin() === "true") { + this.setPreferredFactor({ factor: payload.nextAuthFactors[0], displayName: payload.displayName }); + this.setUnPwOrigin("false"); + } + this.displayForm(payload.nextAuthFactors[0], "submitCreds", payload); } - else if (payload.nextAuthFactors.includes('IDP')) { - this.displayIDPChooserForm(payload); + else if (payload.nextAuthFactors.length > 1 || sameFactorMultipleDevices) { + this.displayAltFactorsSubform(payload); } else { - var sameFactorMultipleDevices = false; - payload.nextAuthFactors.forEach(function(factor) { - if (payload[factor] && payload[factor].enrolledDevices && payload[factor].enrolledDevices.length > 0) { - sameFactorMultipleDevices = true; - } - }); - // Fix on bug reported by Pulkit Agarwal on 12/04/18. Used to happen when MFA is active for a Social User that isn't registered in IDCS. - // We must send the user to enrollment where enrollment is also in nextOp array. - if ( payload.nextOp[1] === "enrollment" ) { - this.displayEnrollmentOptionsForm(payload); - } - // End of fix. - // If there's more than one nextAuthFactor or multiple devices for the same factor, we go to alternative factors flow. - else if (payload.nextAuthFactors.length > 1 || sameFactorMultipleDevices) { - this.displayAltFactorsSubform(payload); - } - - else { - // ER #1 - // Doing this because the API response not always tell whether the factor is the preferred one. - // Setting the user preferred factor. It's the one returned from username/password submit. - // We may also come here via Social Login, in which case the origin is undefined. - if (!this.getUnPwOrigin() || this.getUnPwOrigin() === "true") { - this.setPreferredFactor({factor:payload.nextAuthFactors[0],displayName:payload.displayName}); - this.setUnPwOrigin("false"); - } - // End of ER #1. - this.displayForm(payload.nextAuthFactors[0],"submitCreds",payload); + // ER #1 + // Doing this because the API response not always tell whether the factor is the preferred one. + // Setting the user preferred factor. It's the one returned from username/password submit. + // We may also come here via Social Login, in which case the origin is undefined. + if (!this.getUnPwOrigin() || this.getUnPwOrigin() === "true") { + this.setPreferredFactor({ factor: payload.nextAuthFactors[0], displayName: payload.displayName }); + this.setUnPwOrigin("false"); } + // End of ER #1. + this.displayForm(payload.nextAuthFactors[0], "submitCreds", payload); } } else - if (payload.nextOp.indexOf('enrollment') == -1) { // Alternative factors case - which = Object.keys(self.AuthenticationFactorInfo).filter( function(x) { return x in payload;}); - this.logMsg('nextOperation: Factor is ' + which[0]); - - if (typeof which[0] === 'undefined') { // PUSH alternative factor case, when there's no 'PUSH' in the payload. - if (payload.status === 'pending' && payload.cause && payload.cause[0].code === 'AUTH-1108') { - let signinDiv = document.getElementById("signin-div"); - if (signinDiv && signinDiv.whichFactorForm === "PUSH") { - self.logMsg( signinDiv.whichFactorForm + " is active"); - this.logMsg('Waiting on ' + signinDiv.whichFactorForm + '[submitCreds] with payload ' + this.mask(payload)); + if (payload.nextOp.indexOf('enrollment') == -1) { // Alternative factors case + which = Object.keys(self.AuthenticationFactorInfo).filter(function (x) { return x in payload; }); + this.logMsg('nextOperation: Factor is ' + which[0]); + + if (typeof which[0] === 'undefined') { // PUSH alternative factor case, when there's no 'PUSH' in the payload. + if (payload.status === 'pending' && payload.cause && payload.cause[0].code === 'AUTH-1108') { + let signinDiv = document.getElementById("signin-div"); + if (signinDiv && signinDiv.whichFactorForm === "PUSH") { + self.logMsg(signinDiv.whichFactorForm + " is active"); + this.logMsg('Waiting on ' + signinDiv.whichFactorForm + '[submitCreds] with payload ' + this.mask(payload)); + } + else { + this.logMsg('About to display form for PUSH [submitCreds] with payload ' + this.mask(payload)); + this.displayForm("PUSH", "submitCreds", payload); + } + } + } + else { + this.logMsg('About to display form for ' + which[0] + '[submitCreds] with payload ' + this.mask(payload)); + if (which[0] === "EMAIL" && sessionStorage.getItem("userNameFirst")) { + sessionStorage.removeItem("userNameFirst") } else { - this.logMsg('About to display form for PUSH [submitCreds] with payload ' + this.mask(payload)); - this.displayForm("PUSH","submitCreds",payload); + this.displayForm(which[0], "submitCreds", payload); } } - } - else { - this.logMsg('About to display form for ' + which[0] + '[submitCreds] with payload ' + this.mask(payload)); - this.displayForm(which[0],"submitCreds",payload); - } - } - else - if (payload.EMAIL || payload.SECURITY_QUESTIONS || payload.PUSH || payload.TOTP) { - which = Object.keys(self.AuthenticationFactorInfo).filter( function(x) { return x in payload;}); - this.logMsg('which[0] is ' + which[0]); - this.displayForm(which[0],"enrollment",payload); - } - else - if ( payload.nextOp[1] === "enrollment" ) { - which = Object.keys(self.AuthenticationFactorInfo).filter( function(x) { return x in payload;}); - this.displayForm(which[0],"enrollment",payload); - } - else - if (payload.SMS && payload.SMS.credentials[0] === "otpCode") { - which = Object.keys(self.AuthenticationFactorInfo).filter( function(x) { return x in payload;}); - this.logMsg('which[0] is ' + which[0]); - this.displayForm(which[0],"enrollment",payload); - } - else { - this.logMsg('Do not know what to do with given payload.'); - } + } + else + if (payload.EMAIL || payload.SECURITY_QUESTIONS || payload.PUSH || payload.TOTP || payload.FIDO_AUTHENTICATOR) { + which = Object.keys(self.AuthenticationFactorInfo).filter(function (x) { return x in payload; }); + this.logMsg('which[0] is ' + which[0]); + this.displayForm(which[0], "enrollment", payload); + } + else + if (payload.nextOp[1] === "enrollment") { + which = Object.keys(self.AuthenticationFactorInfo).filter(function (x) { return x in payload; }); + this.displayForm(which[0], "enrollment", payload); + } + else + if (payload.SMS && payload.SMS.credentials[0] === "otpCode") { + which = Object.keys(self.AuthenticationFactorInfo).filter(function (x) { return x in payload; }); + this.logMsg('which[0] is ' + which[0]); + this.displayForm(which[0], "enrollment", payload); + } + else { + this.logMsg('Do not know what to do with given payload.'); + } } // Fix on bug reported by Pulkit Agarwal on 12/04/18. Used to happen when there was only one MFA method active. // Added the check payload.nextOp.indexOf('createToken') >= 0 below. @@ -1818,7 +1680,7 @@ this.removeBtnAndShowSpinner = function(btn) { } } else if (payload.nextOp[0] === 'acceptTOU') { - this.displayForm('TOU',"submitCreds",payload); + this.displayForm('TOU', "submitCreds", payload); } else { this.logMsg('Do not know what to do with given payload.'); @@ -1830,32 +1692,32 @@ this.removeBtnAndShowSpinner = function(btn) { -------------------------------------------- HELPER METHODS ---------------------------------------------------- ---------------------------------------------------------------------------------------------------------------- */ - this.addErrorDetailsIfAny = function(errorElem, details) { + this.addErrorDetailsIfAny = function (errorElem, details) { if (details != null) { var detailsDiv = document.createElement('div'); detailsDiv.classList.add('newline'); - for (i=0; i < details.length; i++) { + for (i = 0; i < details.length; i++) { detailsDiv.innerHTML += '' + details[i] + ''; } errorElem.appendChild(detailsDiv); } } - this.handleBackendError = function(error) { + this.handleBackendError = function (error) { var errorMsg = ''; if (error) { errorMsg = error.msg; if (error.code.indexOf('AUTH-1120') != -1) { - errorMsg = this.localizeMsg('error-AUTH-1120','Invalid state. Please, reinitiate login'); + errorMsg = this.localizeMsg('error-AUTH-1120', 'Invalid state. Please, reinitiate login'); } - else if (error.code.indexOf('AUTH-1112') != -1) { - errorMsg = this.localizeMsg('error-AUTH-1112','Access denied'); + else if (error.code.indexOf('AUTH-1112') != -1) { + errorMsg = this.localizeMsg('error-AUTH-1112', 'Access denied'); } else if (error.code.indexOf('SDK-AUTH') != -1) { - errorMsg = this.localizeMsg('error-' + error.code,error.msg); + errorMsg = this.localizeMsg('error-' + error.code, error.msg); } else if (error.code.indexOf('SSO-') != -1 && error.msg === 'undefined') { - errorMsg = this.localizeMsg('error-' + error.code,''); + errorMsg = this.localizeMsg('error-' + error.code, ''); } else { this.logMsg('Passing backend error message as is: ' + errorMsg); @@ -1864,15 +1726,14 @@ this.removeBtnAndShowSpinner = function(btn) { return errorMsg; } - - this.changeButtonOnError = function(button) { + this.changeButtonOnError = function (button) { if (button) { button.style.display = 'block'; button.disabled = false; } } - this.clearErrorsOnScreenIfAny = function() { + this.clearErrorsOnScreenIfAny = function () { var socialErrorElem = document.getElementById("social-login-error-msg"); if (socialErrorElem) { socialErrorElem.innerHTML = ''; @@ -1883,7 +1744,7 @@ this.removeBtnAndShowSpinner = function(btn) { } } - this.setLoginErrorMessage = function(error) { + this.setLoginErrorMessage = function (error) { this.clearErrorsOnScreenIfAny(); @@ -1918,7 +1779,7 @@ this.removeBtnAndShowSpinner = function(btn) { } } - this.getBackendErrorMsg = function() { + this.getBackendErrorMsg = function () { var error = sessionStorage.getItem('backendError'); // This is set by the server-side backend if (error) { sessionStorage.removeItem('backendError'); @@ -1927,30 +1788,30 @@ this.removeBtnAndShowSpinner = function(btn) { return; } - this.setAccessToken = function(at) { + this.setAccessToken = function (at) { return sessionStorage.setItem("signinAT", at); } - this.getAccessToken = function() { + this.getAccessToken = function () { return sessionStorage.getItem("signinAT"); } - this.isIDPUserInIDCS = function() { + this.isIDPUserInIDCS = function () { return sessionStorage.getItem("isIDPUserInIDCS"); } - this.getIDPAuthnToken = function() { + this.getIDPAuthnToken = function () { return sessionStorage.getItem("IDPAuthnToken"); } - this.getSocialData = function(){ + this.getSocialData = function () { var socialData = {}; socialData.requestState = this.getRequestState(); socialData.userData = JSON.parse(sessionStorage.getItem('social.scimUserAttrs')); return socialData; }; - this.isSocialRegistrationRequired = function() { + this.isSocialRegistrationRequired = function () { var isRequired = sessionStorage.getItem("social.needToRegister"); if (isRequired && isRequired === 'true') { return true; @@ -1959,136 +1820,146 @@ this.removeBtnAndShowSpinner = function(btn) { } }; - this.removeSocialData = function() { + this.removeSocialData = function () { sessionStorage.removeItem('social.scimUserAttrs'); sessionStorage.removeItem('social.needToRegister'); } - this.setRequestState = function(rs) { + this.getLoginCtx = function () { + return sessionStorage.getItem("initialState"); + } + + this.setRequestState = function (rs) { sessionStorage.setItem("requestState", rs); } - this.getRequestState = function() { + this.getRequestState = function () { return sessionStorage.getItem("requestState"); } - this.getClientId = function() { + this.getClientId = function () { return sessionStorage.getItem("clientId"); } - this.getInitialState = function() { - return sessionStorage.getItem("initialState"); + this.getInitialState = function () { + return sessionStorage.getItem("initialState"); } - this.setTrustedDeviceOption = function(trusted) { + this.setTrustedDeviceOption = function (trusted) { sessionStorage.setItem("isDeviceTrusted", trusted); } - this.getTrustedDeviceOption = function() { + this.getTrustedDeviceOption = function () { return sessionStorage.getItem("isDeviceTrusted"); } // Issue #1 // The following six methods were introduced to support the preferred device feature. - this.setPreferredFactor = function(factor) { + this.setPreferredFactor = function (factor) { sessionStorage.setItem("preferredFactor", JSON.stringify(factor)); } - this.getPreferredFactor = function() { + this.getPreferredFactor = function () { return JSON.parse(sessionStorage.getItem("preferredFactor")); } // This captures the device object (containing deviceId and displayName) selected by the user. - this.setSelectedDevice = function(device) { + this.setSelectedDevice = function (device) { this.logMsg('Setting selectedDevice: ' + JSON.stringify(device)); sessionStorage.setItem("device", JSON.stringify(device)); } - this.getSelectedDevice = function() { + this.getSelectedDevice = function () { var selectedDevice = sessionStorage.getItem("device") this.logMsg('Getting selectedDevice: ' + selectedDevice); try { return JSON.parse(selectedDevice); } - catch(e) { + catch (e) { return null; } } // As of IDCS 18.4.2, the preferred factor is the factor returned as a result of successful username/password submit. // So it must be stored and retrieved at a later time. - this.setUnPwOrigin = function(flag) { + this.setUnPwOrigin = function (flag) { sessionStorage.setItem("unPwOrigin", flag) } - this.getUnPwOrigin = function() { + this.getUnPwOrigin = function () { return sessionStorage.getItem("unPwOrigin"); } // End of Issue #1. // This object is used mostly by method displayForm, telling it which form to build. - const self=this; + const self = this; this.AuthenticationFactorInfo = { USERNAME_PASSWORD: { // this one is only used for the initial login screen label: "Username and password", - loginFormFunction: function (formdiv,payload) { self.buildUidPwForm(formdiv,payload);}, + loginFormFunction: function (formdiv, payload) { self.buildUidPwForm(formdiv, payload); }, }, USERNAME: { - // this one is only used for the initial login screen label: "Username", - loginFormFunction: function (formdiv,payload) { self.buildUidForm(formdiv,payload);}, + loginFormFunction: function (formdiv, payload) { self.buildUidPwForm(formdiv, { 'payload': payload, 'userNameFirst': true }); }, }, IDP: { // If the admin removes "local IDP" in the IDP Policies then IDCS asks custom login app // to display only the IDP chooser on the intiial form label: "Select an IDP", - loginFormFunction: function (formdiv,payload) { self.buildIdpChooserForm(formdiv,payload.IDP,true);}, + loginFormFunction: function (formdiv, payload) { self.buildIdpChooserForm(formdiv, payload.IDP); }, }, EMAIL: { label: "Email", description: "Send an email with a code to use", - initEnrollFormFunction: function () { self.displayForm("spinner"), self.sdk.initEnrollOtpEmail();}, - enrollFormFunction: function (formDiv,payload) { self.buildOtpEmailEnrollmentForm(formDiv,payload);}, - loginFormFunction: function (formdiv,payload) { self.buildEmailOtpForm(formdiv,payload);}, + initEnrollFormFunction: function () { self.displayForm("spinner"), self.sdk.initEnrollOtpEmail(); }, + enrollFormFunction: function (formDiv, payload) { self.buildOtpEmailEnrollmentForm(formDiv, payload); }, + loginFormFunction: function (formdiv, payload) { self.buildEmailOtpForm(formdiv, payload); }, + }, + FIDO_AUTHENTICATOR: { + label: "Fido Authenticator", + description: "Fido Authentication", + initEnrollFormFunction: function () { self.displayForm("spinner"), self.sdk.initEnrollFido(); }, + enrollFormFunction: function (formDiv, payload) { self.buildFidoEnrollmentForm(formDiv, payload); }, + loginFormFunction: function (formdiv, payload) { self.buildFidoAuthenticationForm(formdiv, payload); }, }, SECURITY_QUESTIONS: { label: "Security Questions", description: "Security question and answers", - initEnrollFormFunction: function () { self.displayForm("spinner"), self.sdk.initEnrollSecurityQuestions();}, - enrollFormFunction: function (formDiv,payload) { self.buildSecurityQuestionsEnrollmentForm(formDiv,payload);}, - loginFormFunction: function (formdiv,payload) { self.buildSecQuestionsForm(formdiv,payload);}, + initEnrollFormFunction: function () { self.displayForm("spinner"), self.sdk.initEnrollSecurityQuestions(); }, + enrollFormFunction: function (formDiv, payload) { self.buildSecurityQuestionsEnrollmentForm(formDiv, payload); }, + loginFormFunction: function (formdiv, payload) { self.buildSecQuestionsForm(formdiv, payload); }, }, SMS: { label: "SMS", description: "SMS to Mobile Number", - enrollFormFunction: function (formDiv,payload) { self.buildSMSMobileEnrollmentForm(formDiv,payload);}, - loginFormFunction: function (formdiv,payload) { self.buildSmsOtpForm(formdiv,payload);}, + enrollFormFunction: function (formDiv, payload) { self.buildSMSMobileEnrollmentForm(formDiv, payload); }, + loginFormFunction: function (formdiv, payload) { self.buildSmsOtpForm(formdiv, payload); }, }, PUSH: { label: "Oracle Authenticator App", description: "Pop-up on the Oracle Mobile Authenticator", - initEnrollFormFunction: function () { self.displayForm("spinner"), self.sdk.initEnrollPush();}, - enrollFormFunction: function (formDiv,payload) { self.buildPushEnrollForm(formDiv,payload);}, - loginFormFunction: function (formdiv,payload) { self.buildPushLoginForm(formdiv,payload);}, + initEnrollFormFunction: function () { self.displayForm("spinner"), self.sdk.initEnrollPush(); }, + enrollFormFunction: function (formDiv, payload) { self.buildPushEnrollForm(formDiv, payload); }, + loginFormFunction: function (formdiv, payload) { self.buildPushLoginForm(formdiv, payload); }, }, TOTP: { label: "Time-based OTP", description: "OTP code from the Oracle Mobile Authenticator or another TOTP app.", - initEnrollFormFunction: function () { self.displayForm("spinner"), self.sdk.initEnrollOtpTotp();}, + initEnrollFormFunction: function () { self.displayForm("spinner"), self.sdk.initEnrollOtpTotp(); }, // this is not a mistake - buildTOTPForm does both enroll and regular - enrollFormFunction: function (formDiv,payload) { self.buildTOTPForm(formDiv,payload);}, - loginFormFunction: function (formdiv,payload,device) { self.buildTOTPForm(formdiv,payload);}, + enrollFormFunction: function (formDiv, payload) { self.buildTOTPForm(formDiv, payload); }, + loginFormFunction: function (formdiv, payload, device) { self.buildTOTPForm(formdiv, payload); }, }, BYPASSCODE: { label: "Bypass code", description: "Use a security bypass code from your list", - loginFormFunction: function (formdiv,payload) { self.buildBypasscodeLoginForm(formdiv,payload);}, + loginFormFunction: function (formdiv, payload) { self.buildBypasscodeLoginForm(formdiv, payload); }, }, TOU: { label: "Terms of Use", description: "Terms of Use", - loginFormFunction: function (formdiv,payload) { self.buildTermsForm(formdiv,payload);}, + loginFormFunction: function (formdiv, payload) { self.buildTermsForm(formdiv, payload); }, } // If/when enginering adds a new factor begin by copying and then uncommenting this block // NEW_ONE: { @@ -2102,20 +1973,21 @@ this.removeBtnAndShowSpinner = function(btn) { // } } - this.getOperation = function() { + this.getOperation = function () { return sessionStorage.getItem("operation"); } - this.getToken = function() { + this.getToken = function () { return decodeURIComponent(sessionStorage.getItem("token")); } - this.ToBeImplemented = function(which) { - alert( "Case " + which + " needs to be implemented!"); + this.ToBeImplemented = function (which) { + alert("Case " + which + " needs to be implemented!"); } this.sdk = new IdcsAuthnSDK(this); this.sdk.initAuthentication(); + }; // function loginApp const loginApp = new LoginApp(); diff --git a/idcs-authn-api-signin-app/run.sh b/idcs-authn-api-signin-app/run.sh index 0f39795..8269fb9 100644 --- a/idcs-authn-api-signin-app/run.sh +++ b/idcs-authn-api-signin-app/run.sh @@ -6,6 +6,7 @@ export IDCS_URL=https://MYTENNANT.identity.oraclecloud.com export IDCS_CLIENT_ID=1234567890abcdef1234567890abcdef export IDCS_CLIENT_SECRET=12345678-abcd-1234-abcd-123456789abc +export NODE_TLS_REJECT_UNAUTHORIZED=0 # if you want to use the app to do self registration uncomment line # and include the ID of a self registration profile here: