package member import ( "context" "strings" memberenum "gateway/internal/model/member/domain/enum" domusecase "gateway/internal/model/member/domain/usecase" "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") } } // 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, }) }