feat: Passkey (WebAuthn) login (#1111)

* feat(auth): passkey (WebAuthn) login — server endpoints, schema + admin toggle

Add @simplewebauthn/server registration and primary (discoverable) login ceremonies under /api/auth/passkey, a webauthn_credentials + single-use webauthn_challenges schema (migration), the instance-wide passkey_login toggle (default off) enforced before auth by a guard, and require_mfa satisfaction via a verified passkey. RP ID/origin come only from server config (webauthn_rp_id/origins -> APP_URL), never request headers.

* feat(auth): passkey enrolment, login button + admin settings UI

PasskeysSection in account settings (add/rename/remove with a current-password step-up), a 'Sign in with a passkey' button on the login page, the admin enable + RP-ID/origins controls, and a per-user admin reset action.

* i18n(auth): passkey strings across all locales

Add login/settings/admin passkey keys to en and all 19 translated locales.
This commit is contained in:
Maurice
2026-06-05 18:54:13 +02:00
committed by GitHub
parent 247433fb2a
commit a876fb2634
83 changed files with 2421 additions and 8 deletions
+19
View File
@@ -358,5 +358,24 @@ const admin: TranslationStrings = {
'admin.addons.catalog.journey.name': 'Cestovní deník',
'admin.addons.catalog.journey.description':
'Sledování cest a cestovní deník s odbaveními, fotkami a denními příběhy',
'admin.passkey.title': 'Přihlášení přístupovým klíčem',
'admin.passkey.cardHint':
'Umožněte uživatelům přihlašovat se pomocí přístupových klíčů (WebAuthn). Ve výchozím nastavení vypnuto.',
'admin.passkey.login': 'Povolit přihlášení přístupovým klíčem',
'admin.passkey.loginHint':
'Zobrazí možnost „Přihlásit se pomocí přístupového klíče“ a umožní uživatelům zaregistrovat přístupové klíče v nastavení.',
'admin.passkey.notConfigured':
'Pro toto nasazení zatím nelze určit žádnou doménu WebAuthn. Nastavte níže APP_URL nebo Relying Party ID — do té doby zůstanou přístupové klíče skryté.',
'admin.passkey.rpId': 'Relying Party ID (doména)',
'admin.passkey.rpIdHint':
'Holá doména, ke které jsou přístupové klíče vázány, např. trek.example.org. Ponechte prázdné, aby se odvodila z APP_URL. Pozdější změna zneplatní stávající přístupové klíče.',
'admin.passkey.origins': 'Povolené origins',
'admin.passkey.originsHint':
'Úplné origins oddělené čárkou, např. https://trek.example.org. Ponechte prázdné pro použití APP_URL.',
'admin.passkey.reset': 'Resetovat přístupové klíče',
'admin.passkey.resetHint':
'Odebere všechny přístupové klíče tohoto uživatele (např. při ztrátě zařízení). Stále se může přihlásit svým heslem.',
'admin.passkey.resetConfirm': 'Odebrat všechny přístupové klíče uživatele {name}?',
'admin.passkey.resetDone': 'Odebráno {count} přístupových klíčů',
};
export default admin;
+3
View File
@@ -91,5 +91,8 @@ const login: TranslationStrings = {
'login.resetPasswordInvalidLinkBody':
'Odkaz chybí nebo je poškozený. Pro pokračování si vyžádej nový.',
'login.resetPasswordFailed': 'Obnovení se nezdařilo. Odkaz mohl vypršet.',
'login.passkey.signIn': 'Přihlásit se pomocí přístupového klíče',
'login.passkey.failed':
'Přihlášení přístupovým klíčem se nezdařilo. Zkuste to prosím znovu.',
};
export default login;
+24
View File
@@ -297,6 +297,30 @@ const settings: TranslationStrings = {
'settings.notificationPreferences.ntfy': 'Ntfy',
"settings.currency": "Currency",
"settings.currencyHint": "All amounts in Costs are converted to and shown in this currency.",
'settings.passkey.title': 'Přístupové klíče',
'settings.passkey.description':
'Přihlašujte se rychleji a s ochranou proti phishingu pomocí přístupového klíče — otiskem prstu, obličejem, PINem nebo hardwarovým klíčem. Vaše heslo zůstává jako záloha.',
'settings.passkey.notConfigured':
'Přístupové klíče jsou povoleny, ale na tomto serveru zatím nejsou plně nastaveny. Požádejte správce o nastavení domény WebAuthn.',
'settings.passkey.add': 'Přidat přístupový klíč',
'settings.passkey.addTitle': 'Přidat přístupový klíč',
'settings.passkey.passwordPrompt':
'Potvrďte své současné heslo a poté postupujte podle pokynů svého zařízení.',
'settings.passkey.passwordRequired': 'Vaše současné heslo je vyžadováno.',
'settings.passkey.namePlaceholder': 'Název (volitelné, např. „iPhone“)',
'settings.passkey.addedToast': 'Přístupový klíč přidán',
'settings.passkey.added': 'Přidáno',
'settings.passkey.addError': 'Přístupový klíč se nepodařilo přidat',
'settings.passkey.cancelled': 'Nastavení přístupového klíče zrušeno',
'settings.passkey.deleted': 'Přístupový klíč odebrán',
'settings.passkey.deleteConfirm':
'Odebrat tento přístupový klíč? Potvrďte svým heslem.',
'settings.passkey.rename': 'Přejmenovat',
'settings.passkey.defaultName': 'Přístupový klíč',
'settings.passkey.synced': 'Synchronizováno',
'settings.passkey.deviceBound': 'Toto zařízení',
'settings.passkey.lastUsed': 'Naposledy použito',
'settings.passkey.neverUsed': 'Nikdy nepoužito',
};
export default settings;