feat(mfa): require_mfa policy, sign-in rate limit and MFA column

This commit is contained in:
2026-09-16 09:14:16 +00:00
parent f87626cf17
commit d8597ee3ae
5 changed files with 136 additions and 15 deletions
+32
View File
@@ -47,6 +47,38 @@ func mfaCtx() (context.Context, context.CancelFunc) {
return context.WithTimeout(context.Background(), 5*time.Second)
}
// UsersWithMFA returns the set of user IDs in this instance holding a factor.
// One query per collection, not one per member, so the member list stays
// cheap however many users an instance has.
func UsersWithMFA(instanceID string) (map[string]bool, error) {
ctx, cancel := mfaCtx()
defer cancel()
out := map[string]bool{}
cur, err := db.Col("user_mfa").Find(ctx,
bson.M{"instance_id": instanceID, "totp_confirmed_at": bson.M{"$exists": true}})
if err != nil {
return nil, err
}
var rows []models.UserMFA
if err := cur.All(ctx, &rows); err != nil {
return nil, err
}
for _, r := range rows {
out[r.UserID] = true
}
var userIDs []string
if err := db.Col("webauthn_credentials").Distinct(ctx, "user_id",
bson.M{"instance_id": instanceID}).Decode(&userIDs); err != nil {
return nil, err
}
for _, id := range userIDs {
out[id] = true
}
return out, nil
}
// GenerateRecoveryCodes returns the codes to show the user once, and the
// hashed records to store. The plaintext is never persisted.
func GenerateRecoveryCodes() ([]string, []models.RecoveryCode, error) {
+4 -1
View File
@@ -119,7 +119,7 @@ func ResolveSecretsReadToken(token string) (string, bool) {
return s.InstanceID, true
}
func SaveSettings(instanceID string, alerts models.AlertSettings, retentionDays *int, localLoginEnabled *bool, apiTokenMaxDays *int) error {
func SaveSettings(instanceID string, alerts models.AlertSettings, retentionDays *int, localLoginEnabled *bool, requireMFA *bool, apiTokenMaxDays *int) error {
if alerts.OfflineThresholdMinutes <= 0 {
alerts.OfflineThresholdMinutes = 5
}
@@ -149,6 +149,9 @@ func SaveSettings(instanceID string, alerts models.AlertSettings, retentionDays
if localLoginEnabled != nil {
set["local_login_enabled"] = *localLoginEnabled
}
if requireMFA != nil {
set["require_mfa"] = *requireMFA
}
if apiTokenMaxDays != nil {
set["api_token_max_days"] = *apiTokenMaxDays
}