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:
@@ -50,6 +50,29 @@ func TestStartOIDCLoginSetsEncryptedLaxTransactionAndRedirectsWithPKCE(t *testin
|
||||
}
|
||||
}
|
||||
|
||||
func TestStartOIDCLoginAllowsAuthCenterToSelectContext(t *testing.T) {
|
||||
cipher, _ := oidcsession.NewCipher(bytes.Repeat([]byte{3}, 32))
|
||||
client := &fakeOIDCClient{authorizationURL: "https://auth.example.com/authorize"}
|
||||
server := &Server{
|
||||
auth: &auth.Authenticator{OIDCVerifier: &auth.OIDCVerifier{}}, oidcClient: client,
|
||||
oidcSessions: &fakeOIDCSessions{}, oidcSessionCipher: cipher,
|
||||
identityTestRevision: identity.Revision{TenantMode: "multi_tenant"},
|
||||
logger: slog.New(slog.NewTextHandler(io.Discard, nil)),
|
||||
}
|
||||
recorder := httptest.NewRecorder()
|
||||
server.startOIDCLogin(recorder, httptest.NewRequest(
|
||||
http.MethodGet, "/api/v1/auth/oidc/login?returnTo=%2Fworkspace", nil,
|
||||
))
|
||||
if recorder.Code != http.StatusSeeOther || client.contextType != "" || client.tenantHint != "" {
|
||||
t.Fatalf("status=%d contextType=%q tenantHint=%q", recorder.Code, client.contextType, client.tenantHint)
|
||||
}
|
||||
cookies := recorder.Result().Cookies()
|
||||
transaction, err := cipher.DecodeLoginTransaction(cookies[0].Value, time.Now())
|
||||
if err != nil || transaction.ContextType != "" || transaction.ReturnTo != "/workspace" {
|
||||
t.Fatalf("transaction=%+v err=%v", transaction, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStartOIDCLoginRejectsOpenRedirect(t *testing.T) {
|
||||
cipher, _ := oidcsession.NewCipher(bytes.Repeat([]byte{3}, 32))
|
||||
server := &Server{
|
||||
@@ -114,10 +137,10 @@ func TestStartOIDCLoginRejectsInvalidOrSingleTenantHint(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestStartOIDCLoginRejectsMissingOrIncompatibleContext(t *testing.T) {
|
||||
func TestStartOIDCLoginRejectsInvalidOrIncompatibleContext(t *testing.T) {
|
||||
for _, path := range []string{
|
||||
"/api/v1/auth/oidc/login",
|
||||
"/api/v1/auth/oidc/login?contextType=account",
|
||||
"/api/v1/auth/oidc/login?tenantHint=aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
|
||||
"/api/v1/auth/oidc/login?contextType=platform&tenantHint=aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
|
||||
} {
|
||||
cipher, _ := oidcsession.NewCipher(bytes.Repeat([]byte{3}, 32))
|
||||
@@ -140,6 +163,59 @@ func TestStartOIDCLoginRejectsMissingOrIncompatibleContext(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestOIDCLoginContextBindingSupportsUnifiedAndExplicitEntries(t *testing.T) {
|
||||
tenantID := "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa"
|
||||
for _, test := range []struct {
|
||||
name string
|
||||
transaction oidcsession.LoginTransaction
|
||||
identity *auth.User
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "unified platform",
|
||||
transaction: oidcsession.LoginTransaction{},
|
||||
identity: &auth.User{ContextType: "platform"},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "unified tenant",
|
||||
transaction: oidcsession.LoginTransaction{},
|
||||
identity: &auth.User{ContextType: "tenant", TenantID: tenantID},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "legacy explicit platform matches",
|
||||
transaction: oidcsession.LoginTransaction{ContextType: "platform"},
|
||||
identity: &auth.User{ContextType: "platform"},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "legacy explicit platform rejects tenant",
|
||||
transaction: oidcsession.LoginTransaction{ContextType: "platform"},
|
||||
identity: &auth.User{ContextType: "tenant", TenantID: tenantID},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "tenant hint remains bound",
|
||||
transaction: oidcsession.LoginTransaction{ContextType: "tenant", TenantHint: tenantID},
|
||||
identity: &auth.User{ContextType: "tenant", TenantID: tenantID},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "tenant hint mismatch",
|
||||
transaction: oidcsession.LoginTransaction{ContextType: "tenant", TenantHint: tenantID},
|
||||
identity: &auth.User{ContextType: "tenant", TenantID: "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb"},
|
||||
want: false,
|
||||
},
|
||||
} {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
if got := oidcLoginContextMatches(test.transaction, test.identity); got != test.want {
|
||||
t.Fatalf("oidcLoginContextMatches() = %v, want %v", got, test.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCompleteOIDCLoginReportsSafeTransactionFailureReason(t *testing.T) {
|
||||
cipher, err := oidcsession.NewCipher(bytes.Repeat([]byte{3}, 32))
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user