feat: handle password reset admin command
This commit is contained in:
@@ -2,6 +2,7 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"strconv"
|
"strconv"
|
||||||
@@ -31,6 +32,7 @@ const (
|
|||||||
adminCommandUserLogoutAll = "user.logout_all"
|
adminCommandUserLogoutAll = "user.logout_all"
|
||||||
adminCommandUserCancel = "user.account.cancel"
|
adminCommandUserCancel = "user.account.cancel"
|
||||||
adminCommandUserDelete = "user.account.delete"
|
adminCommandUserDelete = "user.account.delete"
|
||||||
|
adminCommandUserPasswordReset = "user.password.reset"
|
||||||
adminCommandKafkaDeadReplay = "kafka.dead_letter.replay"
|
adminCommandKafkaDeadReplay = "kafka.dead_letter.replay"
|
||||||
adminCommandKafkaDeadIgnore = "kafka.dead_letter.ignore"
|
adminCommandKafkaDeadIgnore = "kafka.dead_letter.ignore"
|
||||||
adminCommandKafkaDeadResolve = "kafka.dead_letter.resolve"
|
adminCommandKafkaDeadResolve = "kafka.dead_letter.resolve"
|
||||||
@@ -44,6 +46,7 @@ type adminCommandPayload struct {
|
|||||||
UserIDs []string `json:"user_ids"`
|
UserIDs []string `json:"user_ids"`
|
||||||
TargetID string `json:"target_user_id"`
|
TargetID string `json:"target_user_id"`
|
||||||
DeviceID string `json:"device_id"`
|
DeviceID string `json:"device_id"`
|
||||||
|
PasswordHash string `json:"password_hash"`
|
||||||
Version int `json:"version"`
|
Version int `json:"version"`
|
||||||
Reason string `json:"reason"`
|
Reason string `json:"reason"`
|
||||||
DeadID string `json:"dead_id"`
|
DeadID string `json:"dead_id"`
|
||||||
@@ -157,11 +160,15 @@ func handleKafkaAdminCommand(ctx context.Context, envelope kafkaEnvelope, _ kafk
|
|||||||
}
|
}
|
||||||
|
|
||||||
func executeKafkaAdminCommand(tenantID types.TenantID, envelope kafkaEnvelope, payload adminCommandPayload) (map[string]any, error) {
|
func executeKafkaAdminCommand(tenantID types.TenantID, envelope kafkaEnvelope, payload adminCommandPayload) (map[string]any, error) {
|
||||||
|
eventType := strings.TrimSpace(envelope.EventType)
|
||||||
|
if eventType == adminCommandUserPasswordReset {
|
||||||
|
return executeKafkaUserPasswordResetCommand(tenantID, payload)
|
||||||
|
}
|
||||||
actor := parseKafkaCommandUser(payload.ActorUserID)
|
actor := parseKafkaCommandUser(payload.ActorUserID)
|
||||||
if actor.IsZero() {
|
if actor.IsZero() {
|
||||||
return nil, types.ErrMalformed
|
return nil, types.ErrMalformed
|
||||||
}
|
}
|
||||||
switch strings.TrimSpace(envelope.EventType) {
|
switch eventType {
|
||||||
case adminCommandKafkaDeadReplay:
|
case adminCommandKafkaDeadReplay:
|
||||||
dead, err := replayKafkaDeadLetter(context.Background(), tenantID, payload.DeadID, actor, firstNonEmpty(payload.Note, payload.Reason))
|
dead, err := replayKafkaDeadLetter(context.Background(), tenantID, payload.DeadID, actor, firstNonEmpty(payload.Note, payload.Reason))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -373,6 +380,40 @@ func executeKafkaUserGovernanceCommand(tenantID types.TenantID, envelope kafkaEn
|
|||||||
return nil, errors.New("unsupported user governance command: " + eventType)
|
return nil, errors.New("unsupported user governance command: " + eventType)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func executeKafkaUserPasswordResetCommand(tenantID types.TenantID, payload adminCommandPayload) (map[string]any, error) {
|
||||||
|
target := parseKafkaCommandUser(firstNonEmpty(payload.TargetID, payload.UserID))
|
||||||
|
if target.IsZero() || payload.PasswordHash == "" {
|
||||||
|
return nil, types.ErrMalformed
|
||||||
|
}
|
||||||
|
passwordHash, err := base64.StdEncoding.DecodeString(payload.PasswordHash)
|
||||||
|
if err != nil || len(passwordHash) == 0 {
|
||||||
|
return nil, types.ErrMalformed
|
||||||
|
}
|
||||||
|
_, authLevel, _, _, err := store.Users.GetAuthRecord(tenantID, target)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err = store.Users.UpdateAuthRecord(tenantID, target, authLevel, "", passwordHash, time.Time{}); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
now := time.Now().UTC().Round(time.Millisecond)
|
||||||
|
authVersion := 0
|
||||||
|
if store.Security != nil {
|
||||||
|
version, err := store.Security.BumpAuthVersion(tenantID, target, now)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
authVersion = version
|
||||||
|
}
|
||||||
|
evictUserSessions(target)
|
||||||
|
return map[string]any{
|
||||||
|
"user_id": internalAdminUIDRaw(target),
|
||||||
|
"action": adminCommandUserPasswordReset,
|
||||||
|
"auth_version": authVersion,
|
||||||
|
"evicted": true,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
func evictUserSessions(uid types.Uid) {
|
func evictUserSessions(uid types.Uid) {
|
||||||
if globals.sessionStore != nil {
|
if globals.sessionStore != nil {
|
||||||
globals.sessionStore.EvictUser(uid, "")
|
globals.sessionStore.EvictUser(uid, "")
|
||||||
@@ -575,6 +616,8 @@ func kafkaCommandErrorReason(err error) string {
|
|||||||
switch {
|
switch {
|
||||||
case errors.Is(err, types.ErrMalformed):
|
case errors.Is(err, types.ErrMalformed):
|
||||||
return "MALFORMED_COMMAND"
|
return "MALFORMED_COMMAND"
|
||||||
|
case errors.Is(err, types.ErrPolicy):
|
||||||
|
return "POLICY_VIOLATION"
|
||||||
case errors.Is(err, types.ErrNotFound):
|
case errors.Is(err, types.ErrNotFound):
|
||||||
return "RESOURCE_NOT_FOUND"
|
return "RESOURCE_NOT_FOUND"
|
||||||
case errors.Is(err, types.ErrPermissionDenied):
|
case errors.Is(err, types.ErrPermissionDenied):
|
||||||
|
|||||||
Reference in New Issue
Block a user