UNPKG

@unqtech/age-verification-mitid

Version:

Frontend SDK for age verification via UNQVerify using MitID

352 lines (339 loc) 11 kB
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/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 import { jwtDecode } from "jwt-decode"; function isVerified() { try { const token = getCookie(VERIFICATION_COOKIE_KEY); if (!token) return false; const decoded = 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 import { jwtVerify, createRemoteJWKSet } from "jose"; import { jwtDecode as jwtDecode2 } from "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 = jwtDecode2(token); const jwksUrl = getJwksUrlFromIssuer(decoded.iss); if (!jwksUrl) { console.warn("[UNQVerify] Unknown issuer:", decoded.iss); return false; } const JWKS = createRemoteJWKSet(new URL(jwksUrl)); const { payload } = yield 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 import { jwtDecode as jwtDecode3 } from "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 = jwtDecode3(token); onVerified(payload); } catch (err) { console.error("[UNQVerify] handleRedirectResult failed:", err); onFailure == null ? void 0 : onFailure(err); } }); } // src/verify/getVerifiedAge.ts import { jwtDecode as jwtDecode4 } from "jwt-decode"; function getVerifiedAge() { try { const token = getCookie(VERIFICATION_COOKIE_KEY); if (!token) return null; const decoded = jwtDecode4(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); } export { getVerifiedAge, handleRedirectResult, init, isVerified, resetVerification, startVerificationWithPopup, startVerificationWithRedirect };