@opengovsg/mockpass
Version:
A mock SingPass/CorpPass server for dev purposes
841 lines (774 loc) • 27.6 kB
JavaScript
/**
* Global JS variables related to Login Tab
*/
var qrCodeTab = "#qrcodeloginli";
var loginTab = "#loginli";
/**
* Global JS variables related to password login
*/
var singPassID = "#loginID";
var password= "#password";
var captca = "#jCaptcha";
var captcaImg = "#logincap";
var loginBlock = "#LoginForm";
var logincaptchaBlock = "#loginc";
var loginerrorMessageBlock= "#errorMessage";
var captchaErrorMsgBlock= "#captchaErrMsg";
/**
* Global JS variables related to QR Code
*/
var hasScanned = "has-scanned";
var isExpired = "is-expired";
var cantGen = "cant-gen";
var isUnavailable = "is-unavailable";
var isSuspended = "is-suspended";
var isLocked = "is-locked";
var isQrCodeGenerated = false;
var qrCodeInitStartTime = 0;
/*******************************************************************************
*SOFT TOKEN RELATED METHODS START
******************************************************************************/
//this javascript method is used for app link for web kit singpass in esevice login
function initAppLauncher(spmurl) {
NativeAppLauncher.init({
appLauncherElId: 'qrcodelink', // Element Id of App Launcher button.
notSupportedMessage: 'Sorry, QR Login is not compatible with this browser. Please try another.', // Defaults to 'Not Supported!'
universalLinkUrl: spmurl,
appUri: 'https',
appDeepUri: 'spm',
androidAppId: 'sg.ndi.sp',
iOsAppStore: 'https://itunes.apple.com/app/singpass-mobile/id1340660807?ls=1&mt=8',
debug: false // Optional
});
}
/**
* This method will start the listener for 2 minutes.After 2 minutes.The
* listener expires.
*
* @param succesUrl
* @param logoutUrl
* @returns
*/
function startTwofaPushNotifResponseListener(succesUrl) {
$.ajax({
url : "/spauth/tfa/userpnreslistener",
type : "GET",
cache: false,
complete : function(response) {
var responseObj = response.responseJSON;
if (responseObj.threadCounter < 2){
startTwofaPushNotifResponseListener(succesUrl);
} else if (responseObj.listenerStatus === "SUCCESS") {
window.location.href = succesUrl;
}
},
error : function(xhr, status, error) {
startTwofaPushNotifResponseListener(succesUrl);
}
})
};
/**
* This method is execute the scanned listener via ajax call.
*
*
* @returns
*/
function startQRCodeScannedResponseListener() {
$.ajax({
url : spauthContextPath + "/login/qrscannedlistener",
type : "GET",
cache: false,
global: false,
complete : function(response) {
var responseObj = response.responseJSON;
// check if the request is timeout and validate the no of times the thread has been called for same transaction id
if (responseObj.listenerStatus === "SUCCESS" && responseObj.userStatus == null) {
if (browserLogin !== 'DESKTOP') {
if (document.visibilityState === 'visible') {
startQRCodeAcknowledgedResponseListener();
$('.qr__wrapper').addClass(hasScanned);
} else {
document.addEventListener("visibilitychange", function() {
if (document.visibilityState === 'visible') {
startQRCodeAcknowledgedResponseListener();
$('.qr__wrapper').addClass(hasScanned);
}
});
}
} else {
startQRCodeAcknowledgedResponseListener();
$('.qr__wrapper').addClass(hasScanned);
}
} else if (responseObj.listenerStatus === 'RETRY') {
if (browserLogin !== 'DESKTOP') {
if (document.visibilityState === 'visible') {
startQRCodeScannedResponseListener();
} else {
document.addEventListener("visibilitychange", function() {
if (document.visibilityState === 'visible') {
startQRCodeScannedResponseListener();
}
});
}
} else {
startQRCodeScannedResponseListener();
}
} else if (responseObj.listenerStatus === "ERROR") {
$('.qr__wrapper').addClass(cantGen);
} else if (responseObj.listenerStatus === "EXPIRED") {
$('.qr__wrapper').addClass(isExpired);
} else if (responseObj.userStatus === "SPCP004D" || responseObj.userStatus === "SPCP004E") {
$('.qr__wrapper').addClass(isLocked);
} else if (responseObj.userStatus === "SPCP003" || responseObj.userStatus === "SPCP004A" || responseObj.userStatus === "SPCP004B" || responseObj.userStatus === "SPCP004C" || responseObj.userStatus === "SPCP005" || responseObj.userStatus === "SPCP006") {
$('.qr__wrapper').addClass(isSuspended);
}
},
error : function(xhr, status, error) {
startQRCodeScannedResponseListener();
}
});
}
/**
* This method is execute the acknowledged listener via ajax call clean the previous
* active the scanned and acknowledged listener is session storage
*
* @returns
*/
function startQRCodeAcknowledgedResponseListener() {
var form = $("#qrcodelogin");
$.ajax({
url : spauthContextPath + "/login/qracknowledgedlistener",
type : "POST",
cache: false,
global: false,
data: $(form).serialize() + "&wogaaid=" + initialiseWogaaId(),
complete : function(response) {
var responseObj = response.responseJSON;
if (responseObj.listenerStatus === "SUCCESS") {
setCookie("tabId","qrcodetab");
// to send wogaa request
if (!isSingpassHome) {
sendWogaaRequest(responseObj.wogaaUrl, responseObj.wogaaMessage);
}
// to redirect to given page
doPostRequest("verifyqrcodeauth");
} else if (responseObj.listenerStatus === 'RETRY') {
if (browserLogin !== 'DESKTOP') {
if (document.visibilityState === 'visible') {
startQRCodeAcknowledgedResponseListener();
} else {
document.addEventListener("visibilitychange", function() {
if (document.visibilityState === 'visible') {
startQRCodeAcknowledgedResponseListener();
}
});
}
} else {
startQRCodeAcknowledgedResponseListener();
}
} else if (responseObj.listenerStatus === "ERROR") {
$('.qr__wrapper').addClass(cantGen);
} else if (responseObj.listenerStatus === "EXPIRED") {
$('.qr__wrapper').addClass(isExpired);
}
},
error : function(xhr, status, error) {
startQRCodeAcknowledgedResponseListener();
}
});
}
function refreshQRCode() {
isQrCodeGenerated = false;
generateQRCode();
}
/**
* This method generate the QR code.
* Page will reload when the session has time out.
*
* @returns
*/
function generateQRCode() {
if (isQrCodeGenerated) {
return;
} else {
$('.qr__wrapper').removeClass(cantGen);
$('.qr__wrapper').removeClass(isExpired);
$('.qr__wrapper').removeClass(isLocked);
$('.qr__wrapper').removeClass(isSuspended);
$('.qr__wrapper').removeClass(hasScanned);
$.ajax({
url : spauthContextPath + "/login/generateqrcode",
type : "POST",
cache: false,
success : function(response) {
var responseObj = response;
if (responseObj.error === 136) {
if (isSingpassHome === true) {
doPostRequest("/spauth/login/logout");
// To refresh the page so that user can successfully login
window.location.reload();
} else {
window.location.replace("/spauth/login/eservicelogout");
}
} else if (responseObj.qrcode_byte != null) {
$('#qrcodelink').addClass('flip').delay(500).queue(function(){
initAppLauncher(responseObj.spm_url);
$('#qrImage').attr("src", "data:image/png;base64," + responseObj.qrcode_byte);
$('#qrcodelink').removeClass('flip').dequeue();
});
isQrCodeGenerated = true;
startQRCodeScannedResponseListener();
qrCodeInitStartTime = Date.now();
} else if (responseObj.qrcode_is_unavailable) {
$('.qr__wrapper').addClass(isUnavailable);
} else {
$('.qr__wrapper').addClass(cantGen);
}
},
error: function(XMLHttpRequest, textStatus, errorThrown) {
$('.qr__wrapper').addClass(cantGen);
}
});
}
}
/**
* Helper method to open new tab upon clicking on qr code
* Close the window after one second ( for user login experience )
* @param url
* @returns
*/
function redirectToSingPassMobile(url) {
var spmwindow = window.open(url, "_blank");
}
/**
* This method will get the QR Validity Time (value for loading screen timeout)
*
* @return time in milliseconds that will be used for setting the timeoutQRLoadingScreen global variable.
* Note that negative values will skip loading screen.
*/
function getQRValidityTime() {
var timeoutQRLoadingScreen = 120000 - (Date.now() - qrCodeInitStartTime);
if (timeoutQRLoadingScreen == 0) {
timeoutQRLoadingScreen = -1;
}
return timeoutQRLoadingScreen;
}
/*******************************************************************************
*SOFT TOKEN RELATED METHODS ENDS
******************************************************************************/
/*******************************************************************************
*CAPTCHA RELATED METHODS STARTS
******************************************************************************/
/**
* This method is called when user clicked cancel button in captcha page
*
* @param divIdToHide
* @param divIdToDisplay
*/
function doCancelCaptcha(divToHide, divToDisplay) {
$(divToHide).hide();
$(divToDisplay).show();
$("#jCaptcha").val("");
}
/**
* This method is called when user Singpass ID / Password retries is more than 3
* times.
*
* @param flow
* @param captchId
* @param hideList
* @param showList
* @param captchUrl
* @returns
*/
function isUserRetriesReachedMax(captchId, hideList, showList, captchUrl) {
showElements(showList);
hideElements(hideList);
$(captchId).attr("src", captchUrl);
}
/**
* This method is called when user entered invalid captcha
* @param flow
* @param captchId
* @param errorMessageId
* @param clearList
* @param hideList
* @param showList
* @param captchUrl
* @returns
*/
function invalCap(captchId, errorMessageId, clearList, hideList, showList, captchUrl){
clear(clearList);
showElements(showList);
$(captchId).attr("src", captchUrl);
$(errorMessageId).text("Incorrect code. Please try again.");
}
/*******************************************************************************
*CAPTCHA RELATED METHODS ENDS
******************************************************************************/
/*******************************************************************************
* COMMON LOGIN RELATED METHODS STARTS
******************************************************************************/
/**
* This is common method to show the elements based on the idlist given as
* parameter
*
* @param {List}
* IdList
* @returns
*/
function showElements(IdList) {
var len = IdList.length;
for (i = 0; i < len; i++) {
$(IdList[i]).show();
}
}
/**
* This is common method to hide the elements based on the idlist given as
* parameter
*
* @param IdList
* @returns
*/
function hideElements(IdList) {
var len = IdList.length;
for (i = 0; i < len; i++) {
$(IdList[i]).hide();
}
}
/**
* This is common method to clear the values based on idlist given as the
* parameter
*
* @param IdList
* @returns
*/
function clear(IdList) {
var len = IdList.length;
for (i = 0; i < len; i++) {
$(IdList[i]).val("");
}
}
/**
* This is method that trim the value with value given in the parameter
* @param x
* @returns x after trim.
*/
function myTrim(x) {
return x.replace(/(^[ \t]*\n)/gm, "");
}
/**
* This method listens to changes in userID entry to redirect user when id is clicked
*/
function redirectID() {
const idInput = document.getElementById("id-input");
const optionsList = document.getElementById("id-datalist");
let optionsMap = new Map();
for (let i=0; i<optionsList.options.length; i++) {
optionsMap.set(optionsList.options[i].value, optionsList.options[i].dataset.asserturl);
}
if (optionsMap.has(idInput.value)) {
const assertURL = optionsMap.get(idInput.value);
window.location.href = assertURL;
}
}
/**
* This is generic method called for onkeypress action.
*
* @param e
* @param action
* @param lflow
* @returns
*/
function doKeyPress(e, action) {
var keynum;
if (window.event) { // IE
keynum = e.keyCode;
} else if (e.which) { // Netscape/Firefox/Opera
keynum = e.which;
}
if (keynum == 13) {
if (action == 'LOGIN' || action == 'captcha') {
doSubmit(action);
}
}
return;
}
function getCookie(key) {
var keyValue = document.cookie.match('(^|;) ?' + key + '=([^;]*)(;|$)');
return keyValue ? keyValue[2] : null;
}
function setRememberTab(){
var tabId = getCookie("tabId");
return tabId;
}
function setCookie(key, value) {
var expires = new Date();
expires.setTime(expires.getTime() + (1 * 24 * 60 * 60 * 1000));
document.cookie = key + '=' + value + ';expires=' + expires.toUTCString();
}
/**
* This is generic method to show qr code tab.
*/
function showQRCodeTab() {
$(qrCodeTab).addClass('active');
$('#sectionB').addClass('active');
$(loginTab).removeClass('active');
$('#sectionA').removeClass('active');
generateQRCode();
}
/**
* This is generic method to show login tab.
*/
function showLoginTab() {
$(qrCodeTab).removeClass('active');
$('#sectionB').removeClass('active');
$(loginTab).addClass('active');
$('#sectionA').addClass('active in');
}
function showLoadTab(){
var tabId = setRememberTab();
if (tabId == 'qrcodetab'){
showQRCodeTab();
} else {
showLoginTab();
}
toggleQRTooltip();
}
/**
* Shows QR Tooltip if login tab is active
*/
function toggleQRTooltip() {
if(!$(qrCodeTab).hasClass('active')) {
$('#sp-mobile-tooltip').show();
} else {
$('#sp-mobile-tooltip').hide();
}
}
/*
* Sets the modal top position to be placed just below the mobile-header
*/
function setModalTopPos() {
var modalTopPos = $('#mobile-header').position().top + $('#mobile-header').outerHeight();
modalTopPos > 0 ? $('#myModalHorizontal').find('.homepageLogin.modal-dialog').css('top', modalTopPos+'px') : $('#myModalHorizontal').find('.homepageLogin.modal-dialog').attr('style', '');
}
/**
* This method is to check if the user password is eight character.
* @param password
* @returns
*/
function isEightChar(password) {
if (password.length == 8) {
return true;
}
}
/**
* This method is called when user clicked cancel button.
*
* @param URL
* @returns
*/
function doCancel(URL) {
window.location = URL;
}
/**
* This method is to validate singPassID and Password entered is empty
* @param userId
* @param password
* @param errorDivId
* @returns false if values is is empty
*/
function validateUserIdPassword(userId, password, errorDivId){
if (userId.length == 0 && password.length == 0) {
$(errorDivId).css("display", "block");
$(errorDivId).text("Please enter your SingPass ID and Password");
return false;
}
return true;
}
/**
* Method to validate the mandatory fields
*/
function validateMandatoryFields(actionType) {
var userId = $("#loginID").val();
var password = $("#password").val();
var captchaVal = $("#jCaptcha").val();
if (actionType === 'LOGIN' && userId.length == 0 && password.length == 0) {
document.getElementById('errorMessage').style.display = "block";
document.getElementById('errorMessage').innerHTML = "Please enter your SingPass ID and Password";
return false;
} else if (actionType === 'LOGIN' && userId.length == 0) {
$('#password').val("");
document.getElementById('errorMessage').style.display = "block";
document.getElementById('errorMessage').innerHTML = "Please enter your SingPass ID.";
return false;
} else if (actionType === 'LOGIN' && password.length == 0) {
document.getElementById('errorMessage').style.display = "block";
document.getElementById('errorMessage').innerHTML = "Please enter your SingPass password.";
return false;
} else if (actionType === 'captcha' && captchaVal.length == 0) {
$('#captchaErrMsg').show();
$('#captchaErrMsg').text("Enter the code shown above.");
return false;
}
return true;
}
/**
* This method is to validate singPassID is empty or not
*
* @param userId
* @param errorDivId
* @param errorMessage
* @returns false if values is is empty
*/
function validateUserId(userId, errorDivId, errorMessage,hideErrorDiv) {
if (userId.length == 0) {
$(errorDivId).css("display", "block");
$(errorDivId).text(errorMessage);
$("#plLockedErrorMessage").hide();
return false;
}
return true;
}
/**
* This method is validate password is empty or not
*
* @param password
* @param errorDivId
* @param errorMessage
* @returns if values is is empty
*/
function validatePassword(password, errorDivId, errorMessage) {
if (password.length == 0) {
$(errorDivId).css("display", "block");
$(errorDivId).text(errorMessage);
$("#plpLockedErrorMessage").hide();
return false;
}
return true;
}
/**
* This method to check the mandatory validation
* @param userId
* @param password
* @param errorDivId
* @param errorMessage
* @param pErrorMessage
* @returns
*/
function mandatoryValidation(userId, password, errorDivId, errorMessage, pErrorMessage){
var validation = validateUserIdPassword(userId, password, errorDivId);
if(validation){
validation = validateUserId(userId, errorDivId, errorMessage);
if(validation){
validation = validatePassword(password, errorDivId, pErrorMessage);
}
}
return validation;
}
/**
* This is a common methods used to set all the rba related details
*
* @param flow
* @param obj
* @param data
* @param modulussec
* @param deviceDetId
* @param encryptedRbaDeviceId
* @param rbaDeviceParamId
* @returns
*/
function setRBAData(obj, data, modulussec, deviceDetId, encryptedRbaDeviceId, rbaDeviceParamId) {
var jsonString;
var encryptedRbaDevice;
var rbaDeviceParam2;
try {
var Exponent = obj.EXPONENT;
var Modulus = obj.RSA_PUBLIC_KEY;
var randomString16 = obj.RANDOM_STRING_16;
var rsaBlock = encryptVerifyNoUserRSABlock256(Exponent, Modulus, data,randomString16);
jsonString = JSON.stringify(jsonObj);
} catch (e) {
//doNothing
}
$(deviceDetId).val(jsonString);
}
/**
* This is a common method used to set all the randoms details for password encyrpt.
* @param obj
* @param password
* @param randomString16Id
* @param randomString32Id
* @param randomString64Id
* @param rsaBlockId
* @param rsaBlock1Id
* @param rsaBlock2Id
* @returns
*/
function setRamdoms(obj, password, randomString16Id, randomString32Id, randomString64Id, rsaBlockId, rsaBlock1Id, rsaBlock2Id) {
var Exponent = obj.EXPONENT;
var Modulus = obj.RSA_PUBLIC_KEY;
var randomString16 = obj.RANDOM_STRING_16;
var randomString32 = obj.RANDOM_STRING_32;
var randomString64 = obj.RANDOM_STRING_64;
var rsaBlock = encryptVerifyNoUserRSABlock256(Exponent, Modulus, password,randomString16);
var rsaBlock1 = encryptMigratePwdNoVerifyNoUser256RSABlock512(Exponent,Modulus, password, randomString32, randomString64);
var rsaBlock2 = encryptVerifyStaticNoUserRSABlock512(Exponent, Modulus,password, randomString64);
$(randomString16Id).val(randomString16);
$(randomString32Id).val(randomString32);
$(randomString64Id).val(randomString64);
$(rsaBlockId).val(rsaBlock);
$(rsaBlock1Id).val(rsaBlock1);
$(rsaBlock2Id).val(rsaBlock2);
}
/**
* This method is to hide and show list when user is invalid user
*
* @param flow
* @param errorMessageId
* @param clearList
* @param hideList
* @param showList
* @param message
* @returns
*/
function invalUsr(flow, errorMessageId, clearList, hideList, showList, message) {
clear(clearList);
hideElements(hideList);
showElements(showList);
$(errorMessageId).text(message);
}
/**
* This method is to hide and show list when user is locked/suspend/terminated user
* @param flow
* @param errorMessageId
* @param clearList
* @param hideList
* @param showList
* @param message
* @returns
*/
function commErr(flow, errorMessageId, clearList, hideList, showList, message) {
clear(clearList);
hideElements(hideList);
showElements(showList);
$(errorMessageId).text(message);
}
/**
* This method hide and show login and captcha form elements based on the error message return.
* @param errorMessage
* @returns
*/
function invalidLoginAction(errorMessage, captchaVal) {
if (errorMessage == 'invalUsr') {
// Login Form
$("#LoginForm").show();
$('#loginID').val("");
$('#password').val("");
$('#errorMessage').show();
$('#errorMessage').text("You have entered an invalid SingPass ID or Password.");
// Captcha Form
$("#loginc").hide();
$("#jCaptcha").val("");
$('#captchaErrMsg').hide();
} else if (errorMessage == 'commErr') {
// Login Form
$("#LoginForm").show();
$('#loginID').val("");
$('#password').val("");
$('#errorMessage').show();
$('#errorMessage').text("We are unable to verify your account. Please reset your password. Alternatively, you can contact the SingPass helpdesk for more information.");
// Captcha Form
$("#loginc").hide();
$("#jCaptcha").val("");
$('#captchaErrMsg').hide();
} else if (errorMessage == 'isUserRetriesReachedMax') {
// Login Form
$("#LoginForm").hide();
$('#errorMessage').hide();
// Captcha Form
$("#loginc").show();
$("#jCaptcha").val("");
$('#captchaErrMsg').hide();
$('#logincap').attr('src', captchaVal);
} else if (errorMessage == 'invalCap') {
// Login Form
$("#LoginForm").hide();
$('#errorMessage').hide();
// Captcha Form
$("#loginc").show();
$("#jCaptcha").val("");
$('#captchaErrMsg').show();
$('#captchaErrMsg').text("Incorrect code. Please try again.");
$('#logincap').attr('src', captchaVal);
} else if (errorMessage == 'notActiveIrasUsr') {
$("#LoginForm").show();
$('#loginID').val("");
$('#password').val("");
$('#errorMessage').show();
$('#errorMessage').html("Please set up the <a href='https://singpassmobile.sg/' target='_blank'>SingPass Mobile app</a> on your mobile device to access SingPass or IRAS Digital Services");
// Captcha Form
$("#loginc").hide();
$("#jCaptcha").val("");
$('#captchaErrMsg').hide();
} else {
$("#LoginForm").show();
$('#loginID').val("");
$('#password').val("");
$('#errorMessage').show();
$('#errorMessage').html(errorMessage);
// Captcha Form
$("#loginc").hide();
$("#jCaptcha").val("");
$('#captchaErrMsg').hide();
}
}
/*******************************************************************************
* WOGAA RELATED METHODS STARTS
******************************************************************************/
/**
* This method will initialise WOGAA ID.
*
* @return wogaaId - WOGAA ID.
*/
function initialiseWogaaId() {
var wogaaId = "";
try {
wogaaId = _satellite.getVisitorId().getMarketingCloudVisitorID();
if (wogaaId == null || wogaaId == undefined) {
wogaaId = "";
}
} catch (error) {
wogaaId = error.message;
}
return wogaaId;
}
/**
* This method will send a POST request to WOGAA.
*
* @param wogaaUrl
* contains WOGAA API URL.
* @param wogaaMessage
* contains a string to be sent to WOGAA.
*/
function sendWogaaRequest(wogaaUrl, wogaaMessage) {
try {
if (wogaaUrl !== null && wogaaUrl !== undefined) {
var wogaaRequest = new XMLHttpRequest();
wogaaRequest.open("POST", wogaaUrl, true);
wogaaRequest.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
wogaaRequest.onerror = function(error) {
// do nothing
}
wogaaRequest.send(wogaaMessage);
}
} catch (error) {
// do nothing
}
}
/*******************************************************************************
* WOGAA RELATED METHODS ENDS
******************************************************************************/
/*******************************************************************************
* COMMON LOGIN RELATED METHODS ENDS
******************************************************************************/