@nibssplc/cams-sdk
Version:
Central Authentication Management Service (CAMS) SDK for popup-based authentication with Azure AD + custom 2FA
811 lines (802 loc) • 31.8 kB
JavaScript
;
var zod = require('zod');
exports.CAMSErrorType = void 0;
(function (CAMSErrorType) {
CAMSErrorType["POPUP_BLOCKED"] = "POPUP_BLOCKED";
CAMSErrorType["TIMEOUT"] = "TIMEOUT";
CAMSErrorType["INVALID_ORIGIN"] = "INVALID_ORIGIN";
CAMSErrorType["USER_CANCELLED"] = "USER_CANCELLED";
CAMSErrorType["INVALID_URL"] = "INVALID_URL";
CAMSErrorType["MAX_RETRIES_EXCEEDED"] = "MAX_RETRIES_EXCEEDED";
CAMSErrorType["API_VALIDATION_ERROR"] = "VALIDATION_ERROR";
})(exports.CAMSErrorType || (exports.CAMSErrorType = {}));
class CAMSError extends Error {
type;
constructor(type, message) {
super(message);
this.type = type;
this.name = this.constructor.name;
}
}
// Validate the returned token - non-empty string
const ProfileSchema = zod.z.object({
type: zod.z.literal("AUTH_SUCCESS").or(zod.z.literal("AUTH_FAILURE")),
userProfile: zod.z.object({
name: zod.z.string(),
email: zod.z.email(),
mfaIsEnabled: zod.z.boolean(),
message: zod.z.string(),
isAuthenticated: zod.z.boolean(),
userType: zod.z.enum(["SUPER_ADMIN", "ADMIN", "USER"]),
roles: zod.z.array(zod.z.string()).optional(),
tokens: zod.z.object({
Nonce: zod.z.string(),
Bearer: zod.z.string(),
}),
}),
});
const ErrorMessageSchema = zod.z.object({
error: zod.z.string().min(1, "Error message cannot be empty"),
});
const AuthFailureMessageSchema = zod.z.object({
type: zod.z.literal("AUTH_FAILURE"),
error: zod.z.string().min(1, "Error message cannot be empty"),
errorCode: zod.z.string(),
errorDetails: zod.z.object().or(zod.z.any()),
});
const URLSchema = zod.z.url();
exports.LogLevel = void 0;
(function (LogLevel) {
LogLevel[LogLevel["ERROR"] = 0] = "ERROR";
LogLevel[LogLevel["WARN"] = 1] = "WARN";
LogLevel[LogLevel["INFO"] = 2] = "INFO";
LogLevel[LogLevel["DEBUG"] = 3] = "DEBUG";
})(exports.LogLevel || (exports.LogLevel = {}));
class Logger {
static level = exports.LogLevel.DEBUG;
static prefix = '[CAMS-SDK]';
static isTestEnv = typeof process !== 'undefined' && process.env && process.env.NODE_ENV === 'test';
static setLevel(level) {
Logger.level = level;
}
static error(message, context) {
if (!Logger.isTestEnv && Logger.level >= exports.LogLevel.ERROR) {
console.error(`${Logger.prefix} ERROR:`, message, context || '');
}
}
static warn(message, context) {
if (!Logger.isTestEnv && Logger.level >= exports.LogLevel.WARN) {
console.warn(`${Logger.prefix} WARN:`, message, context || '');
}
}
static info(message, context) {
if (!Logger.isTestEnv && Logger.level >= exports.LogLevel.INFO) {
console.info(`${Logger.prefix} INFO:`, message, context || '');
}
}
static debug(message, context) {
if (!Logger.isTestEnv && Logger.level >= exports.LogLevel.DEBUG) {
console.debug(`${Logger.prefix} DEBUG:`, message, context || '');
}
}
}
// Constants
const DEFAULT_AUTH_TIMEOUT = 300000; // 5 minutes
const DEFAULT_IDLE_TIMEOUT = 120000; // 2 minutes
const INITIAL_POLL_INTERVAL = 1000;
const WINDOW_FEATURES = "scrollbars=yes,resizable=yes";
// Default allowed Microsoft domains for Azure AD redirects
const DEFAULT_MICROSOFT_DOMAINS = [
".microsoft.com",
".microsoftonline.com",
".live.com",
"login.microsoftonline.com"
];
// Secure reference to native functions with proper binding
const NativeFunctions = {
addEventListener: typeof window !== "undefined" ? window.addEventListener.bind(window) : null,
removeEventListener: typeof window !== "undefined" ? window.removeEventListener.bind(window) : null,
setTimeout: typeof window !== "undefined" && window.setTimeout ? window.setTimeout.bind(window) : setTimeout,
clearTimeout: typeof window !== "undefined" && window.clearTimeout ? window.clearTimeout.bind(window) : clearTimeout,
};
/**
* Generate a secure random window name to prevent targeting
*/
const generateSecureWindowName = () => `cams_auth_${Math.random().toString(36).slice(2, 9)}_${Date.now()}`;
/**
* Detect if current window is a popup to prevent recursive popups
*/
const isPopupWindow = () => {
if (typeof window === "undefined")
return false;
// Skip popup detection in test environments
if (typeof process !== "undefined" && process.env && process.env.NODE_ENV === "test") {
return false;
}
try {
return window.opener !== null && window.opener !== window;
}
catch {
return false;
}
};
/**
* Safely close a window with error handling for cross-origin restrictions
*/
const safeWindowClose = (window) => {
if (!window)
return;
try {
if (!window.closed) {
window.close();
}
}
catch (error) {
Logger.debug("Error closing window (cross-origin restriction)", {
error: error instanceof Error ? error.message : "Unknown error"
});
}
};
/**
* Center popup window on screen
*/
const getPopupPosition = (width, height) => {
if (typeof window === "undefined")
return { left: 0, top: 0 };
const left = Math.max(0, (window.screen.width - width) / 2);
const top = Math.max(0, (window.screen.height - height) / 2);
return { left, top };
};
/**
* Validate origin against expected domain with support for subdomains and Microsoft redirects
*/
const isValidOrigin = (eventOrigin, expectedOrigin, microsoftDomains = DEFAULT_MICROSOFT_DOMAINS) => {
try {
const originUrl = new URL(eventOrigin);
const expectedHostname = expectedOrigin.startsWith("http")
? new URL(expectedOrigin).hostname
: expectedOrigin;
// Check Microsoft domains for Azure AD redirects
const isMicrosoftDomain = microsoftDomains.some(domain => originUrl.hostname === domain || originUrl.hostname.endsWith(domain));
// Allow exact match, subdomains, or Microsoft domains
return (originUrl.hostname === expectedHostname ||
originUrl.hostname.endsWith("." + expectedHostname) ||
isMicrosoftDomain);
}
catch {
return false;
}
};
/**
* Safely check if window is closed with cross-origin handling
*/
const isWindowClosed = (window) => {
if (!window)
return true;
try {
return window.closed;
}
catch (error) {
// Cross-origin error means we can't access the property
// This typically happens when redirected to external auth providers
Logger.debug("Cannot access window.closed due to cross-origin restrictions - assuming window is still open", error);
return false;
}
};
/**
* Safely add message event listener with error handling
*/
const addMessageListener = (listener) => {
if (!NativeFunctions.addEventListener) {
throw new CAMSError(exports.CAMSErrorType.POPUP_BLOCKED, "Message listener not available in current environment");
}
const wrappedListener = (event) => {
try {
listener(event);
}
catch (error) {
Logger.error("Error in message listener", {
error: error instanceof Error ? error.message : "Unknown error"
});
}
};
NativeFunctions.addEventListener("message", wrappedListener);
return () => {
if (NativeFunctions.removeEventListener) {
NativeFunctions.removeEventListener("message", wrappedListener);
}
};
};
/**
* Validate configuration and return parsed URL
*/
function validateConfig(config) {
const urlValidation = URLSchema.safeParse(config.camsUrl);
if (!urlValidation.success) {
throw new CAMSError(exports.CAMSErrorType.INVALID_URL, "Invalid CAMS URL format");
}
if (!config.messageOrigin) {
throw new CAMSError(exports.CAMSErrorType.INVALID_URL, "Message Origin must be specified for security");
}
return new URL(config.camsUrl);
}
/**
* Open CAMS authentication popup and handle the authentication flow
*/
function openCAMSPopUpLogin(config) {
return new Promise((resolve, reject) => {
// Environment validation
if (typeof window === "undefined") {
reject(new CAMSError(exports.CAMSErrorType.POPUP_BLOCKED, "Cannot open popup in server-side environment"));
return;
}
if (isPopupWindow()) {
Logger.warn("Attempted to open popup from within popup window");
reject(new CAMSError(exports.CAMSErrorType.POPUP_BLOCKED, "Cannot open authentication popup from within popup window"));
return;
}
// Initialize logging
if (config.debug)
Logger.setLevel(exports.LogLevel.DEBUG);
Logger.info("Starting CAMS authentication", { url: config.camsUrl });
// Validate configuration
let validatedUrl;
try {
validatedUrl = validateConfig(config);
Logger.debug("Configuration validation passed");
}
catch (error) {
Logger.error("Configuration validation failed", {
error: error instanceof Error ? error.message : "Unknown error"
});
reject(error);
return;
}
// Extract configuration with defaults
const { messageOrigin, windowHeight = 600, windowWidth = 500, authTimeout = DEFAULT_AUTH_TIMEOUT, idleTimeout = DEFAULT_IDLE_TIMEOUT, allowedRedirectDomains = [...DEFAULT_MICROSOFT_DOMAINS, messageOrigin], } = config;
// State management
let authWindow = null;
let authTimeoutId = null;
let idleTimeoutId = null;
let checkClosedInterval = null;
let removeMessageListener = null;
let hasReceivedValidMessage = false;
/**
* Clean up all resources and timeouts
*/
const cleanup = () => {
if (removeMessageListener) {
removeMessageListener();
removeMessageListener = null;
}
if (idleTimeoutId)
NativeFunctions.clearTimeout(idleTimeoutId);
if (authTimeoutId)
NativeFunctions.clearTimeout(authTimeoutId);
if (checkClosedInterval)
NativeFunctions.clearTimeout(checkClosedInterval);
idleTimeoutId = null;
authTimeoutId = null;
checkClosedInterval = null;
};
/**
* Clean up and close the popup window, then reject with error
*/
const cleanupAndClose = (error) => {
cleanup();
safeWindowClose(authWindow);
if (error) {
reject(error);
}
};
/**
* Reset idle timeout on user activity
*/
const resetIdleTimeout = () => {
if (idleTimeoutId)
NativeFunctions.clearTimeout(idleTimeoutId);
Logger.debug("Resetting idle timeout", { idleTimeout });
idleTimeoutId = NativeFunctions.setTimeout(() => {
Logger.warn("Authentication idle timeout reached", { idleTimeout });
cleanupAndClose(new CAMSError(exports.CAMSErrorType.TIMEOUT, `Authentication timed out due to inactivity after ${idleTimeout}ms`));
}, idleTimeout);
};
/**
* Set up popup window closure detection with cross-origin handling
*/
const setupClosureDetection = () => {
let pollInterval = INITIAL_POLL_INTERVAL;
const checkClosed = () => {
// Skip closure detection when on Microsoft domains
if (authWindow) {
try {
const currentUrl = authWindow.location.href;
const isMicrosoftDomain = allowedRedirectDomains.some(domain => currentUrl.includes(domain));
if (isMicrosoftDomain) {
Logger.debug("Skipping closure check - on Microsoft domain");
checkClosedInterval = NativeFunctions.setTimeout(checkClosed, pollInterval);
return;
}
}
catch (error) {
// Cross-origin error - assume we're on Microsoft domain and skip check
Logger.debug("Cross-origin error - skipping closure check");
checkClosedInterval = NativeFunctions.setTimeout(checkClosed, pollInterval);
return;
}
}
const isClosed = isWindowClosed(authWindow);
if (isClosed && !hasReceivedValidMessage) {
Logger.info("Authentication window closed by user");
cleanupAndClose(new CAMSError(exports.CAMSErrorType.USER_CANCELLED, "Authentication window was closed"));
return;
}
checkClosedInterval = NativeFunctions.setTimeout(checkClosed, pollInterval);
};
checkClosedInterval = NativeFunctions.setTimeout(checkClosed, pollInterval);
};
/**
* Handle incoming message events from the popup
*/
const handleMessage = (event) => {
Logger.debug("Received message", {
origin: event.origin,
expectedOrigin: messageOrigin,
data: event.data,
});
// Security: Validate origin
if (!isValidOrigin(event.origin, messageOrigin, allowedRedirectDomains)) {
Logger.warn("Blocked message from unauthorized origin", {
origin: event.origin,
expected: messageOrigin,
data: event.data,
});
return;
}
// Security: Validate message source
if (event.source !== authWindow) {
Logger.warn("Message from unexpected source", {
source: event.source,
origin: event.origin,
expected: messageOrigin,
data: event.data,
});
return;
}
// Mark that we've received a valid message (prevents false closure detection)
hasReceivedValidMessage = true;
// Handle heartbeat messages (reset idle timeout)
if (event.data?.type === "heartbeat") {
Logger.debug("Received heartbeat message");
resetIdleTimeout();
return;
}
// Process authentication messages
processAuthenticationMessage(event.data);
};
/**
* Process and validate authentication message payloads
*/
const processAuthenticationMessage = (data) => {
// Success case: Valid profile received
const profileValidation = ProfileSchema.safeParse(data);
if (profileValidation.success) {
Logger.info("Authentication successful");
cleanupAndClose();
resolve(profileValidation.data);
return;
}
// Authentication failure case
const authFailureValidation = AuthFailureMessageSchema.safeParse(data);
if (authFailureValidation.success) {
const { error, errorCode, errorDetails } = authFailureValidation.data;
Logger.warn("Authentication failure received", {
error,
errorCode,
errorDetails,
});
cleanupAndClose(new CAMSError(exports.CAMSErrorType.API_VALIDATION_ERROR, `Authentication failed: ${error}${errorCode ? ` (${errorCode})` : ''}`));
return;
}
// Generic error case
const errorValidation = ErrorMessageSchema.safeParse(data);
if (errorValidation.success) {
Logger.warn("Authentication error received", {
error: errorValidation.data.error,
});
cleanupAndClose(new CAMSError(exports.CAMSErrorType.USER_CANCELLED, `Authentication failed: ${errorValidation.data.error}`));
return;
}
// Invalid message format
Logger.error("Invalid message format received", {
messageData: data,
dataType: typeof data,
profileValidationError: profileValidation.error?.issues,
authFailureValidationError: authFailureValidation.error?.issues,
errorValidationError: errorValidation.error?.issues,
});
cleanupAndClose(new CAMSError(exports.CAMSErrorType.INVALID_ORIGIN, "Invalid message format"));
};
// Main authentication flow execution
try {
// Open popup window
const { left, top } = getPopupPosition(windowWidth, windowHeight);
const secureWindowName = generateSecureWindowName();
Logger.debug("Opening popup window", {
width: windowWidth,
height: windowHeight,
left,
top,
});
authWindow = window.open(validatedUrl.toString(), secureWindowName, `width=${windowWidth},height=${windowHeight},left=${left},top=${top},${WINDOW_FEATURES}`);
// Check for popup blocking
if (!authWindow) {
Logger.error("Popup window blocked or failed to open");
reject(new CAMSError(exports.CAMSErrorType.POPUP_BLOCKED, "Popup blocked by browser. Please allow popups and try again."));
return;
}
// Check for immediate closure (aggressive popup blockers)
if (isWindowClosed(authWindow)) {
Logger.error("Popup immediately closed by blocker");
reject(new CAMSError(exports.CAMSErrorType.POPUP_BLOCKED, "Popup blocked by browser. Please allow popups and try again."));
return;
}
Logger.debug("Popup window opened successfully");
// Set up message listener
removeMessageListener = addMessageListener(handleMessage);
Logger.debug("Message listener attached");
// Set up timeouts
Logger.debug("Setting authentication timeout", { authTimeout });
authTimeoutId = NativeFunctions.setTimeout(() => {
Logger.warn("Authentication timeout reached", { authTimeout });
cleanupAndClose(new CAMSError(exports.CAMSErrorType.TIMEOUT, `Authentication process exceeded time limit of ${authTimeout}ms`));
}, authTimeout);
resetIdleTimeout();
setupClosureDetection();
// Focus popup for better UX
try {
authWindow.focus();
}
catch (error) {
Logger.debug("Could not focus popup window (cross-origin restriction)");
}
}
catch (error) {
Logger.error("Failed to initialize authentication popup", {
error: error instanceof Error ? error.message : "Unknown error"
});
cleanup();
reject(new CAMSError(exports.CAMSErrorType.POPUP_BLOCKED, "Failed to initialize authentication: " +
(error instanceof Error ? error.message : "Unknown error")));
}
});
}
class CAMSSessionManager {
storage;
keyPrefix;
events;
token = null;
profile = null;
constructor(storage = null, keyPrefix, events = {}) {
this.storage = storage || getDefaultStorage();
this.keyPrefix = keyPrefix ?? "CAMS-SDK";
this.events = events;
}
setItem(key, value) {
this.storage.setItem(`${this.keyPrefix}:${key}`, value);
}
getItem(key) {
return this.storage.getItem(`${this.keyPrefix}:${key}`);
}
removeItem(key) {
this.storage.removeItem(`${this.keyPrefix}:${key}`);
}
clear() {
// clear only CAMS-prefixed items
Object.keys(this.storage).forEach((k) => {
if (k.startsWith(this.keyPrefix)) {
this.storage.removeItem(k);
}
});
}
async login(config) {
try {
Logger.info("Session login started");
// If we're in a popup, don't attempt to open another popup
if (isPopupWindow()) {
Logger.warn("Cannot initiate login from popup window");
throw new CAMSError(exports.CAMSErrorType.POPUP_BLOCKED, "Cannot initiate authentication from within popup window");
}
if (this.events.onAuthStart)
this.events.onAuthStart();
let attempts = 0;
const maxAttempts = (config.retryAttempts ?? 0) + 1;
Logger.debug("Login retry configuration", { maxAttempts });
while (attempts < maxAttempts) {
try {
attempts++;
Logger.debug("Login attempt", { attempt: attempts, maxAttempts });
// Add exponential backoff delay for retries
if (attempts > 1) {
const delay = Math.min(1000 * Math.pow(2, attempts - 2), 10000); // Max 10s delay
Logger.debug("Retry delay", { delay });
await new Promise((resolve) => setTimeout(resolve, delay));
}
const response = await openCAMSPopUpLogin(config);
Logger.debug("Login Response", response);
this.profile = response;
this.setToken(response.userProfile.tokens.Bearer);
Logger.info("Session login successful");
if (this.events.onAuthSuccess)
this.events.onAuthSuccess(response);
return response;
}
catch (error) {
Logger.warn("Login attempt failed", {
attempt: attempts,
error: error instanceof Error ? error.message : "Unknown error",
});
if (attempts >= maxAttempts || !(error instanceof CAMSError)) {
throw error;
}
// Only retry on specific error types
if (![exports.CAMSErrorType.TIMEOUT, exports.CAMSErrorType.POPUP_BLOCKED].includes(error.type)) {
throw error;
}
Logger.info("Retrying login", { nextAttempt: attempts + 1 });
}
}
throw new CAMSError(exports.CAMSErrorType.MAX_RETRIES_EXCEEDED, "Max retry attempts exceeded");
}
catch (error) {
const errorMessage = error instanceof Error ? error.message : "Unknown error";
const camsError = error instanceof CAMSError
? error
: new CAMSError(exports.CAMSErrorType.USER_CANCELLED, errorMessage);
Logger.error("Session login failed", {
error: camsError.message,
type: camsError.type,
});
if (this.events.onAuthError)
this.events.onAuthError(camsError);
throw camsError;
}
}
async logout() {
Logger.info("Session logout");
this.clearToken();
}
setToken(token) {
this.token = token;
this.storage.setItem(this.keyPrefix, token);
Logger.debug("Token stored", { storageKey: this.keyPrefix });
}
getAccessToken() {
// Return cached token if available, avoiding redundant storage access
if (this.token)
return this.token;
const raw = this.storage.getItem(this.keyPrefix);
if (!raw)
return null;
this.token = raw;
Logger.debug("Token loaded from storage");
return this.token;
}
clearToken() {
this.token = null;
this.storage.removeItem(this.keyPrefix);
Logger.debug("Token cleared");
}
isExpired() {
const token = this.getAccessToken();
if (!token)
return true;
try {
const parts = token.split(".");
if (parts.length < 3)
return true; // Invalid JWT format
const payload = JSON.parse(atob(parts[1]));
if (typeof payload.exp !== "number")
return true;
const exp = payload.exp * 1000;
return Date.now() > exp;
}
catch (error) {
Logger.warn("Failed to parse JWT token", {
error: error instanceof Error ? error.message : "Unknown error",
});
return true;
}
}
isPaddedExpired() {
const token = this.getAccessToken();
if (!token)
return true;
try {
const parts = token.split(".");
if (parts.length < 3)
return true; // Invalid JWT format
const base64 = parts[1].replace(/-/g, "+").replace(/_/g, "/");
const padded = base64.padEnd(base64.length + ((4 - (base64.length % 4)) % 4), "=");
const payload = JSON.parse(atob(padded));
if (typeof payload.exp !== "number")
return true;
const exp = payload.exp * 1000;
return Date.now() > exp;
}
catch (error) {
Logger.warn("Failed to parse JWT token", {
error: error instanceof Error ? error.message : "Unknown error",
});
return true;
}
}
checkTokenExpiration() {
if (this.isExpired()) {
Logger.info("Token expired");
if (this.events.onTokenExpired)
this.events.onTokenExpired();
return true;
}
return false;
}
isAuthenticated() {
const token = this.getAccessToken();
if (!token) {
Logger.debug("No token found");
return false;
}
if (this.checkTokenExpiration()) {
return false;
}
Logger.debug("Token is valid");
return true;
}
async getProfile() {
const storedToken = this.getAccessToken();
if (!storedToken)
return null;
// Check token expiration before processing
if (this.checkTokenExpiration()) {
return null;
}
return this.profile;
}
getSessionError() {
return null;
}
/**
* Complete authentication in popup window
* This should be called by popup apps when authentication is successful
*/
completePopupAuth(profile, targetOrigin) {
if (!isPopupWindow()) {
Logger.warn("completePopupAuth called outside of popup window");
return;
}
try {
// Store the authentication data locally in the popup
this.profile = profile;
this.setToken(profile.userProfile.tokens.Bearer);
Logger.info("Authentication completed in popup, forwarding to parent");
}
catch (error) {
Logger.error("Failed to complete popup authentication", {
error: error instanceof Error ? error.message : "Unknown error"
});
}
}
/**
* Report authentication error in popup window
*/
errorPopupAuth(error, targetOrigin) {
if (!isPopupWindow()) {
Logger.warn("errorPopupAuth called outside of popup window");
return;
}
Logger.warn("Reporting authentication error from popup");
}
}
// 🔹 Safe fallback for Node/SSR
function getDefaultStorage() {
if (typeof window !== "undefined" && window.localStorage) {
return window.localStorage;
}
const memoryStore = {};
return {
getItem: (key) => (key in memoryStore ? memoryStore[key] : null),
setItem: (key, value) => {
memoryStore[key] = value;
},
removeItem: (key) => {
delete memoryStore[key];
},
key: (i) => Object.keys(memoryStore)[i] ?? null,
get length() {
return Object.keys(memoryStore).length;
},
};
}
class CAMSMFAAuthenticator {
config;
attemptCount = 0;
maxAttempts = 3;
constructor(config) {
this.config = config;
}
async sendEmailOTP() {
try {
Logger.debug("Sending Email OTP");
// Implementation would call your API to send email OTP
// For now, return true as placeholder
return true;
}
catch (error) {
Logger.error("Failed to send Email OTP", { error });
return false;
}
}
async verifyOTP(code, type) {
if (this.attemptCount >= this.maxAttempts) {
throw new CAMSError(exports.CAMSErrorType.MAX_RETRIES_EXCEEDED, "Maximum MFA attempts exceeded");
}
this.attemptCount++;
try {
Logger.debug("Verifying MFA code", { type, attempt: this.attemptCount });
const response = await fetch(this.config.apiEndpoint || '/api/auth/verify-mfa', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${this.config.accessToken}`,
},
body: JSON.stringify({
provider: this.config.provider,
accessToken: this.config.accessToken,
idToken: this.config.idToken,
authenticationType: type,
MFACode: code,
appCode: this.config.appCode,
}),
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
console.log("MFA Response >>>", data);
if (data.isAuthenticated) {
Logger.info("MFA verification successful");
this.resetAttempts();
return data;
}
else {
throw new CAMSError(exports.CAMSErrorType.API_VALIDATION_ERROR, data.message || "MFA verification failed");
}
}
catch (error) {
Logger.error("MFA verification failed", { error, attempt: this.attemptCount });
if (error instanceof CAMSError) {
throw error;
}
throw new CAMSError(exports.CAMSErrorType.API_VALIDATION_ERROR, `MFA verification failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
}
getRemainingAttempts() {
return Math.max(0, this.maxAttempts - this.attemptCount);
}
resetAttempts() {
this.attemptCount = 0;
}
}
exports.AuthFailureMessageSchema = AuthFailureMessageSchema;
exports.CAMSError = CAMSError;
exports.CAMSMFAAuthenticator = CAMSMFAAuthenticator;
exports.CAMSSessionManager = CAMSSessionManager;
exports.ErrorMessageSchema = ErrorMessageSchema;
exports.Logger = Logger;
exports.ProfileSchema = ProfileSchema;
exports.URLSchema = URLSchema;
exports.generateSecureWindowName = generateSecureWindowName;
exports.isPopupWindow = isPopupWindow;
exports.openCAMSPopUpLogin = openCAMSPopUpLogin;
exports.validateConfig = validateConfig;
//# sourceMappingURL=index.cjs.js.map