template-monorepo/internal/logic/member/password_helper.go

96 lines
2.5 KiB
Go
Raw Normal View History

package member
import (
2026-05-27 09:28:13 +00:00
"context"
"strings"
memberenum "gateway/internal/model/member/domain/enum"
domusecase "gateway/internal/model/member/domain/usecase"
2026-05-27 09:28:13 +00:00
"gateway/internal/svc"
"gateway/internal/types"
)
func ensurePlatformNativePassword(member *domusecase.MemberDTO) error {
if member == nil {
return errb.ResNotFound("member", "")
}
switch member.Origin {
case memberenum.MemberOriginPlatformNative:
return nil
case memberenum.MemberOriginOIDC:
return errb.AuthForbidden("social login accounts cannot change password here")
case memberenum.MemberOriginLDAP:
return errb.AuthForbidden("ldap accounts cannot change password here")
case memberenum.MemberOriginSCIM:
return errb.AuthForbidden("scim provisioned accounts cannot change password here")
default:
return errb.AuthForbidden("account cannot change password here")
}
}
2026-05-27 09:28:13 +00:00
// totpEnrolledForActor uses TOTP status (authoritative) with profile flag as fallback.
func totpEnrolledForActor(
ctx context.Context,
sc *svc.ServiceContext,
tenantID, uid string,
member *domusecase.MemberDTO,
) (bool, error) {
if sc.MemberTOTP != nil {
status, err := sc.MemberTOTP.Status(ctx, tenantID, uid)
if err != nil {
return false, err
}
if status != nil {
return status.Enrolled, nil
}
}
if member != nil {
return member.TOTPEnrolled, nil
}
return false, nil
}
// ensurePasswordChangeStepUp requires TOTP enrollment and a consumed step-up token
// or inline TOTP code before password change.
func ensurePasswordChangeStepUp(
ctx context.Context,
sc *svc.ServiceContext,
tenantID, uid string,
member *domusecase.MemberDTO,
req *types.ChangePasswordReq,
) error {
if sc.MemberTOTP == nil {
return errb.SysNotImplemented("member TOTP not configured")
}
enrolled, err := totpEnrolledForActor(ctx, sc, tenantID, uid, member)
if err != nil {
return err
}
if !enrolled {
return errb.AuthForbidden("enroll totp before changing password")
}
token := strings.TrimSpace(req.StepUpToken)
code := strings.TrimSpace(req.TOTPCode)
if token == "" && code == "" {
return errb.AuthForbidden("totp verification required before password change")
}
if token != "" && code != "" {
return errb.InputInvalidFormat("provide either step_up_token or totp_code, not both")
}
if code != "" {
return sc.MemberTOTP.VerifyCode(ctx, tenantID, uid, code)
}
if err := requireStepUp(sc); err != nil {
return err
}
return sc.MemberStepUp.Consume(ctx, &domusecase.ConsumeStepUpRequest{
TokenID: token,
TenantID: tenantID,
UID: uid,
Purpose: memberenum.StepUpPurposeChangePassword,
})
}