feat(auth): account lifecycle — Google linking, passwords, suspension & deletion plumbing

- Link a Google account to a password account, and adopt the Google identity onto a matching
  email account instead of 500ing on a duplicate; set or change a password from Settings.
- Expose has_google/has_password on /api/me for the Sign-in methods UI.
- Mark Google logins email-verified (backfill existing rows, migration 0024); stop a routine
  login from clobbering an admin-assigned role (env ADMIN_EMAILS stays the bootstrap admin).
- Suspension login-gates (password + Google callback + current_user) with a rate-limited
  'suspended' notice; shared purge_user (cascade delete + access-request cleanup + Google-grant
  revoke) behind self- and admin-deletion; single app_base source for user-facing email links.
This commit is contained in:
npeter83 2026-06-19 19:52:02 +02:00
parent c0dde06920
commit 2aa13a6433
9 changed files with 507 additions and 43 deletions

View file

@ -61,7 +61,32 @@
"deleteTitle": "Delete your account?",
"deleteConfirm": "This permanently erases your account and all your data (subscriptions, tags, watch/saved/hidden state, playlists, settings). It can't be undone.",
"deleteConfirmButton": "Delete everything",
"demoNotice": "This is the shared demo account. It has no YouTube connection — you can browse the whole shared library, filter and sort it, build playlists and try things out, but nothing touches a real YouTube account."
"demoNotice": "This is the shared demo account. It has no YouTube connection — you can browse the whole shared library, filter and sort it, build playlists and try things out, but nothing touches a real YouTube account.",
"signInMethods": "Sign-in methods",
"googleLink": {
"title": "Google account",
"connected": "Connected",
"connectedHint": "Your Google account is linked. You can sign in with Google and grant YouTube access below.",
"connectHint": "Link a Google account to sign in with Google and to enable YouTube access for your feed.",
"connect": "Connect Google",
"linked": "Google account linked.",
"conflict": "That Google account is already linked to a different Siftlode account.",
"mismatch": "That's a different Google account than the one already linked here. Sign in with the linked one.",
"error": "Couldn't link the Google account. Please try again."
},
"password": {
"title": "Password",
"setHint": "A password is set. You can sign in with your email and password.",
"unsetHint": "No password yet — set one to sign in with your email instead of (or as well as) Google.",
"set": "Set password",
"change": "Change password",
"current": "Current password",
"new": "New password (min. 10 characters)",
"save": "Save password",
"saving": "Saving…",
"saved": "Password updated for {{email}}.",
"failed": "Couldn't update the password."
}
},
"demo": {
"title": "Demo access",
@ -87,7 +112,7 @@
"addPlaceholder": "Add an email directly…",
"add": "Add",
"decided": "{{count}} decided",
"approved": "Approved — they can sign in now",
"approved": "Approved {{email}} — they can sign in now",
"approveFailed": "Approve failed",
"denyFailed": "Deny failed",
"addedToWhitelist": "Added to the whitelist",