@nibssplc/cams-sdk-react
Version:
React hooks and components for NIBSS CAMS SDK
1,082 lines (1,058 loc) • 303 kB
JavaScript
import * as React from 'react';
import { useState, useRef, useEffect, useCallback, createContext, useContext, useMemo } from 'react';
import { CAMSSessionManager, isPopupWindow, Logger, CAMSError, CAMSErrorType, CAMSMFAAuthenticator } from '@nibssplc/cams-sdk';
export * from '@nibssplc/cams-sdk';
import { useMsal, useAccount, MsalProvider } from '@azure/msal-react';
import { InteractionStatus, PublicClientApplication } from '@azure/msal-browser';
import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
import z$1, { z } from 'zod';
import { OTPInput, OTPInputContext, REGEXP_ONLY_DIGITS } from 'input-otp';
import { RectangleEllipsis, XIcon, CheckCircle, Shield, KeyIcon, ShieldCheck, ShieldClose, Loader2 } from 'lucide-react';
import { appendErrors, FormProvider, Controller, useFormContext, useFormState, useForm } from 'react-hook-form';
import { validateFieldsNatively, toNestErrors } from '@hookform/resolvers';
import { clsx } from 'clsx';
import { twMerge } from 'tailwind-merge';
import { Slot } from '@radix-ui/react-slot';
import * as LabelPrimitive from '@radix-ui/react-label';
import { cva } from 'class-variance-authority';
import * as DialogPrimitive from '@radix-ui/react-dialog';
import { toast } from 'sonner';
import { motion } from 'framer-motion';
/******************************************************************************
Copyright (c) Microsoft Corporation.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
***************************************************************************** */
/* global Reflect, Promise, SuppressedError, Symbol, Iterator */
var __assign = function() {
__assign = Object.assign || function __assign(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
function __rest(s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function")
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
t[p[i]] = s[p[i]];
}
return t;
}
function __awaiter$1(thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
}
function __generator$1(thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (g && (g = 0, op[0] && (_ = 0)), _) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
}
function __spreadArray(to, from, pack) {
if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
if (ar || !(i in from)) {
if (!ar) ar = Array.prototype.slice.call(from, 0, i);
ar[i] = from[i];
}
}
return to.concat(ar || Array.prototype.slice.call(from));
}
typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
var e = new Error(message);
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
};
function useCAMSAuth(options) {
var _this = this;
var _a;
if (options === void 0) { options = {
appCode: "",
loginExpiry: 1,
storageKey: "CAMS-AUTH-SDK",
}; }
var _b = useState(false), isAuthenticated = _b[0], setIsAuthenticated = _b[1];
var _c = useState(false), isLoading = _c[0], setIsLoading = _c[1];
var _d = useState(null), error = _d[0], setError = _d[1];
var _e = useState(""), token = _e[0], setToken = _e[1];
var _f = useState(null), profile = _f[0], setProfile = _f[1];
var sessionManagerRef = useRef(null);
useEffect(function () {
var _a, _b;
// Only initialize on client side
if (typeof window === "undefined")
return;
(_a = sessionManagerRef.current) !== null && _a !== void 0 ? _a : (sessionManagerRef.current = new CAMSSessionManager(localStorage, options.storageKey || "CAMS-AUTH-SDK", {
onAuthSuccess: function (res) {
var _a;
setToken(res.userProfile.tokens.Bearer);
setIsAuthenticated(true);
setIsLoading(false);
(_a = options.onAuthSuccess) === null || _a === void 0 ? void 0 : _a.call(options, res.userProfile.tokens.Bearer);
},
onAuthError: function (error) {
var _a;
setError(error);
setIsAuthenticated(false);
setToken("");
setProfile(null);
setIsLoading(false);
(_a = options.onAuthError) === null || _a === void 0 ? void 0 : _a.call(options, error);
},
onTokenExpired: function () {
var _a;
setIsAuthenticated(false);
setToken("");
setProfile(null);
(_a = options.onTokenExpired) === null || _a === void 0 ? void 0 : _a.call(options);
},
}));
// Check initial auth state (skip if in popup)
if (!isPopupWindow()) {
var initialAuth = sessionManagerRef.current.isAuthenticated();
setIsAuthenticated(initialAuth);
if (initialAuth) {
setToken((_b = sessionManagerRef.current.getAccessToken()) !== null && _b !== void 0 ? _b : "");
sessionManagerRef.current.getProfile().then(setProfile);
}
}
}, [options.storageKey]);
var login = useCallback(function (config) { return __awaiter$1(_this, void 0, void 0, function () {
var loginConfig, userProfile, err_1, e, isPopupClosedError, restoredToken, userProfile;
var _a, _b, _c;
return __generator$1(this, function (_d) {
switch (_d.label) {
case 0:
if (!sessionManagerRef.current)
return [2 /*return*/];
setIsLoading(true);
setError(null);
_d.label = 1;
case 1:
_d.trys.push([1, 4, 8, 9]);
loginConfig = __assign(__assign({}, config), { idleTimeout: options.idleTimeout });
return [4 /*yield*/, sessionManagerRef.current.login(loginConfig)];
case 2:
_d.sent();
return [4 /*yield*/, sessionManagerRef.current.getProfile()];
case 3:
userProfile = _d.sent();
setProfile(userProfile);
return [3 /*break*/, 9];
case 4:
err_1 = _d.sent();
e = err_1;
isPopupClosedError = ((_a = e === null || e === void 0 ? void 0 : e.message) === null || _a === void 0 ? void 0 : _a.includes("Authentication window was closed")) ||
(e === null || e === void 0 ? void 0 : e.error) === "Authentication window was closed";
if (!(isPopupClosedError && sessionManagerRef.current.isAuthenticated())) return [3 /*break*/, 6];
restoredToken = (_b = sessionManagerRef.current.getAccessToken()) !== null && _b !== void 0 ? _b : "";
setToken(restoredToken);
setIsAuthenticated(true);
return [4 /*yield*/, sessionManagerRef.current.getProfile()];
case 5:
userProfile = _d.sent();
setProfile(userProfile);
(_c = options.onAuthSuccess) === null || _c === void 0 ? void 0 : _c.call(options, restoredToken);
return [3 /*break*/, 7];
case 6:
setError(err_1);
setIsAuthenticated(false);
setToken("");
setProfile(null);
_d.label = 7;
case 7: return [3 /*break*/, 9];
case 8:
setIsLoading(false);
return [7 /*endfinally*/];
case 9: return [2 /*return*/];
}
});
}); }, [options.idleTimeout]);
var logout = useCallback(function () { return __awaiter$1(_this, void 0, void 0, function () {
return __generator$1(this, function (_a) {
switch (_a.label) {
case 0:
if (!sessionManagerRef.current)
return [2 /*return*/];
return [4 /*yield*/, sessionManagerRef.current.logout()];
case 1:
_a.sent();
setIsAuthenticated(false);
setToken("");
setProfile(null);
setError(null);
return [2 /*return*/];
}
});
}); }, []);
return {
login: login,
logout: logout,
isAuthenticated: isAuthenticated,
isLoading: isLoading,
error: error,
token: token,
profile: profile,
loginExpiry: (_a = options.loginExpiry) !== null && _a !== void 0 ? _a : 1,
appCode: options.appCode,
storageKey: options.storageKey || "CAMS-AUTH-SDK",
};
}
var _a;
// Crypto polyfill for MSAL browser compatibility
// Only apply if crypto.subtle is completely missing
if (typeof window !== "undefined" && !((_a = window.crypto) === null || _a === void 0 ? void 0 : _a.subtle)) {
// Ensure crypto object exists
if (!window.crypto) {
window.crypto = {};
}
// Polyfill getRandomValues
if (!window.crypto.getRandomValues) {
window.crypto.getRandomValues = function (array) {
var bytes = new Uint8Array(array.buffer, array.byteOffset, array.byteLength);
for (var i = 0; i < bytes.length; i++) {
bytes[i] = Math.floor(Math.random() * 256);
}
return array;
};
}
var sha256_1 = function (data) { return __awaiter(void 0, void 0, void 0, function () {
var toUint8, bytes, hash, k, ml, msg, dv, high, low, i, w, j, j, s0, s1, a, b, c, d, e, f, g, h, j, S1, ch, temp1, S0, maj, temp2, result, i;
return __generator(this, function (_a) {
toUint8 = function (src) {
if (src instanceof ArrayBuffer)
return new Uint8Array(src);
if (ArrayBuffer.isView(src)) {
var view = src;
return new Uint8Array(view.buffer, view.byteOffset || 0, view.byteLength);
}
throw new TypeError("Unsupported BufferSource");
};
bytes = toUint8(data);
hash = new Uint32Array(8);
k = new Uint32Array([
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1,
0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786,
0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147,
0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b,
0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a,
0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
]);
hash[0] = 0x6a09e667;
hash[1] = 0xbb67ae85;
hash[2] = 0x3c6ef372;
hash[3] = 0xa54ff53a;
hash[4] = 0x510e527f;
hash[5] = 0x9b05688c;
hash[6] = 0x1f83d9ab;
hash[7] = 0x5be0cd19;
ml = bytes.length * 8;
msg = new Uint8Array(bytes.length + 64 + ((64 - ((bytes.length + 9) % 64)) % 64));
msg.set(bytes);
msg[bytes.length] = 0x80;
dv = new DataView(msg.buffer);
high = Math.floor(ml / 0x100000000);
low = ml >>> 0;
dv.setUint32(msg.length - 8, high, false);
dv.setUint32(msg.length - 4, low, false);
for (i = 0; i < msg.length; i += 64) {
w = new Uint32Array(64);
for (j = 0; j < 16; j++)
w[j] = new DataView(msg.buffer).getUint32(i + j * 4, false);
for (j = 16; j < 64; j++) {
s0 = ((w[j - 15] >>> 7) | (w[j - 15] << 25)) ^
((w[j - 15] >>> 18) | (w[j - 15] << 14)) ^
(w[j - 15] >>> 3);
s1 = ((w[j - 2] >>> 17) | (w[j - 2] << 15)) ^
((w[j - 2] >>> 19) | (w[j - 2] << 13)) ^
(w[j - 2] >>> 10);
w[j] = (w[j - 16] + s0 + w[j - 7] + s1) >>> 0;
}
a = hash[0], b = hash[1], c = hash[2], d = hash[3], e = hash[4], f = hash[5], g = hash[6], h = hash[7];
for (j = 0; j < 64; j++) {
S1 = ((e >>> 6) | (e << 26)) ^
((e >>> 11) | (e << 21)) ^
((e >>> 25) | (e << 7));
ch = (e & f) ^ (~e & g);
temp1 = (h + S1 + ch + k[j] + w[j]) >>> 0;
S0 = ((a >>> 2) | (a << 30)) ^
((a >>> 13) | (a << 19)) ^
((a >>> 22) | (a << 10));
maj = (a & b) ^ (a & c) ^ (b & c);
temp2 = (S0 + maj) >>> 0;
h = g;
g = f;
f = e;
e = (d + temp1) >>> 0;
d = c;
c = b;
b = a;
a = (temp1 + temp2) >>> 0;
}
hash[0] = (hash[0] + a) >>> 0;
hash[1] = (hash[1] + b) >>> 0;
hash[2] = (hash[2] + c) >>> 0;
hash[3] = (hash[3] + d) >>> 0;
hash[4] = (hash[4] + e) >>> 0;
hash[5] = (hash[5] + f) >>> 0;
hash[6] = (hash[6] + g) >>> 0;
hash[7] = (hash[7] + h) >>> 0;
}
result = new Uint8Array(32);
for (i = 0; i < 8; i++)
new DataView(result.buffer).setUint32(i * 4, hash[i], false);
return [2 /*return*/, result.buffer];
});
}); };
// Create the polyfilled subtle object
var polyfillSubtle = {
digest: function (algorithm, data) { return __awaiter(void 0, void 0, void 0, function () {
var alg;
return __generator(this, function (_a) {
alg = typeof algorithm === "string"
? algorithm
: algorithm.name;
if (alg === "SHA-256")
return [2 /*return*/, sha256_1(data)];
throw new Error("Unsupported algorithm: ".concat(alg));
});
}); },
};
// Set the polyfilled subtle object
window.crypto.subtle = polyfillSubtle;
// Polyfill randomUUID
if (!window.crypto.randomUUID) {
window.crypto.randomUUID =
function () {
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (c) {
var r = (Math.random() * 16) | 0;
var v = c === "x" ? r : (r & 0x3) | 0x8;
return v.toString(16);
});
};
}
console.log("Crypto polyfill applied (native crypto.subtle not available)");
}
var setCookie = function (name, value, days) {
if (days === void 0) { days = 1; }
var expires = new Date(Date.now() + days * 24 * 60 * 60 * 1000).toUTCString();
console.log("Auth Cookie Expires >>>", { name: name, value: value, expires: expires });
document.cookie = "".concat(name, "=").concat(encodeURIComponent(value), "; expires=").concat(expires, "; path=/; samesite=Lax");
};
var getCookie = function (name) {
var _a;
return ((_a = document.cookie
.split("; ")
.find(function (row) { return row.startsWith(name + "="); })) === null || _a === void 0 ? void 0 : _a.split("=")[1])
? decodeURIComponent(document.cookie
.split("; ")
.find(function (row) { return row.startsWith(name + "="); })
.split("=")[1])
: null;
};
var deleteCookie = function (name) {
document.cookie = name + "=; Max-Age=-99999999; path=/";
};
function useCAMSMSALAuth(options) {
var _this = this;
var optStorageKey = options.storageKey, optScopes = options.scopes, prompt = options.prompt, appCode = options.appCode, ValidateUserEndpoint = options.ValidateUserEndpoint, _a = options.activeCookiePeriod, activeCookiePeriod = _a === void 0 ? 1 : _a;
var storageKey = optStorageKey || "CAMS-MSAL-AUTH-SDK";
var _b = useMsal(), instance = _b.instance, inProgress = _b.inProgress, accounts = _b.accounts;
var account = useAccount(accounts[0] || {});
var _c = useState(null), error = _c[0], setError = _c[1];
var _d = useState(""), idToken = _d[0], setIdToken = _d[1];
var _e = useState(""), accessToken = _e[0], setAccessToken = _e[1];
// const [mfaAuthenticator, setMfaAuthenticator] =
// useState<CAMSMFAAuthenticator | null>(null);
var _f = useState(false), requiresMFA = _f[0], setRequiresMFA = _f[1];
var isLoading = inProgress !== InteractionStatus.None;
var isAuthenticated = !!account && !!accessToken && !requiresMFA;
var scopes = optScopes || ["openid", "profile", "email"];
var isTokenValid = function (token) {
try {
var payload = JSON.parse(atob(token.split(".")[1]));
return payload.exp * 1000 > Date.now();
}
catch (_a) {
return false;
}
};
useEffect(function () {
if (typeof window !== "undefined" && !accessToken) {
var stored = localStorage.getItem(storageKey);
if (stored) {
try {
var _a = JSON.parse(stored), accessToken_1 = _a.accessToken, idToken_1 = _a.idToken, storedRequiresMFA = _a.requiresMFA, storedIsAuthenticated = _a.isAuthenticated;
// Restore MFA state
if (accessToken_1 && isTokenValid(accessToken_1)) {
setAccessToken(accessToken_1);
setIdToken(idToken_1);
setRequiresMFA(storedRequiresMFA);
Logger.debug("Restored authentication state from storage", {
accessToken: accessToken_1,
idToken: idToken_1,
requiresMFA: storedRequiresMFA,
isAuthenticated: storedIsAuthenticated,
});
}
else {
localStorage.removeItem(storageKey);
}
}
catch (_b) { }
}
else if (account) {
// Storage cleared but MSAL account exists - clear MSAL state
instance.logoutRedirect().catch(function () { });
}
}
}, [accessToken, account, instance, options.storageKey]);
var login = useCallback(function () { return __awaiter$1(_this, void 0, void 0, function () {
var response, mfaConfig, authenticator, userConfig, err_1, camsError_1, camsError;
var _a;
return __generator$1(this, function (_b) {
switch (_b.label) {
case 0:
if (inProgress !== InteractionStatus.None) {
Logger.warn("Authentication already in progress, ignoring duplicate call");
return [2 /*return*/];
}
setError(null);
_b.label = 1;
case 1:
_b.trys.push([1, 4, , 5]);
return [4 /*yield*/, instance.loginPopup({
scopes: scopes,
prompt: prompt || "login",
})];
case 2:
response = _b.sent();
console.log("Login Token response:", {
accessToken: response.accessToken,
idToken: response.idToken,
});
setAccessToken(response.accessToken);
setIdToken(response.idToken);
mfaConfig = {
accessToken: response.accessToken,
idToken: response.idToken,
provider: "MSAL",
APIAuthEndpoint: ValidateUserEndpoint,
};
authenticator = new CAMSMFAAuthenticator();
return [4 /*yield*/, authenticator.GetUserMFAConfig(mfaConfig)];
case 3:
userConfig = _b.sent();
Logger.debug("MFA Authenticator initialized:", userConfig);
console.log("MFA Authenticator initialized:", userConfig);
// Don't persist as authenticated until MFA is complete
if (typeof window !== "undefined" && userConfig.isValid) {
setRequiresMFA(userConfig.userInfo.isMFAEnabled);
localStorage.setItem(storageKey, JSON.stringify({
isAuthenticated: false,
requiresMFA: userConfig.userInfo.isMFAEnabled,
accessToken: response.accessToken,
idToken: response.idToken,
}));
}
return [3 /*break*/, 5];
case 4:
err_1 = _b.sent();
// Handle interaction_in_progress error
if (err_1.errorCode === "interaction_in_progress") {
Logger.warn("Interaction already in progress, please wait");
return [2 /*return*/];
}
// Handle user cancellation gracefully
if (err_1.errorCode === "user_cancelled") {
Logger.error("User cancelled login");
setError(null); // Don't treat cancellation as an error
return [2 /*return*/];
}
// If popup is blocked
if (err_1.errorCode === "popup_window_error" ||
((_a = err_1.message) === null || _a === void 0 ? void 0 : _a.includes("popup"))) {
camsError_1 = new CAMSError(CAMSErrorType.POPUP_BLOCKED, "Popup blocked by browser. Please allow popups and try again.");
setError(camsError_1);
return [2 /*return*/];
}
camsError = new CAMSError(CAMSErrorType.API_VALIDATION_ERROR, "Login failed: " + err_1.message || err_1);
setError(camsError);
return [3 /*break*/, 5];
case 5: return [2 /*return*/];
}
});
}); }, [
instance,
scopes,
prompt,
appCode,
ValidateUserEndpoint,
storageKey,
inProgress,
]);
var completeMFA = useCallback(function (data) { return __awaiter$1(_this, void 0, void 0, function () {
return __generator$1(this, function (_a) {
Logger.info("Completed MFA.. Setting State");
try {
// Update storage with complete authentication BEFORE setting state
if (typeof window !== "undefined") {
localStorage.setItem(storageKey, JSON.stringify({
isAuthenticated: true,
requiresMFA: false,
accessToken: accessToken,
idToken: idToken,
}));
setCookie("CAMS-MSAL-AUTH-SDK-PROFILE", JSON.stringify({
state: "AUTH_SUCCESS",
role: data.userInfo.role,
profile: __assign({}, data),
}), activeCookiePeriod);
setRequiresMFA(false);
// Set requiresMFA to false after storage update
Logger.debug("MFA completed successfully, storage updated", {
accessToken: accessToken,
idToken: idToken,
isAuthenticated: true,
requiresMFA: false,
});
}
Logger.debug("MFA completed successfully, requiresMFA set to false");
return [2 /*return*/, data];
}
catch (error) {
setError(error);
throw error;
}
return [2 /*return*/];
});
}); }, [accessToken, idToken, storageKey, activeCookiePeriod]);
var LoginADCredentials = useCallback(function (credentials, appCode, CredentialsAuthEndpoint) { return __awaiter$1(_this, void 0, void 0, function () {
var authenticator;
return __generator$1(this, function (_a) {
switch (_a.label) {
case 0:
authenticator = new CAMSMFAAuthenticator();
return [4 /*yield*/, authenticator.LoginADCredentials(credentials, CredentialsAuthEndpoint)];
case 1: return [2 /*return*/, _a.sent()];
}
});
}); }, []);
var logout = useCallback(function () { return __awaiter$1(_this, void 0, void 0, function () {
var err_2, camsError;
return __generator$1(this, function (_a) {
switch (_a.label) {
case 0:
_a.trys.push([0, 2, , 3]);
Logger.info("Logging User Out");
return [4 /*yield*/, instance.logoutRedirect()];
case 1:
_a.sent();
setAccessToken("");
setIdToken("");
setError(null);
setRequiresMFA(false);
if (typeof window !== "undefined") {
localStorage.removeItem(storageKey);
// deleteCookie("CAMS-MSAL-AUTH-SDK-PROFILE");
}
return [3 /*break*/, 3];
case 2:
err_2 = _a.sent();
camsError = new CAMSError(CAMSErrorType.API_VALIDATION_ERROR, "Logout failed: " + err_2);
setError(camsError);
return [3 /*break*/, 3];
case 3: return [2 /*return*/];
}
});
}); }, [instance, storageKey]);
return {
login: login,
logout: logout,
storageKey: storageKey,
isAuthenticated: isAuthenticated,
isLoading: isLoading,
error: error,
idToken: idToken,
accessToken: accessToken,
appCode: appCode,
requiresMFA: requiresMFA,
completeMFA: completeMFA,
LoginADCredentials: LoginADCredentials,
setRequiresMFA: setRequiresMFA,
activeCookiePeriod: activeCookiePeriod,
};
}
/**
* Converts a base64url-encoded string to an ArrayBuffer.
* @param base64url The base64url-encoded string
*/
function base64urlToArrayBuffer(base64url) {
var base64 = base64url.replace(/-/g, "+").replace(/_/g, "/");
var padLength = (4 - (base64.length % 4)) % 4;
var padded = base64 + "=".repeat(padLength);
var binaryStr = atob(padded);
var buffer = new ArrayBuffer(binaryStr.length);
var bytes = new Uint8Array(buffer);
for (var i = 0; i < binaryStr.length; i++) {
bytes[i] = binaryStr.charCodeAt(i);
}
return buffer;
}
/**
* Converts an ArrayBuffer to a base64url-encoded string.
* @param buffer The ArrayBuffer to convert
*/
function arrayBufferToBase64url(buffer) {
var bytes = new Uint8Array(buffer);
var binaryStr = Array.from(bytes, function (b) { return String.fromCharCode(b); }).join("");
return btoa(binaryStr)
.replace(/\+/g, "-")
.replace(/\//g, "_")
.replace(/=+$/, ""); // use regex to remove all trailing '='
}
/**
* Initiates the WebAuthn registration process.
* It takes server-provided options, converts them for the browser API,
* calls navigator.credentials.create(), and then converts the result
* back into a JSON-friendly format.
*
* @param options - The PublicKeyCredentialCreationOptions from the server.
* @returns A promise that resolves to a JSON-serializable representation of the PublicKeyCredential.
*/
function register(options) {
return __awaiter$1(this, void 0, void 0, function () {
var createOptions, credential, publicKeyCredential, attestationResponse, transports, err_1;
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
return __generator$1(this, function (_k) {
switch (_k.label) {
case 0:
_k.trys.push([0, 2, , 3]);
createOptions = __assign(__assign({}, options), { challenge: base64urlToArrayBuffer(options.challenge), user: __assign(__assign({}, options.user), { id: base64urlToArrayBuffer(options.user.id) }) });
return [4 /*yield*/, navigator.credentials.create({
publicKey: __assign(__assign({}, createOptions), { pubKeyCredParams: (_a = options.pubKeyCredParams) === null || _a === void 0 ? void 0 : _a.map(function (param) { return ({
type: "public-key",
alg: param.alg,
}); }), attestation: (((_b = options.attestation) === null || _b === void 0 ? void 0 : _b.toLowerCase()) || "none"), authenticatorSelection: __assign(__assign({}, options.authenticatorSelection), { residentKey: (((_d = (_c = options.authenticatorSelection) === null || _c === void 0 ? void 0 : _c.residentKey) === null || _d === void 0 ? void 0 : _d.toLowerCase()) ||
"discouraged"), userVerification: (((_f = (_e = options.authenticatorSelection) === null || _e === void 0 ? void 0 : _e.userVerification) === null || _f === void 0 ? void 0 : _f.toLowerCase()) ||
"discouraged") }) }),
})];
case 1:
credential = _k.sent();
if (!credential)
throw new Error("No credential created.");
publicKeyCredential = credential;
attestationResponse = publicKeyCredential.response;
transports = (_j = (_h = (_g = publicKeyCredential.response).getTransports) === null || _h === void 0 ? void 0 : _h.call(_g)) !== null && _j !== void 0 ? _j : [];
return [2 /*return*/, {
id: publicKeyCredential.id,
rawId: arrayBufferToBase64url(publicKeyCredential.rawId),
type: credential.type,
response: {
clientDataJSON: arrayBufferToBase64url(attestationResponse.clientDataJSON),
attestationObject: arrayBufferToBase64url(attestationResponse.attestationObject),
transports: transports,
},
}];
case 2:
err_1 = _k.sent();
console.error("Error during registration:", err_1);
if (err_1.name === "NotAllowedError") {
throw new Error("Face ID/Touch ID cancelled or failed. Please try again.");
}
if (err_1.name === "InvalidStateError") {
throw new Error("Passkey already registered for this user.");
}
if (err_1.name === "NotSupportedError") {
throw new Error("Passkeys not supported on this device.");
}
throw err_1;
case 3: return [2 /*return*/];
}
});
});
}
/**
* Initiates the WebAuthn authentication process.
* It takes server-provided options, converts them for the browser API,
* calls navigator.credentials.get(), and then converts the result
* back into a JSON-friendly format.
*
* @param options - The PublicKeyCredentialRequestOptions from the server.
* @returns A promise that resolves to a JSON-serializable representation of the PublicKeyCredential.
*/
function authenticate(options) {
return __awaiter$1(this, void 0, void 0, function () {
var getOptions, credential, publicKeyCredential, assertionResponse;
var _a;
return __generator$1(this, function (_b) {
switch (_b.label) {
case 0:
getOptions = __assign(__assign({}, options), { challenge: base64urlToArrayBuffer(options.challenge), allowCredentials: (_a = options.allowCredentials) === null || _a === void 0 ? void 0 : _a.map(function (cred) { return (__assign(__assign({}, cred), { id: base64urlToArrayBuffer(cred.id) })); }) });
return [4 /*yield*/, navigator.credentials.get({ publicKey: getOptions })];
case 1:
credential = _b.sent();
if (!credential) {
throw new Error("Failed to get credential.");
}
publicKeyCredential = credential;
assertionResponse = publicKeyCredential.response;
return [2 /*return*/, {
id: publicKeyCredential.id,
rawId: arrayBufferToBase64url(publicKeyCredential.rawId),
type: credential.type,
response: {
clientDataJSON: arrayBufferToBase64url(assertionResponse.clientDataJSON),
authenticatorData: arrayBufferToBase64url(assertionResponse.authenticatorData),
signature: arrayBufferToBase64url(assertionResponse.signature),
userHandle: assertionResponse.userHandle
? arrayBufferToBase64url(assertionResponse.userHandle)
: null,
},
}];
}
});
});
}
/**
* A React hook that provides access to the WebAuthn `register` and `authenticate` functions.
* @returns An object containing the `register` and `authenticate` functions.
*/
var useWebAuthn = function () {
return {
register: register,
authenticate: authenticate,
};
};
var CAMSContext$1 = createContext(null);
function useCAMSContext$1() {
var context = useContext(CAMSContext$1);
if (!context) {
throw new Error('useCAMSContext must be used within a CAMSProvider');
}
return context;
}
function ProtectedRoute(_a) {
var children = _a.children, fallback = _a.fallback, redirectTo = _a.redirectTo;
var _b = useCAMSContext$1(), isAuthenticated = _b.isAuthenticated, isLoading = _b.isLoading;
if (isLoading) {
return fallback || jsx("div", { className: 'h-screen flex items-center justify-center', children: "Loading..." });
}
if (!isAuthenticated) {
if (redirectTo && typeof window !== 'undefined') {
window.location.href = redirectTo;
return null;
}
return fallback || jsx("div", { children: "Access denied. Please log in." });
}
return jsx(Fragment, { children: children });
}
var CAMSMSALContext = createContext(null);
var isTokenValid = function (token) {
try {
var payload = JSON.parse(atob(token.split(".")[1]));
return payload.exp * 1000 > Date.now();
}
catch (_a) {
return false;
}
};
function CAMSMSALProviderInner(_a) {
var _this = this;
var children = _a.children, authOptions = __rest(_a, ["children"]);
var auth = useCAMSMSALAuth(authOptions);
var profileStorageKey = "".concat(auth.storageKey, "-PROFILE");
var getInitialProfile = function () {
if (typeof window === "undefined") {
return null;
}
try {
var storedProfile = getCookie(profileStorageKey);
return storedProfile ? JSON.parse(storedProfile) : null;
}
catch (_a) {
return null;
}
};
var _b = useState(getInitialProfile), userProfile = _b[0], setUserProfile = _b[1];
// Load profile from storage on mount
useEffect(function () {
if (typeof window !== "undefined") {
// const storedProfile = localStorage.get Item(profileStorageKey);
var storedProfile = getCookie(profileStorageKey);
if (storedProfile) {
try {
setUserProfile(JSON.parse(storedProfile));
}
catch (_a) { }
}
}
}, [profileStorageKey]);
// Persist tokens and profile
useEffect(function () {
if (auth.accessToken &&
isTokenValid(auth.accessToken) &&
typeof window !== "undefined") {
localStorage.setItem(auth.storageKey, JSON.stringify({
accessToken: auth.accessToken,
idToken: auth.idToken,
appCode: auth.appCode,
}));
}
}, [auth.accessToken, auth.idToken, auth.storageKey]);
// Persist profile separately
useEffect(function () {
if (typeof window !== "undefined") {
if (userProfile) {
setCookie(profileStorageKey, JSON.stringify(userProfile), 1); // Store for 1 day
}
else {
deleteCookie(profileStorageKey);
}
}
}, [userProfile, profileStorageKey]);
// Enhanced logout that also clears profile
var enhancedLogout = function () { return __awaiter$1(_this, void 0, void 0, function () {
return __generator$1(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, auth.logout()];
case 1:
_a.sent();
setUserProfile(null);
if (typeof window !== "undefined") {
deleteCookie(profileStorageKey);
}
return [2 /*return*/];
}
});
}); };
var value = useMemo(function () { return (__assign(__assign({}, auth), { logout: enhancedLogout, userProfile: userProfile, setUserProfile: setUserProfile })); }, [auth, userProfile]);
return (jsx(CAMSMSALContext.Provider, { value: value, children: children }));
}
function CAMSMSALProvider(props) {
var _a;
var msalConfig = props.msalConfig, msalInstance = props.msalInstance;
// Ensure crypto is available before creating MSAL instance
if (typeof window !== "undefined" && !((_a = window.crypto) === null || _a === void 0 ? void 0 : _a.subtle)) {
throw new Error("Crypto API not available. Polyfill failed to load.");
}
var instance = msalInstance || new PublicClientApplication(msalConfig);
return (jsx(MsalProvider, { instance: instance, children: jsx(CAMSMSALProviderInner, __assign({}, props)) }));
}
function useCAMSMSALContext() {
var context = useContext(CAMSMSALContext);
if (!context) {
throw new Error("useCAMSMSALContext must be used within a CAMSMSALProvider");
}
return context;
}
var ClientOnly = function (_a) {
var children = _a.children, _b = _a.fallback, fallback = _b === void 0 ? null : _b;
var _c = useState(false), hasMounted = _c[0], setHasMounted = _c[1];
useEffect(function () {
setHasMounted(true);
}, []);
if (!hasMounted) {
return jsx(Fragment, { children: fallback });
}
return jsx(Fragment, { children: children });
};
var CAMSContext = createContext(null);
function useCAMSContext() {
var context = useContext(CAMSContext);
if (!context) {
throw new Error("useCAMSContext must be used within a UnifiedCAMSProvider");
}
return context;
}
var GuidSchema = z.uuid("appCode must be a valid GUID");
function CAMSProviderCore(props) {
var _this = this;
var children = props.children, mode = props.mode, appCode = props.appCode;
// Always call both hooks to satisfy Rules of Hooks
var regularAuth = useCAMSAuth(mode === "REGULAR"
? __assign(__assign({}, props), { appCode: appCode })
: { appCode: "" });
var msalAuth = useCAMSMSALAuth(mode === "MSAL"
? __assign(__assign({}, props), { appCode: appCode, ValidateUserEndpoint: props.ValidateUserEndpoint })
: { appCode: "", ValidateUserEndpoint: "" });
var auth = mode === "REGULAR" ? regularAuth : msalAuth;
var profileStorageKey = "".concat(auth.storageKey, "-PROFILE");
var getInitialProfile = function () {
if (typeof window === "undefined")
return null;
try {
var storedProfile = getCookie(profileStorageKey);
return storedProfile ? JSON.parse(storedProfile) : null;
}
catch (_a) {
return null;
}
};
var _a = useState(getInitialProfile), userProfile = _a[0], setUserProfile = _a[1];
useEffect(function () {
if (typeof window !== "undefined") {
var storedProfile = getCookie(profileStorageKey);
if (storedProfile) {
try {
setUserProfile(JSON.parse(storedProfile));
}
catch (_a) { }
}
}
}, [profileStorageKey]);
useEffect(function () {
if (typeof window !== "undefined") {
if (userProfile) {
setCookie(profileStorageKey, JSON.stringify(userProfile), 1);
}
else {
deleteCookie(profileStorageKey);
}
}
}, [userProfile, profileStorageKey]);
var enhancedLogout = function () { return __awaiter$1(_this, void 0, void 0, function () {
return __generator$1(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, auth.logout()];
case 1:
_a.sent();
setUserProfile(null);
if (typeof window !== "undefined") {
deleteCookie(profileStorageKey);
}
return [2 /*return*/];
}
});
}); };
useEffect(function () {
if (mode === "MSAL" &&
"requiresMFA" in auth &&
!auth.requiresMFA &&
auth.isAuthenticated) {
var storedData = localStorage.getItem(auth.storageKey);
if (storedData) {
try {
var parsed = JSON.parse(storedData);
if (parsed.userProfile) {
setUserProfile(parsed.userProfile);
}
}
catch (_a) { }
}
}
}, [
mode,
"requiresMFA" in auth ? auth.requiresMFA : false,
auth.isAuthenticated,
auth.storageKey,
userProfile,
]);
var value = useMemo(function () {
auth.logout; var authRest = __rest(auth, ["logout"]);
return __assign(__assign({}, authRest), { logout: enhancedLogout, user: userProfile, setUserProfile: setUserProfile, authMode: mode, onAuthSuccess: mode === "MSAL"
? props.onAuthSuccess
: undefined, onAuthError: mode === "MSAL" ? props.onAuthError : undefined });
}, [auth, userProfile, mode, props]);
return jsx(CAMSContext.Provider, { value: value, children: children });
}
function UnifiedCAMSProvider(props) {
var _a;
// Validate appCode is a valid GUID
var appCodeValidation = GuidSchema.safeParse(props.appCode);
if (!appCodeValidation.success) {
throw new Error("Invalid CAS APP CODE: ".concat(appCodeValidation.error.issues[0].message));
}
if (props.mode === "MSAL") {
var msalConfig = props.msalConfig, msalInstance = props.msalInstance;
if (typeof window !== "undefined" && !((_a = window.crypto) === null || _a === void 0 ? void 0 : _a.subtle)) {
throw new Error("Crypto API not available. Polyfill failed to load.");
}
var instance = msalInstance || new PublicClientApplication(msalConfig);
return (jsx(MsalProvider, { instance: instance, children: jsx(CAMSProviderCore, __assign({}, props)) }));
}
return (jsx(ClientOnly, { fallback: jsx("div", { className: "h-screen flex items-center justify-center", children: "Loading..." }), children: jsx(CAMSProviderCore, __assign({}, props)) }));
}
// Backward compatibility exports
var CAMSProvider = function (props) { return (jsx(UnifiedCAMSProvider, __assign({}, props, { mode: "REGULAR" }))); };
/** A special constant with type `never` */
function $constructor(name, initializer, params) {
function init(inst, def) {
var _a;
Object.defineProperty(inst, "_zod", {
value: inst._zod ?? {},
enumerable: false,
});
(_a = inst._zod).traits ?? (_a.traits = new Set());
inst._zod.traits.add(name);
initializer(inst, def);
// support prototype modifications
for (const k in _.prototype) {
if (!(k in inst))
Object.defineProperty(inst, k, { value: _.prototype[k].bind(inst) });
}
inst._zod.constr = _;
inst._zod.def = def;
}
// doesn't work if Parent has a constructor with arguments
const Parent = params?.Parent ?? Object;
class Definition extends Parent {
}
Object.defineProperty(Definition, "name", { value: name });
function _(def) {
var _a;
const inst = params?.Parent ? new Definition() : this;
init(inst, def);
(_a = inst._zod).deferred ?? (_a.deferred = []);
for (const fn of inst._zod.deferred) {
fn();
}
return inst;
}
Object.defineProperty(_, "init", { value: init });
Object.defineProperty(_, Symbol.hasInstance, {
value: (inst) => {
if (params?.Parent && inst instanceof