feat: Add two-factor authentication via TOTP

Adds TOTP setup with QR code, login challenge, recovery codes and
trusted devices that let a browser skip the second factor for 30
days. Admins can reset another user's second factor by confirming
with their own password.
This commit is contained in:
Daniel Hiller
2026-08-07 20:11:55 +02:00
parent 36aa732fec
commit 2e4904f77d
74 changed files with 4173 additions and 4 deletions
+14
View File
@@ -36,10 +36,24 @@ const updateTermsLanguage = (value) => ({
},
});
const verifyTotp = (data) => ({
type: EntryActionTypes.TOTP_VERIFY,
payload: {
data,
},
});
const cancelTotpChallenge = () => ({
type: EntryActionTypes.TOTP_CHALLENGE_CANCEL,
payload: {},
});
export default {
authenticate,
clearAuthenticateError,
acceptTerms,
cancelTerms,
updateTermsLanguage,
verifyTotp,
cancelTotpChallenge,
};
+67
View File
@@ -175,6 +175,64 @@ const clearUserApiKeyValue = (id) => ({
},
});
const setupCurrentUserTotp = (data) => ({
type: EntryActionTypes.CURRENT_USER_TOTP_SETUP,
payload: {
data,
},
});
const clearCurrentUserTotpSetupValue = () => ({
type: EntryActionTypes.CURRENT_USER_TOTP_SETUP_VALUE_CLEAR,
payload: {},
});
const enableCurrentUserTotp = (data) => ({
type: EntryActionTypes.CURRENT_USER_TOTP_ENABLE,
payload: {
data,
},
});
const disableCurrentUserTotp = (data) => ({
type: EntryActionTypes.CURRENT_USER_TOTP_DISABLE,
payload: {
data,
},
});
const disableUserTotp = (id, data) => ({
type: EntryActionTypes.USER_TOTP_DISABLE,
payload: {
id,
data,
},
});
const regenerateCurrentUserTotpRecoveryCodes = (data) => ({
type: EntryActionTypes.CURRENT_USER_TOTP_RECOVERY_CODES_REGENERATE,
payload: {
data,
},
});
const clearCurrentUserTotpRecoveryCodes = () => ({
type: EntryActionTypes.CURRENT_USER_TOTP_RECOVERY_CODES_CLEAR,
payload: {},
});
const fetchCurrentUserTrustedDevices = () => ({
type: EntryActionTypes.CURRENT_USER_TRUSTED_DEVICES_FETCH,
payload: {},
});
const deleteCurrentUserTrustedDevice = (deviceId) => ({
type: EntryActionTypes.CURRENT_USER_TRUSTED_DEVICE_DELETE,
payload: {
deviceId,
},
});
const deleteUser = (id) => ({
type: EntryActionTypes.USER_DELETE,
payload: {
@@ -284,6 +342,15 @@ export default {
createUserApiKey,
deleteUserApiKey,
clearUserApiKeyValue,
setupCurrentUserTotp,
clearCurrentUserTotpSetupValue,
enableCurrentUserTotp,
disableCurrentUserTotp,
disableUserTotp,
regenerateCurrentUserTotpRecoveryCodes,
clearCurrentUserTotpRecoveryCodes,
fetchCurrentUserTrustedDevices,
deleteCurrentUserTrustedDevice,
deleteUser,
handleUserDelete,
addUserToCard,