2026-05-09 21:47:33 +08:00

166 lines
4.7 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Package auth 实现 user-service 的认证、三方登录、密码登录和 refresh session 用例。
package auth
import (
"context"
"strings"
"hyapp/pkg/appcode"
"hyapp/pkg/xerr"
authdomain "hyapp/services/user-service/internal/domain/auth"
)
// Meta 是 gateway 透传到 user-service 的审计元信息。
type Meta struct {
// AppCode 是 gateway 已解析的 App 租户键。
AppCode string
// RequestID 是 gateway 透传的请求 ID用于登录审计。
RequestID string
// ClientIP 是客户端 IP。
ClientIP string
// UserAgent 是客户端 UA。
UserAgent string
// CountryByIP 是 gateway 或可信边缘层根据入口 IP 得到的国家快照。
CountryByIP string
// Platform 是客户端平台快照,认证链路只做审计和风险任务透传。
Platform string
// Language 是客户端语言偏好gateway 只用作弱信号快拦。
Language string
// Timezone 是客户端 IANA 时区gateway 只用作弱信号快拦。
Timezone string
}
func (s *Service) audit(ctx context.Context, meta Meta, userID int64, loginType string, provider string, result string, failureCode string) {
s.recordAudit(ctx, authdomain.LoginAudit{
AppCode: appcode.Normalize(meta.AppCode),
RequestID: meta.RequestID,
UserID: userID,
LoginType: loginType,
Provider: provider,
Channel: auditChannel(loginType, provider),
Platform: normalizePlatform(meta.Platform),
Result: result,
FailureCode: failureCode,
ClientIP: meta.ClientIP,
IPCountryCode: normalizeCountryCode(meta.CountryByIP),
UserAgent: meta.UserAgent,
CreatedAtMs: s.now().UnixMilli(),
})
}
func (s *Service) recordAudit(ctx context.Context, audit authdomain.LoginAudit) {
if s.authRepository == nil {
// 没有 repository 时无法写审计,但不应让辅助审计路径触发 panic。
return
}
// 审计失败不影响登录主链路,所以这里有意忽略错误。
_ = s.authRepository.RecordLoginAudit(ctx, audit)
}
// RecordLoginBlocked 写入 gateway fast guard 拒绝的登录审计;失败不应影响 gateway 返回阻断响应。
func (s *Service) RecordLoginBlocked(ctx context.Context, meta Meta, channel string, platform string, language string, timezone string, blockReason string) bool {
if s.authRepository == nil {
return false
}
nowMs := s.now().UnixMilli()
audit := authdomain.LoginAudit{
AppCode: appcode.Normalize(meta.AppCode),
RequestID: meta.RequestID,
LoginType: blockedLoginType(channel),
Provider: blockedProvider(channel),
Channel: normalizeChannel(channel),
Platform: normalizePlatform(firstNonEmpty(platform, meta.Platform)),
Result: "blocked",
FailureCode: string(xerr.AuthLoginBlocked),
ClientIP: meta.ClientIP,
IPCountryCode: normalizeCountryCode(meta.CountryByIP),
Blocked: true,
BlockReason: strings.TrimSpace(blockReason),
UserAgent: meta.UserAgent,
CreatedAtMs: nowMs,
}
if audit.Channel == "" {
audit.Channel = "account"
}
return s.authRepository.RecordLoginAudit(ctx, audit) == nil
}
func auditChannel(loginType string, provider string) string {
if loginType == loginPassword {
return "account"
}
if loginType == loginThird {
return normalizeThirdPartyChannel(provider)
}
return strings.TrimSpace(loginType)
}
func blockedLoginType(channel string) string {
if normalizeChannel(channel) == "account" {
return loginPassword
}
return loginThird
}
func blockedProvider(channel string) string {
channel = normalizeChannel(channel)
if channel == "account" {
return ""
}
return channel
}
func normalizeThirdPartyChannel(provider string) string {
provider = strings.ToLower(strings.TrimSpace(provider))
switch provider {
case "google", "google.com", "firebase":
return "google"
case "facebook", "facebook.com":
return "facebook"
case "twitter", "x":
return "twitter"
case "tiktok":
return "tiktok"
default:
return provider
}
}
func normalizeChannel(channel string) string {
channel = strings.ToLower(strings.TrimSpace(channel))
switch channel {
case "", "password":
return "account"
case "third_party":
return ""
default:
return normalizeThirdPartyChannel(channel)
}
}
func normalizePlatform(platform string) string {
platform = strings.ToLower(strings.TrimSpace(platform))
if platform == "android" || platform == "ios" {
return platform
}
return platform
}
func normalizeCountryCode(country string) string {
country = strings.ToUpper(strings.TrimSpace(country))
if len(country) < 2 || len(country) > 3 {
return ""
}
return country
}
func firstNonEmpty(values ...string) string {
for _, value := range values {
if strings.TrimSpace(value) != "" {
return value
}
}
return ""
}