feat(identity): 收敛 AI Gateway 统一认证入口
Web 登录页合并为单一统一认证入口,未指定上下文时由认证中心在登录后决策。继续接受旧客户端显式传入 platform 或 tenant,并保持回调上下文及 tenant_hint 的绑定校验。同步更新 OpenAPI 产物。\n\n验证:Go 全量测试与 go vet 通过;Web lint、141 项 Vitest 和生产构建通过;本地 platform.admin 登录及管理工作台访问通过。
This commit is contained in:
@@ -42,11 +42,11 @@ func NewLoginTransactionWithContext(
|
||||
}
|
||||
contextType = strings.TrimSpace(contextType)
|
||||
tenantHint = strings.TrimSpace(tenantHint)
|
||||
if contextType != "platform" && contextType != "tenant" {
|
||||
return LoginTransaction{}, errors.New("contextType must be platform or tenant")
|
||||
if contextType != "" && contextType != "platform" && contextType != "tenant" {
|
||||
return LoginTransaction{}, errors.New("contextType must be empty, platform or tenant")
|
||||
}
|
||||
if contextType == "platform" && tenantHint != "" {
|
||||
return LoginTransaction{}, errors.New("platform context cannot bind a tenant hint")
|
||||
if contextType != "tenant" && tenantHint != "" {
|
||||
return LoginTransaction{}, errors.New("only tenant context can bind a tenant hint")
|
||||
}
|
||||
if tenantHint != "" && uuid.Validate(tenantHint) != nil {
|
||||
return LoginTransaction{}, errors.New("tenantHint must be a UUID")
|
||||
@@ -88,8 +88,8 @@ func (c *Cipher) DecodeLoginTransaction(encoded string, now time.Time) (LoginTra
|
||||
return LoginTransaction{}, err
|
||||
}
|
||||
if transaction.State == "" || transaction.Nonce == "" || transaction.PKCEVerifier == "" || !ValidReturnTo(transaction.ReturnTo) ||
|
||||
transaction.ContextType != "platform" && transaction.ContextType != "tenant" ||
|
||||
transaction.ContextType == "platform" && transaction.TenantHint != "" ||
|
||||
transaction.ContextType != "" && transaction.ContextType != "platform" && transaction.ContextType != "tenant" ||
|
||||
transaction.ContextType != "tenant" && transaction.TenantHint != "" ||
|
||||
transaction.TenantHint != "" && uuid.Validate(transaction.TenantHint) != nil ||
|
||||
transaction.CreatedAt.IsZero() || now.Before(transaction.CreatedAt.Add(-time.Minute)) || !now.Before(transaction.CreatedAt.Add(10*time.Minute)) {
|
||||
return LoginTransaction{}, errors.New("OIDC login transaction has expired or is invalid")
|
||||
|
||||
@@ -54,6 +54,23 @@ func TestLoginTransactionEncryptsTenantHint(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoginTransactionAllowsIssuerSelectedContextWithoutTenantHint(t *testing.T) {
|
||||
now := time.Date(2026, 7, 31, 12, 0, 0, 0, time.UTC)
|
||||
cipher, _ := NewCipher(bytes.Repeat([]byte{9}, 32))
|
||||
transaction, err := NewLoginTransactionWithContext("/", "", "", now)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
encoded, err := cipher.EncodeLoginTransaction(transaction)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
decoded, err := cipher.DecodeLoginTransaction(encoded, now)
|
||||
if err != nil || decoded.ContextType != "" || decoded.TenantHint != "" {
|
||||
t.Fatalf("decoded transaction=%+v err=%v", decoded, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoginTransactionRejectsInvalidContextBinding(t *testing.T) {
|
||||
now := time.Date(2026, 7, 28, 12, 0, 0, 0, time.UTC)
|
||||
tenantHint := "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa"
|
||||
@@ -61,7 +78,7 @@ func TestLoginTransactionRejectsInvalidContextBinding(t *testing.T) {
|
||||
contextType string
|
||||
tenantHint string
|
||||
}{
|
||||
{contextType: ""},
|
||||
{contextType: "", tenantHint: tenantHint},
|
||||
{contextType: "account"},
|
||||
{contextType: "platform", tenantHint: tenantHint},
|
||||
} {
|
||||
|
||||
Reference in New Issue
Block a user