fix(oidc): 同步认证中心用户资料到本地用户

从已验证的 OIDC Claim 提取用户名、显示名称、邮箱、手机号和头像,并覆盖单租户、多租户及平台用户的 JIT 创建与重复登录同步。\n\n保留 metadata.manualProfile 标记下的人工资料,限制字段长度且仅接收已验证联系方式与 HTTPS 头像。已通过 auth、httpapi、store 测试及临时 PostgreSQL 集成验证。
This commit is contained in:
2026-08-03 15:55:50 +08:00
parent 3c8d9839a4
commit b125599354
10 changed files with 206 additions and 24 deletions
+36 -3
View File
@@ -252,18 +252,51 @@ func (v *OIDCVerifier) Verify(ctx context.Context, raw string) (*User, error) {
return nil, oidcUnauthorized("TOKEN_INACTIVE", "token is inactive", err)
}
}
username := stringClaim(claims, "preferred_username")
username := oidcProfileText(claims, "preferred_username", 320)
if username == "" {
username = stringClaim(claims, "username")
username = oidcProfileText(claims, "username", 320)
}
return &User{
ID: stringClaim(claims, "sub"), Username: username, Roles: roles,
ID: stringClaim(claims, "sub"), Username: username,
DisplayName: oidcProfileText(claims, "name", 200),
Email: oidcVerifiedProfileText(claims, "email", "email_verified", 320),
Phone: oidcVerifiedProfileText(claims, "phone_number", "phone_number_verified", 64),
AvatarURL: safeOIDCProfileURL(oidcProfileText(claims, "picture", 2048)),
Roles: roles,
ContextType: contextType, TenantID: tenantID, Source: "oidc",
TokenExpiresAt: expiresAt, TokenIssuedAt: issuedAt, Issuer: v.config.Issuer,
ApplicationID: v.config.ApplicationID, OIDCClientID: clientID,
}, nil
}
func oidcProfileText(claims jwt.MapClaims, key string, limit int) string {
value := strings.TrimSpace(stringClaim(claims, key))
if value == "" {
return ""
}
runes := []rune(value)
if len(runes) > limit {
return string(runes[:limit])
}
return value
}
func oidcVerifiedProfileText(claims jwt.MapClaims, key, verifiedKey string, limit int) string {
verified, ok := claims[verifiedKey].(bool)
if !ok || !verified {
return ""
}
return oidcProfileText(claims, key, limit)
}
func safeOIDCProfileURL(value string) string {
parsed, err := url.Parse(value)
if err != nil || parsed.Scheme != "https" || parsed.Host == "" || parsed.User != nil {
return ""
}
return parsed.String()
}
type oidcValidationError struct {
category string
reason string