Vendor-neutral age verification, with IMVU as the first provider
CI / test (push) Successful in 35s

Age checks are recorded in one table (age_check) by provider, method and result, never with documents or
birth dates; one rule decides whether an IMVU account counts as adult (its person's check, or its own).
Providers implement a small interface (start, webhook or browser completion, re-check) and are chosen by
configuration, so phone wallets and Didit can be added, or swapped, as adapters. IMVU's answer per account
is re-checked when it expires (about daily). Existing links' results move into the new table.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
dhkatzandClaude Opus 5.5 committed 2026-10-29 05:44:36 -07:00
1 parent 339ba8e2d6
commit 7db9fc0370
11 files changed
+1602 -48

No files matched your search

+35
View File
@@ -0,0 +1,35 @@
// Age from IMVU itself: an IMVU account that IMVU has age verified, or that has Access Pass, counts as adult. Read
// from IMVU's public API; IMVU can change it (verification revoked, Access Pass lapsed), so it's checked again
// about daily.
import type { ImvuClient } from "../imvu";
import type { AgeResult, Provider, Subject } from "./index";
const recheckAfter = 24 * 60 * 60 * 1000;
export function imvuAgeProvider(imvu: ImvuClient): Provider {
const check = async (subject: Subject): Promise<AgeResult> => {
if (subject.imvuId === undefined) throw new Error("IMVU checks an IMVU account");
const u = await imvu.byId(subject.imvuId);
return {
overEighteen: u.ageVerified || u.accessPass,
method: u.ageVerified ? "imvu_age_verified" : u.accessPass ? "imvu_access_pass" : "imvu_none",
expiresAt: new Date(Date.now() + recheckAfter),
};
};
return {
id: "imvu",
label: "IMVU's age verification or Access Pass",
scope: "imvu_account",
start: async (subject) => ({ kind: "done", result: await check(subject) }),
recheck: check,
};
}
/** The IMVU result from a lookup already made (when linking). */
export function imvuResult(u: { ageVerified: boolean; accessPass: boolean }): AgeResult {
return {
overEighteen: u.ageVerified || u.accessPass,
method: u.ageVerified ? "imvu_age_verified" : u.accessPass ? "imvu_access_pass" : "imvu_none",
expiresAt: new Date(Date.now() + recheckAfter),
};
}