Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 54 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,10 @@ hex = "0.4"
# Password hashing for admin user accounts (argon2id). `std` pulls in
# the OsRng salt generator via password-hash.
argon2 = { version = "0.5", features = ["std"] }
# TOTP enrollment (#1005 slice 2). Keep QR rendering separate so
# `totp-rs` does not pull its image stack into the server binary.
totp-rs = { version = "5.7.2", features = ["gen_secret", "otpauth"] }
qrcode = { version = "0.14.1", default-features = false, features = ["svg"] }

# Testing
pretty_assertions = "1.4"
Expand Down
5 changes: 5 additions & 0 deletions crates/ruscker-admin/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,11 @@ base64 = { workspace = true }
# User accounts — password hashing (argon2id)
argon2 = { workspace = true }

# User TOTP enrollment + dependency-light inline SVG QR rendering (#1005).
totp-rs = { workspace = true }
qrcode = { workspace = true }
ring = { workspace = true }

# Async trait for the SessionStore seam (in-memory now, Postgres in HA)
async-trait = { workspace = true }

Expand Down
36 changes: 36 additions & 0 deletions crates/ruscker-admin/assets/i18n/en/landing.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -993,3 +993,39 @@ admin-schedules-flash-toggled = Schedule updated.
admin-schedules-flash-bad-cron = Invalid cron expression — use the 5-field form, e.g. "0 3 * * *".
admin-schedules-flash-bad-spec = Unknown app, or the app has no container image to run.
admin-schedules-flash-error = The operation failed — check the server logs.

# — TOTP / two-factor authentication (#1005)
chrome-mfa = Two-factor authentication
admin-mfa-title = Two-factor authentication
admin-mfa-help = Protect your account with temporary codes from an authenticator app.
admin-mfa-error-password = Your current password is incorrect.
admin-mfa-error-key = RUSCKER_MASTER_KEY is not configured. Configure it and restart Ruscker before enrolling 2FA.
admin-mfa-break-glass = Break-glass token sessions have no account or password and cannot enroll 2FA. Sign in with a username and password.
admin-mfa-already = 2FA is already configured. An administrator must reset it before you can enroll again.
admin-mfa-enrolled = 2FA configured
admin-mfa-enrolled-since = Configured since
admin-mfa-reenroll-note = To change authenticator apps, ask an administrator to reset your 2FA and then enroll again.
admin-mfa-not-enrolled = 2FA not configured
admin-mfa-pending-note = An enrollment is incomplete. Enter your password to restart with a new key.
admin-mfa-current-password = Current password
admin-mfa-start = Set up 2FA
admin-mfa-setup-title = Link your authenticator app
admin-mfa-setup-help = Scan the QR code in your app, then enter the 6-digit code it generates.
admin-mfa-error-rate = Too many incorrect attempts. Wait one minute and try again.
admin-mfa-error-code = Incorrect or expired code. Check your device clock and try again.
admin-mfa-manual-title = Manual setup key
admin-mfa-manual-help = If you cannot scan the QR code, enter this key in your authenticator app.
admin-mfa-profile = Profile: SHA-1, 6 digits, 30-second period.
admin-mfa-code-label = 6-digit code
admin-mfa-confirm = Confirm and enable
admin-mfa-recovery-title = Save your recovery codes
admin-mfa-recovery-warning = These codes are shown only once. Copy or save them somewhere safe now.
admin-mfa-recovery-help = Each code can be used only once if you lose access to your authenticator app.
admin-mfa-continue = Continue
admin-users-mfa-section = Two-factor authentication
admin-users-mfa-configured = 2FA configured since
admin-users-mfa-reset-hint = Resetting deletes the key and all recovery codes. The user will need to enroll 2FA again.
admin-users-mfa-reset-confirm = Reset this user's 2FA? The key and ALL recovery codes will be deleted immediately.
admin-users-mfa-reset = Reset 2FA
admin-users-mfa-not-configured = 2FA not configured
admin-users-mfa-reset-ok = 2FA and its recovery codes were reset.
36 changes: 36 additions & 0 deletions crates/ruscker-admin/assets/i18n/es/landing.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -993,3 +993,39 @@ admin-schedules-flash-toggled = Programación actualizada.
admin-schedules-flash-bad-cron = Expresión cron no válida — usa la forma de 5 campos, p. ej. "0 3 * * *".
admin-schedules-flash-bad-spec = App desconocida, o la app no tiene imagen de contenedor que ejecutar.
admin-schedules-flash-error = La operación falló — revisa los logs del servidor.

# — TOTP / autenticación de dos factores (#1005)
chrome-mfa = Autenticación de dos factores
admin-mfa-title = Autenticación de dos factores
admin-mfa-help = Protege tu cuenta con códigos temporales de una aplicación de autenticación.
admin-mfa-error-password = La contraseña actual es incorrecta.
admin-mfa-error-key = RUSCKER_MASTER_KEY no está configurada. Configúrala y reinicia Ruscker antes de registrar el 2FA.
admin-mfa-break-glass = Las sesiones de emergencia por token no tienen cuenta ni contraseña y no pueden configurar 2FA. Inicia sesión con usuario y contraseña.
admin-mfa-already = El 2FA ya está configurado. Un administrador debe restablecerlo antes de volver a configurarlo.
admin-mfa-enrolled = 2FA configurado
admin-mfa-enrolled-since = Configurado desde
admin-mfa-reenroll-note = Para cambiar de aplicación, pide a un administrador que restablezca tu 2FA y vuelve a configurarlo.
admin-mfa-not-enrolled = 2FA no configurado
admin-mfa-pending-note = Hay una configuración incompleta. Introduce tu contraseña para comenzar de nuevo con una clave nueva.
admin-mfa-current-password = Contraseña actual
admin-mfa-start = Configurar 2FA
admin-mfa-setup-title = Vincula tu aplicación de autenticación
admin-mfa-setup-help = Escanea el código QR en la aplicación e introduce el código de 6 dígitos que genera.
admin-mfa-error-rate = Demasiados intentos incorrectos. Espera un minuto e inténtalo de nuevo.
admin-mfa-error-code = Código incorrecto o caducado. Comprueba el reloj del dispositivo e inténtalo de nuevo.
admin-mfa-manual-title = Clave de configuración manual
admin-mfa-manual-help = Si no puedes escanear el QR, introduce esta clave en la aplicación de autenticación.
admin-mfa-profile = Perfil: SHA-1, 6 dígitos, periodo de 30 segundos.
admin-mfa-code-label = Código de 6 dígitos
admin-mfa-confirm = Confirmar y activar
admin-mfa-recovery-title = Guarda tus códigos de recuperación
admin-mfa-recovery-warning = Estos códigos se muestran una sola vez. Cópialos o guárdalos ahora en un lugar seguro.
admin-mfa-recovery-help = Cada código puede usarse una sola vez si pierdes acceso a la aplicación de autenticación.
admin-mfa-continue = Continuar
admin-users-mfa-section = Autenticación de dos factores
admin-users-mfa-configured = 2FA configurado desde
admin-users-mfa-reset-hint = El restablecimiento elimina la clave y todos los códigos de recuperación. El usuario deberá configurar 2FA de nuevo.
admin-users-mfa-reset-confirm = ¿Restablecer el 2FA de este usuario? La clave y TODOS los códigos de recuperación se eliminarán inmediatamente.
admin-users-mfa-reset = Restablecer 2FA
admin-users-mfa-not-configured = 2FA no configurado
admin-users-mfa-reset-ok = Se restablecieron el 2FA y sus códigos de recuperación.
36 changes: 36 additions & 0 deletions crates/ruscker-admin/assets/i18n/fr/landing.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -993,3 +993,39 @@ admin-schedules-flash-toggled = Planification mise à jour.
admin-schedules-flash-bad-cron = Expression cron invalide — utilisez la forme à 5 champs, p. ex. "0 3 * * *".
admin-schedules-flash-bad-spec = App inconnue, ou l'app n'a pas d'image de conteneur à exécuter.
admin-schedules-flash-error = L'opération a échoué — consultez les logs du serveur.

# — TOTP / authentification à deux facteurs (#1005)
chrome-mfa = Authentification à deux facteurs
admin-mfa-title = Authentification à deux facteurs
admin-mfa-help = Protégez votre compte avec les codes temporaires d’une application d’authentification.
admin-mfa-error-password = Votre mot de passe actuel est incorrect.
admin-mfa-error-key = RUSCKER_MASTER_KEY n’est pas configurée. Configurez-la et redémarrez Ruscker avant d’inscrire la 2FA.
admin-mfa-break-glass = Les sessions d’urgence par jeton n’ont ni compte ni mot de passe et ne peuvent pas configurer la 2FA. Connectez-vous avec un nom d’utilisateur et un mot de passe.
admin-mfa-already = La 2FA est déjà configurée. Un administrateur doit la réinitialiser avant une nouvelle inscription.
admin-mfa-enrolled = 2FA configurée
admin-mfa-enrolled-since = Configurée depuis
admin-mfa-reenroll-note = Pour changer d’application, demandez à un administrateur de réinitialiser votre 2FA, puis configurez-la à nouveau.
admin-mfa-not-enrolled = 2FA non configurée
admin-mfa-pending-note = Une inscription est incomplète. Saisissez votre mot de passe pour recommencer avec une nouvelle clé.
admin-mfa-current-password = Mot de passe actuel
admin-mfa-start = Configurer la 2FA
admin-mfa-setup-title = Associez votre application d’authentification
admin-mfa-setup-help = Scannez le code QR dans l’application, puis saisissez le code à 6 chiffres qu’elle génère.
admin-mfa-error-rate = Trop de tentatives incorrectes. Attendez une minute et réessayez.
admin-mfa-error-code = Code incorrect ou expiré. Vérifiez l’heure de l’appareil et réessayez.
admin-mfa-manual-title = Clé de configuration manuelle
admin-mfa-manual-help = Si vous ne pouvez pas scanner le QR, saisissez cette clé dans l’application d’authentification.
admin-mfa-profile = Profil : SHA-1, 6 chiffres, période de 30 secondes.
admin-mfa-code-label = Code à 6 chiffres
admin-mfa-confirm = Confirmer et activer
admin-mfa-recovery-title = Enregistrez vos codes de récupération
admin-mfa-recovery-warning = Ces codes ne sont affichés qu’une seule fois. Copiez-les ou conservez-les maintenant dans un lieu sûr.
admin-mfa-recovery-help = Chaque code ne peut être utilisé qu’une fois si vous perdez l’accès à l’application d’authentification.
admin-mfa-continue = Continuer
admin-users-mfa-section = Authentification à deux facteurs
admin-users-mfa-configured = 2FA configurée depuis
admin-users-mfa-reset-hint = La réinitialisation supprime la clé et tous les codes de récupération. L’utilisateur devra configurer à nouveau la 2FA.
admin-users-mfa-reset-confirm = Réinitialiser la 2FA de cet utilisateur ? La clé et TOUS les codes de récupération seront supprimés immédiatement.
admin-users-mfa-reset = Réinitialiser la 2FA
admin-users-mfa-not-configured = 2FA non configurée
admin-users-mfa-reset-ok = La 2FA et ses codes de récupération ont été réinitialisés.
36 changes: 36 additions & 0 deletions crates/ruscker-admin/assets/i18n/pt/landing.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -997,3 +997,39 @@ admin-schedules-flash-toggled = Agendamento atualizado.
admin-schedules-flash-bad-cron = Expressão cron inválida — use a forma de 5 campos, ex.: "0 3 * * *".
admin-schedules-flash-bad-spec = App desconhecido, ou o app não tem imagem de container para executar.
admin-schedules-flash-error = A operação falhou — verifique os logs do servidor.

# — TOTP / autenticação em dois fatores (#1005)
chrome-mfa = Autenticação em dois fatores
admin-mfa-title = Autenticação em dois fatores
admin-mfa-help = Proteja sua conta com códigos temporários de um aplicativo autenticador.
admin-mfa-error-password = A senha atual está incorreta.
admin-mfa-error-key = RUSCKER_MASTER_KEY não está configurada. Configure-a e reinicie o Ruscker antes de cadastrar o 2FA.
admin-mfa-break-glass = Sessões de emergência por token não têm uma conta nem senha e não podem configurar 2FA. Entre com usuário e senha.
admin-mfa-already = O 2FA já está configurado. Um administrador precisa redefini-lo antes de um novo cadastro.
admin-mfa-enrolled = 2FA configurado
admin-mfa-enrolled-since = Configurado desde
admin-mfa-reenroll-note = Para trocar de aplicativo autenticador, peça a um administrador para redefinir seu 2FA e cadastre-o novamente.
admin-mfa-not-enrolled = 2FA não configurado
admin-mfa-pending-note = Há um cadastro incompleto. Informe sua senha para começar novamente com uma nova chave.
admin-mfa-current-password = Senha atual
admin-mfa-start = Configurar 2FA
admin-mfa-setup-title = Vincule seu aplicativo autenticador
admin-mfa-setup-help = Leia o QR code no aplicativo e informe abaixo o código de 6 dígitos gerado por ele.
admin-mfa-error-rate = Muitas tentativas incorretas. Aguarde um minuto e tente novamente.
admin-mfa-error-code = Código incorreto ou expirado. Confira o relógio do dispositivo e tente novamente.
admin-mfa-manual-title = Chave para cadastro manual
admin-mfa-manual-help = Se não puder ler o QR code, digite esta chave no aplicativo autenticador.
admin-mfa-profile = Perfil: SHA-1, 6 dígitos, período de 30 segundos.
admin-mfa-code-label = Código de 6 dígitos
admin-mfa-confirm = Confirmar e ativar
admin-mfa-recovery-title = Salve seus códigos de recuperação
admin-mfa-recovery-warning = Estes códigos aparecem uma única vez. Copie ou guarde-os agora em um local seguro.
admin-mfa-recovery-help = Cada código pode ser usado somente uma vez caso você perca acesso ao aplicativo autenticador.
admin-mfa-continue = Continuar
admin-users-mfa-section = Autenticação em dois fatores
admin-users-mfa-configured = 2FA configurado desde
admin-users-mfa-reset-hint = A redefinição apaga a chave e todos os códigos de recuperação. O usuário precisará cadastrar o 2FA novamente.
admin-users-mfa-reset-confirm = Redefinir o 2FA deste usuário? A chave e TODOS os códigos de recuperação serão apagados imediatamente.
admin-users-mfa-reset = Redefinir 2FA
admin-users-mfa-not-configured = 2FA não configurado
admin-users-mfa-reset-ok = O 2FA e os códigos de recuperação foram redefinidos.
26 changes: 26 additions & 0 deletions crates/ruscker-admin/migrations-pg/0029_user_mfa.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
-- Postgres twin of migrations/0029_user_mfa.sql (#1005 slice 2).
CREATE TABLE user_mfa (
username TEXT PRIMARY KEY REFERENCES users(username) ON DELETE CASCADE,
secret_enc BYTEA NOT NULL,
secret_nonce BYTEA NOT NULL,
-- Random per-enrollment ceremony token: binds the pending secret to
-- the browser that passed the password re-auth (cookie) and makes the
-- confirm UPDATE conditional, so a racing re-start can never get its
-- replacement secret confirmed by a code proving the old one.
ceremony TEXT NOT NULL,
confirmed_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL,
updated_at TIMESTAMPTZ NOT NULL,
-- Reserved for slice 3's TOTP replay prevention.
last_used_step BIGINT
);

CREATE TABLE user_mfa_recovery (
id TEXT PRIMARY KEY,
username TEXT NOT NULL REFERENCES users(username) ON DELETE CASCADE,
code_hash TEXT NOT NULL,
used_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL
);
CREATE INDEX idx_user_mfa_recovery_username
ON user_mfa_recovery(username, used_at);
29 changes: 29 additions & 0 deletions crates/ruscker-admin/migrations/0029_user_mfa.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
-- User-owned TOTP factors and one-time recovery codes (#1005 slice 2).
-- `confirmed_at IS NULL` is a pending enrollment awaiting its first valid
-- authenticator code. Secrets are AES-256-GCM ciphertext + nonce; plaintext
-- never reaches the database.
CREATE TABLE user_mfa (
username TEXT PRIMARY KEY REFERENCES users(username) ON DELETE CASCADE,
secret_enc BLOB NOT NULL,
secret_nonce BLOB NOT NULL,
-- Random per-enrollment ceremony token: binds the pending secret to
-- the browser that passed the password re-auth (cookie) and makes the
-- confirm UPDATE conditional, so a racing re-start can never get its
-- replacement secret confirmed by a code proving the old one.
ceremony TEXT NOT NULL,
confirmed_at TEXT,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
-- Reserved for slice 3's TOTP replay prevention.
last_used_step BIGINT
);

CREATE TABLE user_mfa_recovery (
id TEXT PRIMARY KEY,
username TEXT NOT NULL REFERENCES users(username) ON DELETE CASCADE,
code_hash TEXT NOT NULL,
used_at TEXT,
created_at TEXT NOT NULL
);
CREATE INDEX idx_user_mfa_recovery_username
ON user_mfa_recovery(username, used_at);
3 changes: 3 additions & 0 deletions crates/ruscker-admin/src/db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ pub mod export;
pub mod images;
pub mod landing;
pub mod landing_blocks;
pub mod mfa;
pub mod ruscker_images;
pub mod schedules;
pub mod settings;
Expand Down Expand Up @@ -250,6 +251,8 @@ mod pg_tests {
"landing_customization",
"spec_versions",
"specs",
"user_mfa",
"user_mfa_recovery",
"users",
] {
assert!(present.contains(expected), "missing table: {expected}");
Expand Down
Loading