+ 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 @@
+
+
+
+