feat(auth): admin invite links
Lint & Test / lint-and-check (push) Failing after 5m4s
Lint & Test / test (push) Has been skipped

Replaces the blunt registrationEnabled toggle with per-invite access.
Invites are tokenized, single-use, optionally locked to an email, can
grant user or admin role, and expire (default 7d, max 90d).

- Invite model with tokenHash (bcrypt), email, role, expiresAt,
  usedAt/usedByUserId.
- inviteService: create, list, revoke, findInviteByToken, consumeInvite.
  Token is shown exactly once at creation.
- /admin/invites page: list with status (Active/Used/Expired), generate
  with email lock + role + custom expiry, copy one-shot URL, revoke.
- /register?invite=TOKEN: accepts invite even when registrationEnabled
  is false; shows a banner; enforces email lock; applies the invite's
  role on creation; consumes the invite on success.
- Linked from the admin navbar.
This commit is contained in:
2026-04-16 04:00:18 +03:00
parent 9cab7262e6
commit 38335e925b
10 changed files with 530 additions and 11 deletions
+15
View File
@@ -41,6 +41,21 @@ model User {
@@index([email])
}
model Invite {
id String @id @default(cuid())
tokenHash String @unique
email String? // optional — lock the invite to a specific email
role String @default("user") // user | admin
expiresAt DateTime
usedAt DateTime?
usedByUserId String?
createdById String?
createdAt DateTime @default(now())
@@index([tokenHash])
@@index([createdById])
}
model Session {
id String @id @default(cuid())
userId String