@unqtech/age-verification-mitid
Version:
Frontend SDK for age verification via UNQVerify using MitID
352 lines (339 loc) • 11 kB
JavaScript
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
};