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

+27 -3
View File
@@ -137,15 +137,39 @@ export const imvuLink = pgTable(
pairwiseId: text("pairwise_id").notNull().unique(),
username: text("username").notNull(),
linkedAt: timestamp("linked_at", { withTimezone: true }).notNull().defaultNow(),
ageVerified: boolean("age_verified").notNull().default(false),
accessPass: boolean("access_pass").notNull().default(false),
checkedAt: timestamp("checked_at", { withTimezone: true }).notNull().defaultNow(),
showBadge: boolean("show_badge").notNull().default(false),
showPresence: boolean("show_presence").notNull().default(false),
},
(t) => [index("imvu_link_user").on(t.userId)],
);
// An age check by one provider: of a person, or of one of their linked IMVU accounts. Only the outcome: never a
// birth date, document, photo or name (providers that see those keep them).
export const ageCheck = pgTable(
"age_check",
{
id: text("id").primaryKey(),
userId: text("user_id")
.notNull()
.references(() => user.id, { onDelete: "cascade" }),
// Set when the check is of one IMVU account (IMVU's own verification); null when it's of the person.
imvuId: bigint("imvu_id", { mode: "number" }),
provider: text("provider").notNull(), // "imvu", "didit", "wallet", ...
method: text("method").notNull().default(""), // e.g. "imvu_age_verified", "id_document", "wallet_mdoc"
status: text("status").notNull(), // "pending", "complete", "failed", "revoked"
overEighteen: boolean("over_eighteen").notNull().default(false),
providerRef: text("provider_ref"), // the provider's session or transaction ID
createdAt: createdAt(),
checkedAt: timestamp("checked_at", { withTimezone: true }),
expiresAt: timestamp("expires_at", { withTimezone: true }),
},
(t) => [
index("age_check_user").on(t.userId),
index("age_check_imvu").on(t.imvuId),
uniqueIndex("age_check_provider_ref").on(t.provider, t.providerRef),
],
);
// A code the person is to put in an IMVU account's tagline, proving it's theirs.
export const linkChallenge = pgTable(
"link_challenge",