diff --git a/build.sh b/build.sh index 163bf82..4ee2650 100755 --- a/build.sh +++ b/build.sh @@ -15,6 +15,10 @@ perl -pi -e "s/\{\{AUTH0DOMAIN\}\}/$AUTH0DOMAIN/g" $SIGNUPFILENAME CHECKEMAIL="./web-assets/static-pages/check_email.html" perl -pi -e "s/\{\{DOMAIN\}\}/$DOMAIN/g" $CHECKEMAIL +OTPFILENAME="./web-assets/js/otp.js" +perl -pi -e "s/\{\{DOMAIN\}\}/$DOMAIN/g" $OTPFILENAME +perl -pi -e "s/\{\{AUTH0DOMAIN\}\}/$AUTH0DOMAIN/g" $OTPFILENAME + mkdir dist cp -rv ./web-assets/css/* ./dist/ cp -rv ./web-assets/js/* ./dist/ diff --git a/web-assets/auth0/dev-tenant/rules/DICE DID.js b/web-assets/auth0/dev-tenant/rules/DICE DID.js index 700d06d..107214e 100644 --- a/web-assets/auth0/dev-tenant/rules/DICE DID.js +++ b/web-assets/auth0/dev-tenant/rules/DICE DID.js @@ -21,7 +21,7 @@ function (user, context, callback) { // User was redirected to the /continue endpoint console.log("rule:DICE DID:User was redirected to the /continue endpoint"); if (context.request.query.diceVerificationStatus === 'false') { - return callback('Login Error: Whoops! Something went wrong. Please connect to DICE Platform Admin dice.wallet@wipro.com.
Back to application ', user, context); + return callback('Login Error: Credentials verification is failed.
Please contact with support support@topcoder.com.
Back to application ', user, context); } else if (context.request.query.otp) { request.post({ url: 'https://api.' + configuration.DOMAIN + '/v3/users/checkOtp', @@ -82,8 +82,14 @@ function (user, context, callback) { return callback('Login Error: Whoops! Something went wrong.', user, context); } console.log("rule:DICE DID: redirecting to OTP page"); + const hostName = _.get(context, "request.hostname", null); + const otpCompletetUrl = "https://" + hostName + "/continue"; + const retUrl = _.get(context, "request.query.returnUrl", null); + const otpRedirectUrl = configuration.CUSTOM_PAGES_BASE_URL + + "/otp.html?formAction=" + otpCompletetUrl + + "&returnUrl=" + retUrl; context.redirect = { - url: `https://accounts-auth0.${configuration.DOMAIN}/check_email.html` + url: otpRedirectUrl }; return callback(null, user, context); }); diff --git a/web-assets/auth0/prod-tenant/database/login.js b/web-assets/auth0/prod-tenant/database/login.js index 2b336eb..9041e92 100644 --- a/web-assets/auth0/prod-tenant/database/login.js +++ b/web-assets/auth0/prod-tenant/database/login.js @@ -1,6 +1,6 @@ function login(handleOrEmail, password, callback) { -request.post({ - url: "https://api."+configuration.DOMAIN+"/v3/users/login", + request.post({ + url: "https://api." + configuration.DOMAIN + "/v3/users/login", form: { handleOrEmail: handleOrEmail, password: password @@ -12,16 +12,19 @@ request.post({ if (err) return callback(err); if (response.statusCode === 401) return callback(); var user = JSON.parse(body); - user.result.content.roles = user.result.content.roles.map(function(role) { + user.result.content.roles = user.result.content.roles.map(function (role) { return role.roleName; }); - callback(null, { + callback(null, { user_id: user.result.content.id, nickname: user.result.content.handle, email: user.result.content.email, roles: user.result.content.roles, email_verified: user.result.content.emailActive, + created_at: user.result.content.createdAt, + mfa_enabled: user.result.content.mfaEnabled, + mfa_verified: user.result.content.mfaVerified }); }); -} +} \ No newline at end of file diff --git a/web-assets/auth0/prod-tenant/rules/DICE DID.js b/web-assets/auth0/prod-tenant/rules/DICE DID.js new file mode 100644 index 0000000..18bb878 --- /dev/null +++ b/web-assets/auth0/prod-tenant/rules/DICE DID.js @@ -0,0 +1,130 @@ +function (user, context, callback) { + if (context.clientID === configuration.CLIENT_ACCOUNTS_LOGIN) { + console.log("rule:DICE DID:enter"); + if (context.redirect) { + console.log("rule:DICE DID:exiting due to context being a redirect"); + return callback(null, user, context); + } + const _ = require('lodash'); + const isAuth0 = (_.get(user, "identities[0].provider") === 'auth0') ? true : false; + const isSocial = _.get(user, "identities[0].isSocial"); + const mfaEnabled = _.get(user, "mfa_enabled", false); + const mfaVerified = _.get(user, "mfa_verified", false); + if (!isAuth0 && !isSocial) { + console.log("rule:DICE DID:exiting due to enterprise user"); + return callback(null, user, context); + } + if (mfaEnabled && mfaVerified) { + if (context.protocol === "redirect-callback") { + // User was redirected to the /continue endpoint + console.log("rule:DICE DID:User was redirected to the /continue endpoint"); + if (context.request.query.diceVerificationStatus === 'false') { + return callback('Login Error: Credentials verification is failed.
Please contact with support support@topcoder.com.
Back to application ', user, context); + } else if (context.request.query.otp) { + request.post({ + url: 'https://api.' + configuration.DOMAIN + '/v3/users/checkOtp', + json: { + "param": { + "userId": user.userId, + "otp": context.request.query.otp + } + } + }, function (error, response, body) { + if (error) return callback(error, user, context); + if (response.statusCode !== 200) { + return callback('Login Error: Whoops! Something went wrong.', user, context); + } + if (body.result.content.verified === true) { + return callback(null, user, context); + } else { + return callback('Login Error: wrong OTP', user, context); + } + }); + } else { + const jwt_decode = require('jwt-decode'); + request.post({ + url: 'https://tc-vcauth.diceid.com/vc/connect/token', + form: { + code: context.request.query.code, + grant_type: 'authorization_code', + client_id: 'topcoder' + } + }, function (error, response, body) { + if (error) return callback(error, user, context); + if (response.statusCode !== 200) { + return callback('Login Error: Whoops! Something went wrong.', user, context); + } + const result = JSON.parse(body); + const decoded = jwt_decode(result.id_token); + console.log("Decoded: ", decoded); + if (decoded.Email !== user.email) { + return callback('Login Error: Credetials do not match', user, context); + } + console.log("rule:DICE DID:credentials approved"); + return callback(null, user, context); + }); + } + } else { + const maxRetry = 2; + const useOtp = function () { + request.post({ + url: 'https://api.' + configuration.DOMAIN + '/v3/users/sendOtp', + json: { + "param": { + "userId": user.userId + } + } + }, function (error, response, body) { + if (error) return callback(error, user, context); + if (response.statusCode !== 200) { + return callback('Login Error: Whoops! Something went wrong.', user, context); + } + console.log("rule:DICE DID: redirecting to OTP page"); + const hostName = _.get(context, "request.hostname", null); + const otpCompletetUrl = "https://" + hostName + "/continue"; + const retUrl = _.get(context, "request.query.returnUrl", null); + const otpRedirectUrl = configuration.CUSTOM_PAGES_BASE_URL + + "/otp.html?formAction=" + otpCompletetUrl + + "&returnUrl=" + retUrl; + context.redirect = { + url: otpRedirectUrl + }; + return callback(null, user, context); + }); + }; + const checkDiceHealth = function (attempt) { + console.log("rule:DICE DID:checking dice health, attempt:" + attempt); + request.get({ + url: 'https://tc-vcauth.diceid.com/.well-known/openid-configuration' + }, function (error, response, body) { + if (error || response.statusCode !== 200) { + if (attempt >= maxRetry) { + console.log("rule:DICE DID:dice services down, using otp flow..."); + useOtp(); + } else { + checkDiceHealth(attempt + 1); + } + } else { + console.log("rule:DICE DID:exiting with redirecting user to QR code page."); + context.redirect = { + url: `https://tc-vcauth.diceid.com/vc/connect/authorize?pres_req_conf_id=Topcoder_2FA&client_id=topcoder&redirect_uri=https%3A%2F%2Fauth.topcoder.com%2Fcontinue&response_type=code&scope=openid%20profile%20vc_authn` + }; + return callback(null, user, context); + } + }); + }; + if (!global.ENABLE_2FA) { + console.log("rule:DICE DID:dice switch disabled, using otp flow..."); + useOtp(); + } else { + checkDiceHealth(1); + } + } + } else { + console.log("rule:DICE DID:exiting due to mfa is not enabled"); + return callback(null, user, context); + } + } else { + return callback(null, user, context); + } +} \ No newline at end of file diff --git a/web-assets/auth0/prod-tenant/rules/DisableDID.js b/web-assets/auth0/prod-tenant/rules/DisableDID.js new file mode 100644 index 0000000..dd7e055 --- /dev/null +++ b/web-assets/auth0/prod-tenant/rules/DisableDID.js @@ -0,0 +1,5 @@ +function (user, context, callback) { + // Set the Enable_2FA to false globally - safety switch to turn off + global.ENABLE_2FA = false; + return callback(null, user, context); +} \ No newline at end of file diff --git a/web-assets/auth0/prod-tenant/rules/Global variables and functions.js b/web-assets/auth0/prod-tenant/rules/Global variables and functions.js new file mode 100644 index 0000000..cfcd6b4 --- /dev/null +++ b/web-assets/auth0/prod-tenant/rules/Global variables and functions.js @@ -0,0 +1,9 @@ +function (user, context, callback) { + if (!global.init) { + global.AUTH0_CLAIM_NAMESPACE = "https://topcoder.com/"; + global.CUSTOM_CLAIMS_PROTOCOLS = ['oauth2-refresh-token', 'oauth2-device-code', 'oidc-basic-profile']; + global.ENABLE_2FA = true; + global.init = true; + } + callback(null, user, context); +} \ No newline at end of file diff --git a/web-assets/auth0/prod-tenant/rules/custom.js b/web-assets/auth0/prod-tenant/rules/New-Account-App-Custom-Claims.js similarity index 76% rename from web-assets/auth0/prod-tenant/rules/custom.js rename to web-assets/auth0/prod-tenant/rules/New-Account-App-Custom-Claims.js index e51dd7a..bca4f7c 100644 --- a/web-assets/auth0/prod-tenant/rules/custom.js +++ b/web-assets/auth0/prod-tenant/rules/New-Account-App-Custom-Claims.js @@ -31,8 +31,12 @@ function (user, context, callback) { } let res = JSON.parse(body); + // Assign MFA flags to user objects - PLAT-1420 + user.mfa_enabled = res.result.content.mfaEnabled; + user.mfa_verified = res.result.content.mfaVerified; // TODO need to double sure about multiple result or no result let userId = res.result.content.id; + user.userId = userId; let handle = res.result.content.handle; let roles = res.result.content.roles.map(function (role) { return role.roleName; @@ -42,11 +46,11 @@ function (user, context, callback) { // TEMP let tcsso = res.result.content.regSource || ''; - // block wipro/topgear contractor user - const topgearBlockMessage = 'Topgear can be accessed only by Wipro Employees. If you are a Wipro employee and not able to access, drop an email to ask.topgear@wipro.com with the error message.Back to application '; - if (roles.indexOf(configuration.TOPGEAR_CONTRACTOR_ROLE) > -1) { - return callback(topgearBlockMessage, user, context); - } + // block wipro/topgear contractor user + const topgearBlockMessage = 'Topgear can be accessed only by Wipro Employees. If you are a Wipro employee and not able to access, drop an email to ask.topgear@wipro.com with the error message.Back to application '; + if (roles.indexOf(configuration.TOPGEAR_CONTRACTOR_ROLE) > -1) { + return callback(topgearBlockMessage, user, context); + } context.idToken[global.AUTH0_CLAIM_NAMESPACE + 'roles'] = roles; context.idToken[global.AUTH0_CLAIM_NAMESPACE + 'userId'] = userId; @@ -55,14 +59,14 @@ function (user, context, callback) { context.idToken[global.AUTH0_CLAIM_NAMESPACE + 'tcsso'] = tcsso; context.idToken[global.AUTH0_CLAIM_NAMESPACE + 'active'] = userStatus; context.idToken.nickname = handle; - - if (!userStatus) { - context.redirect = { - url: `https://accounts-auth0.${configuration.DOMAIN}/check_email.html` - }; - return callback(null, user, context); + + if (!userStatus) { + context.redirect = { + url: `https://accounts-auth0.${configuration.DOMAIN}/check_email.html` + }; + return callback(null, user, context); } - + //console.log(user, context); return callback(null, user, context); } diff --git a/web-assets/js/otp.js b/web-assets/js/otp.js new file mode 100644 index 0000000..c3f2853 --- /dev/null +++ b/web-assets/js/otp.js @@ -0,0 +1,92 @@ +var qs = (function (a) { + if (a == "") return {}; + var b = {}; + for (var i = 0; i < a.length; ++i) { + var p = a[i].split("=", 2); + if (p.length == 1) b[p[0]] = ""; + else b[p[0]] = decodeURIComponent(p[1].replace(/\+/g, " ")); + } + return b; +})(window.location.search.substr(1).split("&")); +$(document).ready(function () { + window.history.forward(); + $("#continueBtn").click(function () { + var otp = $("#otp").val(); + if (!otp) { + $("#error").html("Need Password"); + $("#error").closest(".message").fadeIn(); + return false; + } + $("#error").closest(".message").fadeOut(); + $("#error").html(""); + let formAction = qs["formAction"]; + console.log(formAction) + const opt1 = 'https://auth.{{DOMAIN}}/continue'; + const opt2 = 'https://{{AUTH0DOMAIN}}/continue'; + if (!formAction.startsWith(opt1) && !formAction.startsWith(opt2)) { + // looks like XSS attack + console.log("err") + formAction = "#"; + } + $('#verifyOtp').attr('action', formAction); + $("#state").val(qs["state"]); + $("#returnUrl").val(qs["returnUrl"]); + $("#verifyOtp").submit(); + return false; + }); + + /** + * Script for field placeholder + **/ + $(".messages .close-error").on("click", function () { + $(this).closest(".message").fadeOut(); + }); + var inputObj = $(".input-field .input-text"), + continueBtnDisable = false; + inputObj + .on("focus", function () { + $(this).parent().addClass("active focussed"); + }) + .on("blur", function () { + var parentObj = $(this).parent(); + if ($(this).val() === "") { + parentObj.removeClass("active"); + } + parentObj.removeClass("focussed"); + }) + .on("change keydown paste input", function () { + var disableStatus = false; + inputObj.each(function (index, element) { + if ($(element).val() === "") { + disableStatus = true; + return; + } else { + disableStatus = false; + return; + } + }); + setContinueButtonDisabledStatus(disableStatus); + }) + .each(function (index, element) { + var parentObj = $(element).parent(); + if ($(element).val() !== "") { + parentObj.addClass("active"); + } else { + parentObj.removeClass("active"); + } + + if ($(element).val() === "" && continueBtnDisable === false) { + continueBtnDisable = true; + } + + setContinueButtonDisabledStatus(continueBtnDisable); + }); +}); +function setContinueButtonDisabledStatus(status) { + var continueBtnObj = $("#continueBtn"); + if (status) { + continueBtnObj.attr("disabled", true); + } else { + continueBtnObj.removeAttr("disabled"); + } +} diff --git a/web-assets/static-pages/otp.html b/web-assets/static-pages/otp.html new file mode 100644 index 0000000..c09c518 --- /dev/null +++ b/web-assets/static-pages/otp.html @@ -0,0 +1,70 @@ + + + + + Check your email + + + + + + + + + + + + + +
+ + + +
+
+ Check Email +
+
+ +

+ Check your email
and enter your password +

+
+ +
+
+ + +
+ + +
+
+ +
+
+
+
+ +
+ + + + \ No newline at end of file