Added 2FA

This commit is contained in:
2026-08-08 15:09:26 +02:00
parent b5de4d6ac7
commit 9f311ab4ae
19 changed files with 1341 additions and 1 deletions
@@ -26,6 +26,17 @@ public class User {
@Column(name = "enabled", nullable = false)
private boolean enabled = true;
@Column(name = "totp_secret")
private @Nullable String totpSecret;
@Column(name = "two_factor_enabled", nullable = false)
private boolean twoFactorEnabled = false;
// The most recently accepted TOTP time step, tracked to reject replay of an
// observed code within its own validity window (see TwoFactorService#verify).
@Column(name = "totp_last_used_step")
private @Nullable Long totpLastUsedTimeStep;
@ElementCollection(fetch = FetchType.EAGER)
@CollectionTable(name = "app_user_role", joinColumns = @JoinColumn(name = "user_id"))
@Column(name = "role", nullable = false)
@@ -85,6 +96,36 @@ public class User {
roles.add(role);
}
public @Nullable String getTotpSecret() {
return totpSecret;
}
public boolean isTwoFactorEnabled() {
return twoFactorEnabled;
}
public @Nullable Long getTotpLastUsedTimeStep() {
return totpLastUsedTimeStep;
}
/**
* Enables two-factor authentication with the given TOTP secret. The caller
* must have already verified a code against this secret before calling
* this method — it does not verify anything itself.
*/
public void enableTwoFactor(String totpSecret) {
if (totpSecret.isBlank()) {
throw new IllegalArgumentException("TOTP secret must not be blank");
}
this.totpSecret = totpSecret;
this.twoFactorEnabled = true;
this.totpLastUsedTimeStep = null;
}
public void recordAcceptedTotpTimeStep(long timeStep) {
this.totpLastUsedTimeStep = timeStep;
}
@Override
public boolean equals(Object obj) {
if (obj == null || !getClass().isAssignableFrom(obj.getClass())) {