// Package auth 实现 user-service 的认证、三方登录、密码登录和 refresh session 用例。 package auth import ( "context" "time" "hyapp/pkg/idgen" authdomain "hyapp/services/user-service/internal/domain/auth" userdomain "hyapp/services/user-service/internal/domain/user" userservice "hyapp/services/user-service/internal/service/user" ) const ( // hashAlgBcrypt 是当前密码 hash 算法标识。 hashAlgBcrypt = "bcrypt" // tokenType 是对外 token 类型。 tokenType = "Bearer" // loginPassword 是密码登录审计类型。 loginPassword = "password" // loginThird 是三方登录审计类型。 loginThird = "third_party" // loginRefresh 是 refresh token 轮换审计类型。 loginRefresh = "refresh" // loginAccessResign 是不轮换 refresh token 的 access token 重签审计类型。 loginAccessResign = "access_resign" // loginSetPass 是设置密码审计类型。 loginSetPass = "set_password" // resultSuccess 是审计成功结果。 resultSuccess = "success" // resultFailed 是审计失败结果。 resultFailed = "failed" ) // AuthRepository 是认证用例的持久化边界。 // 跨表写入仍按用例保留事务入口,例如三方首次登录要一次性创建用户、默认短号、三方身份和 session。 type AuthRepository interface { // AllocateDisplayUserID 分配当前 App 下的下一个默认短号。 AllocateDisplayUserID(ctx context.Context, appCode string) (string, error) // SetPassword 为已有用户首次写入密码身份。 SetPassword(ctx context.Context, account authdomain.PasswordAccount) error // FindPasswordByDisplayUserID 通过当前 active display_user_id 读取密码身份。 FindPasswordByDisplayUserID(ctx context.Context, displayUserID string, nowMs int64) (authdomain.PasswordAccount, error) // CreateSession 创建新的 refresh session。 CreateSession(ctx context.Context, session authdomain.Session) error // FindActiveSessionByRefreshHash 通过 refresh token hash 查找未过期未吊销 session。 FindActiveSessionByRefreshHash(ctx context.Context, refreshTokenHash string, nowMs int64) (authdomain.Session, error) // FindActiveSessionByID 通过 session_id 查找未过期未吊销 session,用于只重签 access token 的链路。 FindActiveSessionByID(ctx context.Context, sessionID string, nowMs int64) (authdomain.Session, error) // ReplaceSession 原子吊销旧 session 并创建新 session。 ReplaceSession(ctx context.Context, oldSessionID string, newSession authdomain.Session, revokedAtMs int64) error // RevokeSession 按 session_id 或 refresh token hash 吊销 session。 RevokeSession(ctx context.Context, sessionID string, refreshTokenHash string, revokedAtMs int64) (bool, error) // FindThirdPartyIdentity 查找 provider + subject 的绑定。 FindThirdPartyIdentity(ctx context.Context, provider string, providerSubject string) (authdomain.ThirdPartyIdentity, error) // CreateThirdPartyUser 原子创建用户、默认短号、三方身份和首个 session。 CreateThirdPartyUser(ctx context.Context, user userdomain.User, identity userdomain.Identity, thirdParty authdomain.ThirdPartyIdentity, session authdomain.Session) error // RecordLoginAudit 记录登录审计,主链路不会因为审计失败而失败。 RecordLoginAudit(ctx context.Context, audit authdomain.LoginAudit) error } // UserRepository 为 auth service 提供登录签发前的用户快照读取。 // Auth service 不关心用户创建和短号修改细节,只需要判断用户是否可登录以及签发当前展示号。 type UserRepository interface { // GetUser 返回登录签发前需要的用户快照。 GetUser(ctx context.Context, userID int64) (userdomain.User, error) } // IdentityRepository 为 auth service 提供靓号懒过期能力。 // 过期恢复仍由 identity repository 的事务完成,避免 auth service 手动改 users 和短号表。 type IdentityRepository interface { // ExpirePrettyDisplayUserID 原子恢复过期靓号,避免 token 签发过期展示号。 ExpirePrettyDisplayUserID(ctx context.Context, command userdomain.ExpirePrettyDisplayUserIDCommand) (userdomain.Identity, error) } // ThirdPartyVerifier 把 provider credential 校验成稳定 provider_subject。 // gateway 不接触 provider secret,三方校验必须停留在 user-service。 type ThirdPartyVerifier interface { // Verify 校验 provider credential,并返回 provider 内稳定身份。 Verify(ctx context.Context, provider string, credential string) (authdomain.ThirdPartyProfile, error) } // Config 保存认证底座需要的稳定参数。 type Config struct { // Issuer 写入 access token 的 iss claim。 Issuer string // AccessTokenTTLSec 是短期 access token 有效期。 AccessTokenTTLSec int64 // RefreshTokenTTLSec 是服务端 refresh session 有效期。 RefreshTokenTTLSec int64 // SigningAlg 当前只支持 HS256。 SigningAlg string // SigningSecret 是 HS256 签名密钥。 SigningSecret string } // Service 承载登录注册用例。 type Service struct { // cfg 保存 token 签发配置。 cfg Config // authRepository 负责密码身份、session、三方绑定和登录审计。 authRepository AuthRepository // userRepository 提供登录签发前的用户主状态读取。 userRepository UserRepository // identityRepository 提供靓号懒过期恢复事务。 identityRepository IdentityRepository // countryRegionRepository 提供三方首次注册时的国家主数据校验和区域解析。 countryRegionRepository userservice.CountryRegionRepository // idGenerator 生成三方首次登录时的新 user_id。 idGenerator userservice.IDGenerator // displayUserIDAllocator 生成三方首次登录时的默认短号候选。 displayUserIDAllocator userservice.DisplayUserIDAllocator // thirdPartyVerifier 把 provider credential 校验成稳定 subject。 thirdPartyVerifier ThirdPartyVerifier // now 是服务时钟,测试可注入固定时间。 now func() time.Time // allocateMaxAttempts 是默认短号冲突重试上限。 allocateMaxAttempts int } // Option 调整 auth service 的依赖和策略。 type Option func(*Service) // New 创建认证服务。 func New(cfg Config, options ...Option) *Service { // 默认依赖只保证可构造,生产必须通过 app 层注入 repository 和真实配置。 svc := &Service{ cfg: cfg, idGenerator: idgen.NewInt64Generator(0), displayUserIDAllocator: userservice.NumericDisplayUserIDAllocator{}, thirdPartyVerifier: NewStaticThirdPartyVerifier([]string{"wechat"}), now: time.Now, allocateMaxAttempts: 8, } for _, option := range options { // option 只替换依赖和策略,不执行 IO。 option(svc) } return svc } // WithAuthRepository 注入纯认证持久化实现。 func WithAuthRepository(repository AuthRepository) Option { return func(s *Service) { if repository != nil { // nil 不覆盖现有配置,避免测试误传导致 service 不可用。 s.authRepository = repository } } } // WithUserRepository 注入用户快照读取实现。 func WithUserRepository(repository UserRepository) Option { return func(s *Service) { if repository != nil { // auth service 只读取用户快照,不直接创建或修改用户资料。 s.userRepository = repository } } } // WithIdentityRepository 注入短号懒过期实现。 func WithIdentityRepository(repository IdentityRepository) Option { return func(s *Service) { if repository != nil { // identityRepository 只负责过期靓号恢复事务。 s.identityRepository = repository } } } // WithCountryRegionRepository 注入国家和区域解析 repository。 func WithCountryRegionRepository(repository userservice.CountryRegionRepository) Option { return func(s *Service) { if repository != nil { s.countryRegionRepository = repository } } } // WithIDGenerator 注入 user_id 发号器。 func WithIDGenerator(generator userservice.IDGenerator) Option { return func(s *Service) { if generator != nil { // 三方首次登录需要生成新用户 ID。 s.idGenerator = generator } } } // WithDisplayUserIDAllocator 注入短号候选生成器。 func WithDisplayUserIDAllocator(allocator userservice.DisplayUserIDAllocator) Option { return func(s *Service) { if allocator != nil { // allocator 只生成候选,唯一性由 repository 事务保证。 s.displayUserIDAllocator = allocator } } } // WithThirdPartyVerifier 注入三方 provider 校验器。 func WithThirdPartyVerifier(verifier ThirdPartyVerifier) Option { return func(s *Service) { if verifier != nil { // verifier 可替换为真实三方 SDK 实现。 s.thirdPartyVerifier = verifier } } } // WithClock 注入时间函数,保证 session 轮换测试稳定。 func WithClock(now func() time.Time) Option { return func(s *Service) { if now != nil { // 固定时钟用于 session 过期和 refresh 轮换测试。 s.now = now } } } // WithDisplayUserIDAllocateMaxAttempts 配置默认短号分配重试次数。 func WithDisplayUserIDAllocateMaxAttempts(maxAttempts int) Option { return func(s *Service) { if maxAttempts > 0 { // 只接受正数,避免三方注册永远无法分配默认短号。 s.allocateMaxAttempts = maxAttempts } } }