feat: add passkey management and auth settings to TenantPortalService
This commit is contained in:
@@ -24,6 +24,7 @@ import java.time.temporal.ChronoUnit;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
@Service
|
||||
@@ -77,7 +78,7 @@ public class TenantPortalService {
|
||||
String serverEndpoint, Instant createdAt
|
||||
) {}
|
||||
|
||||
public record MfaStatusData(boolean enrolled, boolean hasBackupCodes) {}
|
||||
public record MfaStatusData(boolean enrolled, boolean hasBackupCodes, boolean passkeyEnrolled, int passkeyCount) {}
|
||||
|
||||
public record MfaSetupData(String secret, String secretQrCode) {}
|
||||
|
||||
@@ -297,7 +298,10 @@ public class TenantPortalService {
|
||||
.anyMatch(v -> "Totp".equals(String.valueOf(v.get("type"))));
|
||||
boolean hasBackupCodes = verifications.stream()
|
||||
.anyMatch(v -> "BackupCode".equals(String.valueOf(v.get("type"))));
|
||||
return new MfaStatusData(enrolled, hasBackupCodes);
|
||||
long passkeyCount = verifications.stream()
|
||||
.filter(v -> "WebAuthn".equals(String.valueOf(v.get("type"))))
|
||||
.count();
|
||||
return new MfaStatusData(enrolled, hasBackupCodes, passkeyCount > 0, (int) passkeyCount);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@@ -370,18 +374,87 @@ public class TenantPortalService {
|
||||
logtoClient.deleteAllMfaVerifications(userId);
|
||||
}
|
||||
|
||||
// --- Passkey methods ---
|
||||
|
||||
public record PasskeyCredential(String id, String name, String agent, String createdAt) {}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public List<PasskeyCredential> listPasskeys(String userId) {
|
||||
return logtoClient.getWebAuthnCredentials(userId).stream()
|
||||
.map(v -> new PasskeyCredential(
|
||||
String.valueOf(v.get("id")),
|
||||
v.get("name") != null ? String.valueOf(v.get("name")) : null,
|
||||
v.get("agent") != null ? String.valueOf(v.get("agent")) : null,
|
||||
v.get("createdAt") != null ? String.valueOf(v.get("createdAt")) : null
|
||||
))
|
||||
.toList();
|
||||
}
|
||||
|
||||
public void renamePasskey(String userId, String credentialId, String name) {
|
||||
var credentials = logtoClient.getWebAuthnCredentials(userId);
|
||||
boolean owns = credentials.stream()
|
||||
.anyMatch(v -> credentialId.equals(String.valueOf(v.get("id"))));
|
||||
if (!owns) {
|
||||
throw new IllegalArgumentException("Credential not found");
|
||||
}
|
||||
logtoClient.renameMfaVerification(userId, credentialId, name);
|
||||
}
|
||||
|
||||
public void deletePasskey(String userId, String credentialId) {
|
||||
var credentials = logtoClient.getWebAuthnCredentials(userId);
|
||||
boolean owns = credentials.stream()
|
||||
.anyMatch(v -> credentialId.equals(String.valueOf(v.get("id"))));
|
||||
if (!owns) {
|
||||
throw new IllegalArgumentException("Credential not found");
|
||||
}
|
||||
logtoClient.deleteMfaVerification(userId, credentialId);
|
||||
}
|
||||
|
||||
public void updateMfaMethodPreference(String userId, String preference) {
|
||||
logtoClient.updateUserCustomData(userId, Map.of("mfa_method_preference", preference));
|
||||
}
|
||||
|
||||
public void updateTenantSettings(Map<String, Object> updates) {
|
||||
TenantEntity tenant = resolveTenant();
|
||||
Map<String, Object> settings = new HashMap<>(
|
||||
tenant.getSettings() != null ? tenant.getSettings() : Map.of());
|
||||
// Only allow known keys
|
||||
if (updates.containsKey("mfaRequired")) {
|
||||
settings.put("mfaRequired", Boolean.TRUE.equals(updates.get("mfaRequired")));
|
||||
}
|
||||
if (updates.containsKey("mfaMode")) {
|
||||
String mode = String.valueOf(updates.get("mfaMode"));
|
||||
if (Set.of("off", "optional", "required").contains(mode)) {
|
||||
settings.put("mfaMode", mode);
|
||||
}
|
||||
}
|
||||
if (updates.containsKey("passkeyEnabled")) {
|
||||
settings.put("passkeyEnabled", Boolean.TRUE.equals(updates.get("passkeyEnabled")));
|
||||
}
|
||||
if (updates.containsKey("passkeyMode")) {
|
||||
String mode = String.valueOf(updates.get("passkeyMode"));
|
||||
if (Set.of("optional", "preferred", "required").contains(mode)) {
|
||||
settings.put("passkeyMode", mode);
|
||||
}
|
||||
}
|
||||
tenant.setSettings(settings);
|
||||
tenantService.save(tenant);
|
||||
}
|
||||
|
||||
public record AuthSettingsData(String mfaMode, boolean passkeyEnabled, String passkeyMode) {}
|
||||
|
||||
public AuthSettingsData getAuthSettings() {
|
||||
TenantEntity tenant = resolveTenant();
|
||||
Map<String, Object> settings = tenant.getSettings() != null ? tenant.getSettings() : Map.of();
|
||||
String mfaMode = settings.containsKey("mfaMode")
|
||||
? String.valueOf(settings.get("mfaMode"))
|
||||
: (Boolean.TRUE.equals(settings.get("mfaRequired")) ? "required" : "off");
|
||||
boolean passkeyEnabled = Boolean.TRUE.equals(settings.get("passkeyEnabled"));
|
||||
String passkeyMode = settings.containsKey("passkeyMode")
|
||||
? String.valueOf(settings.get("passkeyMode"))
|
||||
: "optional";
|
||||
return new AuthSettingsData(mfaMode, passkeyEnabled, passkeyMode);
|
||||
}
|
||||
|
||||
// --- TOTP helpers ---
|
||||
|
||||
private String computeTotp(String base32Secret, long timeStep) {
|
||||
|
||||
Reference in New Issue
Block a user