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.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@@ -77,7 +78,7 @@ public class TenantPortalService {
|
|||||||
String serverEndpoint, Instant createdAt
|
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) {}
|
public record MfaSetupData(String secret, String secretQrCode) {}
|
||||||
|
|
||||||
@@ -297,7 +298,10 @@ public class TenantPortalService {
|
|||||||
.anyMatch(v -> "Totp".equals(String.valueOf(v.get("type"))));
|
.anyMatch(v -> "Totp".equals(String.valueOf(v.get("type"))));
|
||||||
boolean hasBackupCodes = verifications.stream()
|
boolean hasBackupCodes = verifications.stream()
|
||||||
.anyMatch(v -> "BackupCode".equals(String.valueOf(v.get("type"))));
|
.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")
|
@SuppressWarnings("unchecked")
|
||||||
@@ -370,18 +374,87 @@ public class TenantPortalService {
|
|||||||
logtoClient.deleteAllMfaVerifications(userId);
|
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) {
|
public void updateTenantSettings(Map<String, Object> updates) {
|
||||||
TenantEntity tenant = resolveTenant();
|
TenantEntity tenant = resolveTenant();
|
||||||
Map<String, Object> settings = new HashMap<>(
|
Map<String, Object> settings = new HashMap<>(
|
||||||
tenant.getSettings() != null ? tenant.getSettings() : Map.of());
|
tenant.getSettings() != null ? tenant.getSettings() : Map.of());
|
||||||
// Only allow known keys
|
|
||||||
if (updates.containsKey("mfaRequired")) {
|
if (updates.containsKey("mfaRequired")) {
|
||||||
settings.put("mfaRequired", Boolean.TRUE.equals(updates.get("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);
|
tenant.setSettings(settings);
|
||||||
tenantService.save(tenant);
|
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 ---
|
// --- TOTP helpers ---
|
||||||
|
|
||||||
private String computeTotp(String base32Secret, long timeStep) {
|
private String computeTotp(String base32Secret, long timeStep) {
|
||||||
|
|||||||
Reference in New Issue
Block a user