feat: age verification with an ID document (Didit), and closed registration
Age: a Didit provider behind the vendor-neutral age interface. A person verifies once (ID document, passive liveness, face match) and all their linked IMVU accounts count as adult. Webhooks are checked against Didit's signatures; only "over 18" is kept. A later answer replaces an earlier one (an approval taken back is revoked). Checks are limited: one unfinished check per person, 3 in 30 days, none once verified, and a monthly cap (DIDIT_MONTHLY_LIMIT, default 30: Didit's free plan is $10 of credit a month and a check costs $0.30). The Account page gets an Age Verification card. Registration: open or closed, an admin's setting in the console (default from REGISTRATION). Closed: new accounts can only sign in until a moderator approves them (or declines, deleting the account); the website shows them a waiting page and the API treats them as signed out. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
86ebe976ba
commit
c75a4c9afc
28 files changed
+3260
-89
No files matched your search
@@ -1,5 +1,6 @@
|
||||
"use server";
|
||||
import { revalidatePath } from "next/cache";
|
||||
import { redirect } from "next/navigation";
|
||||
import { api, unwrap } from "@/lib/api";
|
||||
import { attempt, field, type ActionState } from "@/lib/actions";
|
||||
|
||||
@@ -52,3 +53,25 @@ export async function decideCatalog(_: ActionState, form: FormData): Promise<Act
|
||||
revalidatePath("/catalogs");
|
||||
});
|
||||
}
|
||||
|
||||
/** An account waiting for approval: approve it, or decline it (deleting it). */
|
||||
export async function decideAccount(_: ActionState, form: FormData): Promise<ActionState> {
|
||||
const id = field(form, "id");
|
||||
const decline = field(form, "decision") === "decline";
|
||||
const state = await attempt(async () => {
|
||||
if (decline) unwrap(await api.console.people({ id }).decline.post());
|
||||
else unwrap(await api.console.people({ id }).approve.post());
|
||||
revalidatePath("/console", "layout");
|
||||
});
|
||||
if (decline && !state?.error) redirect("/console/people?waiting=true");
|
||||
return state;
|
||||
}
|
||||
|
||||
export async function setRegistration(_: ActionState, form: FormData): Promise<ActionState> {
|
||||
return attempt(async () => {
|
||||
const registration = field(form, "registration") === "closed" ? "closed" : "open";
|
||||
unwrap(await api.console.settings.put({ registration }));
|
||||
revalidatePath("/console");
|
||||
return registration === "closed" ? "Registration closed: new accounts wait for approval." : "Registration open.";
|
||||
});
|
||||
}
|
||||
Reference in new issue
Block a user