UNPKG

@unqtech/age-verification-mitid

Version:

Frontend SDK for age verification via UNQVerify using MitID

384 lines (370 loc) 12.4 kB
"use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); var __async = (__this, __arguments, generator) => { return new Promise((resolve, reject) => { var fulfilled = (value) => { try { step(generator.next(value)); } catch (e) { reject(e); } }; var rejected = (value) => { try { step(generator.throw(value)); } catch (e) { reject(e); } }; var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected); step((generator = generator.apply(__this, __arguments)).next()); }); }; // src/index.ts var src_exports = {}; __export(src_exports, { getVerifiedAge: () => getVerifiedAge, handleRedirectResult: () => handleRedirectResult, init: () => init, isVerified: () => isVerified, resetVerification: () => resetVerification, startVerificationWithPopup: () => startVerificationWithPopup, startVerificationWithRedirect: () => startVerificationWithRedirect }); module.exports = __toCommonJS(src_exports); // src/state.ts var config = null; function setConfig(c) { config = c; } function getConfig() { if (!config) throw new Error("[UNQVerify] SDK not initialized"); return config; } // src/verification/buildUrl.ts function buildOidcUrl(config2) { const isTestKey = config2.publicKey.startsWith("pk_test_"); const baseUrl = isTestKey ? "https://test.api.aldersverificering.dk" : "https://api.aldersverificering.dk"; const query = new URLSearchParams({ RedirectUri: config2.redirectUri, AgeToVerify: config2.ageToVerify.toString() }); return `${baseUrl}/api/v1/oidc/create?${query.toString()}`; } // src/verification/callApi.ts function callVerificationApi(url, publicKey) { return __async(this, null, function* () { const res = yield fetch(url, { method: "POST", headers: { "Content-Type": "application/json", "x-public-key": publicKey } }); if (!res.ok) { throw new Error(`API error: ${res.status}`); } const redirectUrl = yield res.text(); if (!redirectUrl.startsWith("http")) { throw new Error("Invalid redirect URL returned from API"); } return redirectUrl; }); } // src/utils/cookies.ts function getCookie(name) { const value = `; ${document.cookie}`; const parts = value.split(`; ${name}=`); if (parts.length === 2) return parts.pop().split(";").shift() || null; return null; } function setCookie(name, value, expiresInSeconds) { let cookie = `${name}=${value}; path=/; SameSite=Lax`; if (expiresInSeconds) { const expiryDate = new Date(Date.now() + expiresInSeconds * 1e3); cookie += `; expires=${expiryDate.toUTCString()}`; } document.cookie = cookie; } function deleteCookie(name) { document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/`; } // src/constants.ts var VERIFICATION_COOKIE_KEY = "unqverify_token"; // src/verify/isVerified.ts var import_jwt_decode = require("jwt-decode"); function isVerified() { try { const token = getCookie(VERIFICATION_COOKIE_KEY); if (!token) return false; const decoded = (0, import_jwt_decode.jwtDecode)(token); if (!decoded.aldersverificeringdk_verification_result) return false; const now = Math.floor(Date.now() / 1e3); if (decoded.exp < now) return false; return true; } catch (err) { console.warn("[UNQVerify] Failed to parse verification token:", err); return false; } } // src/verification/actions.ts function startVerificationWithRedirect() { return __async(this, null, function* () { var _a, _b; let config2 = null; try { config2 = getConfig(); if (!config2) { throw new Error( "[UNQVerify] Configuration missing. Ensure `getConfig()` is set up correctly." ); } } catch (err) { console.error("[UNQVerify] Failed to retrieve configuration:", err); if (config2 == null ? void 0 : config2.onFailure) { config2.onFailure(err instanceof Error ? err : new Error(String(err))); } return; } if (isVerified()) { const token = getCookie(VERIFICATION_COOKIE_KEY); if (token) { config2.onVerified({ token }); } else { console.warn( "[UNQVerify] Token missing even though isVerified() was true." ); (_a = config2.onFailure) == null ? void 0 : _a.call( config2, new Error("User appears verified, but token cookie is missing.") ); } return; } try { const oidcUrl = buildOidcUrl(config2); const redirectUrl = yield callVerificationApi(oidcUrl, config2.publicKey); if (!redirectUrl || typeof redirectUrl !== "string") { throw new Error("Invalid redirect URL returned from verification API."); } window.location.href = redirectUrl; } catch (err) { console.error("[UNQVerify] Failed to start verification:", err); (_b = config2.onFailure) == null ? void 0 : _b.call(config2, err instanceof Error ? err : new Error(String(err))); } }); } function startVerificationWithPopup(providedPopup) { return __async(this, null, function* () { var _a, _b; const config2 = getConfig(); let popup = null; let shouldClosePopup = false; let hasCompleted = false; try { const url = buildOidcUrl(config2); const redirectUrl = yield callVerificationApi(url, config2.publicKey); if (providedPopup && !providedPopup.closed) { popup = providedPopup; popup.location.href = redirectUrl; shouldClosePopup = false; } else { popup = window.open( redirectUrl, "unqverify-popup", "width=500,height=650" ); shouldClosePopup = true; if (!popup) { console.warn("[UNQVerify] Popup blocked"); (_a = config2.onFailure) == null ? void 0 : _a.call( config2, new Error( "Popup blocked by browser. Please allow popups and try again." ) ); return; } } const listener = (event) => { var _a2, _b2; if (((_a2 = event.data) == null ? void 0 : _a2.type) === "UNQVERIFY_RESULT") { hasCompleted = true; try { config2.onVerified(event.data.payload); window.dispatchEvent(new CustomEvent("unqverify:updated")); } catch (eventErr) { console.error("[UNQVerify] Error handling message event:", eventErr); (_b2 = config2.onFailure) == null ? void 0 : _b2.call( config2, eventErr instanceof Error ? eventErr : new Error(String(eventErr)) ); } finally { window.removeEventListener("message", listener); if (shouldClosePopup && popup) { try { popup.close(); } catch (closeErr) { console.error("[UNQVerify] Failed to close popup:", closeErr); } } } } }; window.addEventListener("message", listener); const popupChecker = setInterval(() => { var _a2; if (popup == null ? void 0 : popup.closed) { clearInterval(popupChecker); window.removeEventListener("message", listener); if (!hasCompleted) { (_a2 = config2.onFailure) == null ? void 0 : _a2.call( config2, new Error("User closed the popup before completing verification.") ); } } }, 500); } catch (err) { if (shouldClosePopup && popup) { try { popup.close(); } catch (closeErr) { console.error("[UNQVerify] Failed to close popup:", closeErr); } } console.error("[UNQVerify] Failed to initiate popup verification:", err); (_b = config2.onFailure) == null ? void 0 : _b.call(config2, err instanceof Error ? err : new Error(String(err))); } }); } // src/verify/decodeAndStoreToken.ts var import_jose = require("jose"); var import_jwt_decode2 = require("jwt-decode"); function getJwksUrlFromIssuer(issuer) { if (issuer === "https://test.aldersverificering.dk") { return "https://test.api.aldersverificering.dk/well-known/openid-configuration/jwks"; } if (issuer === "https://aldersverificering.dk") { return "https://api.aldersverificering.dk/well-known/openid-configuration/jwks"; } return null; } function decodeAndStoreToken(token) { return __async(this, null, function* () { try { const decoded = (0, import_jwt_decode2.jwtDecode)(token); const jwksUrl = getJwksUrlFromIssuer(decoded.iss); if (!jwksUrl) { console.warn("[UNQVerify] Unknown issuer:", decoded.iss); return false; } const JWKS = (0, import_jose.createRemoteJWKSet)(new URL(jwksUrl)); const { payload } = yield (0, import_jose.jwtVerify)(token, JWKS, { algorithms: ["RS256"] }); const { aldersverificeringdk_verification_result, exp } = payload; if (!aldersverificeringdk_verification_result || typeof exp !== "number") { console.warn( "[UNQVerify] Token is valid but user is not verified or exp is missing" ); return false; } const secondsToExpiry = exp - Math.floor(Date.now() / 1e3); if (secondsToExpiry <= 0) return false; setCookie(VERIFICATION_COOKIE_KEY, token, secondsToExpiry); return true; } catch (err) { console.error("[UNQVerify] Failed to verify JWT with JWKS:", err); return false; } }); } // src/verify/handleRedirectResult.ts var import_jwt_decode3 = require("jwt-decode"); function handleRedirectResult(_0) { return __async(this, arguments, function* ({ onVerified, onFailure }) { try { const url = new URL(window.location.href); const token = url.searchParams.get("jwt"); if (!token) { throw new Error("No JWT found in URL"); } const verified = yield decodeAndStoreToken(token); if (!verified) { throw new Error("JWT verification failed"); } url.searchParams.delete("jwt"); window.history.replaceState({}, document.title, url.toString()); const payload = (0, import_jwt_decode3.jwtDecode)(token); onVerified(payload); } catch (err) { console.error("[UNQVerify] handleRedirectResult failed:", err); onFailure == null ? void 0 : onFailure(err); } }); } // src/verify/getVerifiedAge.ts var import_jwt_decode4 = require("jwt-decode"); function getVerifiedAge() { try { const token = getCookie(VERIFICATION_COOKIE_KEY); if (!token) return null; const decoded = (0, import_jwt_decode4.jwtDecode)(token); const now = Math.floor(Date.now() / 1e3); const isTokenValid = decoded.exp >= now; const isVerified2 = decoded.aldersverificeringdk_verification_result; if (isTokenValid && isVerified2) { return decoded.aldersverificeringdk_verification_age; } return null; } catch (err) { console.warn("[UNQVerify] Failed to extract verified age from token:", err); return null; } } // src/verify/resetVerification.ts function resetVerification() { deleteCookie(VERIFICATION_COOKIE_KEY); console.info("[UNQVerify] Verification token cleared"); } // src/index.ts function init(config2) { setConfig(config2); } // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { getVerifiedAge, handleRedirectResult, init, isVerified, resetVerification, startVerificationWithPopup, startVerificationWithRedirect });