feat(identity): 实现接入码配对后台流程
Gateway 使用一次性接入码创建 Exchange,将 Exchange Token、机器凭据与 Session Key 仅写入 SecretStore,并持久化脱敏配对进度。\n\n资源就绪后先保存凭据和 Manifest,再自动准备 SSF,最后确认远端 Exchange;SecretStore 失败时保持 ready,重试会触发新 Secret 轮换,不回放旧值。\n\n验证:go test ./...;go vet ./...
This commit is contained in:
@@ -13,6 +13,7 @@ const identityRevisionColumns = `
|
||||
id::text,state,schema_version,auth_center_url,COALESCE(issuer,''),COALESCE(tenant_id,''),COALESCE(application_id,''),
|
||||
COALESCE(audience,''),COALESCE(browser_client_id,''),COALESCE(machine_client_id,''),scopes,capabilities,role_prefix,
|
||||
local_tenant_key,public_base_url,web_base_url,jit_enabled,legacy_jwt_enabled,token_introspection,session_revocation,
|
||||
COALESCE(security_event_issuer,''),COALESCE(security_event_configuration_url,''),COALESCE(security_event_audience,''),
|
||||
COALESCE(machine_credential_ref,''),COALESCE(session_encryption_key_ref,''),session_idle_seconds,session_absolute_seconds,
|
||||
session_refresh_seconds,version,COALESCE(last_error_category,''),COALESCE(last_trace_id,''),COALESCE(last_audit_id,''),
|
||||
validated_at,activated_at,superseded_at,created_at,updated_at`
|
||||
@@ -62,13 +63,15 @@ func (s *Store) ApplyIdentityManifest(ctx context.Context, id string, expectedVe
|
||||
UPDATE gateway_identity_configuration_revisions SET
|
||||
issuer=$3,tenant_id=$4,application_id=$5,audience=NULLIF($6,''),browser_client_id=NULLIF($7,''),machine_client_id=NULLIF($8,''),
|
||||
scopes=$9::jsonb,capabilities=$10::jsonb,token_introspection=$11,session_revocation=$12,
|
||||
machine_credential_ref=NULLIF($13,''),session_encryption_key_ref=NULLIF($14,''),last_trace_id=NULLIF($15,''),
|
||||
last_audit_id=NULLIF($16,''),last_error_category=NULL,version=version+1,updated_at=now()
|
||||
security_event_issuer=NULLIF($13,''),security_event_configuration_url=NULLIF($14,''),security_event_audience=NULLIF($15,''),
|
||||
machine_credential_ref=NULLIF($16,''),session_encryption_key_ref=NULLIF($17,''),last_trace_id=NULLIF($18,''),
|
||||
last_audit_id=NULLIF($19,''),last_error_category=NULL,version=version+1,updated_at=now()
|
||||
WHERE id=$1::uuid AND version=$2 AND state='draft'
|
||||
RETURNING `+identityRevisionColumns,
|
||||
id, expectedVersion, updated.Issuer, updated.TenantID, updated.ApplicationID, updated.Audience,
|
||||
updated.BrowserClientID, updated.MachineClientID, string(scopes), string(capabilities), updated.TokenIntrospection,
|
||||
updated.SessionRevocation, updated.MachineCredentialRef, updated.SessionEncryptionKeyRef, updated.LastTraceID, updated.LastAuditID,
|
||||
updated.SessionRevocation, updated.SecurityEventIssuer, updated.SecurityEventConfigURL, updated.SecurityEventAudience,
|
||||
updated.MachineCredentialRef, updated.SessionEncryptionKeyRef, updated.LastTraceID, updated.LastAuditID,
|
||||
))
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
return identity.Revision{}, identity.ErrRevisionConflict
|
||||
@@ -213,6 +216,7 @@ func scanIdentityRevision(row scanner) (identity.Revision, error) {
|
||||
&revision.ApplicationID, &revision.Audience, &revision.BrowserClientID, &revision.MachineClientID, &scopes,
|
||||
&capabilities, &revision.RolePrefix, &revision.LocalTenantKey, &revision.PublicBaseURL, &revision.WebBaseURL,
|
||||
&revision.JITEnabled, &revision.LegacyJWTEnabled, &revision.TokenIntrospection, &revision.SessionRevocation,
|
||||
&revision.SecurityEventIssuer, &revision.SecurityEventConfigURL, &revision.SecurityEventAudience,
|
||||
&revision.MachineCredentialRef, &revision.SessionEncryptionKeyRef, &revision.SessionIdleSeconds,
|
||||
&revision.SessionAbsoluteSeconds, &revision.SessionRefreshSeconds, &revision.Version, &revision.LastErrorCategory,
|
||||
&revision.LastTraceID, &revision.LastAuditID, &revision.ValidatedAt, &revision.ActivatedAt, &revision.SupersededAt,
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"github.com/easyai/easyai-ai-gateway/apps/api/internal/identity"
|
||||
"github.com/jackc/pgx/v5"
|
||||
)
|
||||
|
||||
const identityPairingColumns = `
|
||||
id::text,revision_id::text,remote_exchange_id::text,exchange_token_ref,status,remote_version,expires_at,version,
|
||||
COALESCE(last_error_category,''),COALESCE(auth_center_audit_id,''),COALESCE(last_trace_id,''),created_at,updated_at`
|
||||
|
||||
func (s *Store) CreateIdentityPairingExchange(ctx context.Context, exchange identity.PairingExchange) (identity.PairingExchange, error) {
|
||||
return scanIdentityPairing(s.pool.QueryRow(ctx, `
|
||||
INSERT INTO gateway_identity_onboarding_exchanges (
|
||||
id,revision_id,remote_exchange_id,exchange_token_ref,status,remote_version,expires_at,last_trace_id
|
||||
) VALUES ($1::uuid,$2::uuid,$3::uuid,$4,$5,$6,$7,NULLIF($8,''))
|
||||
RETURNING `+identityPairingColumns,
|
||||
exchange.ID, exchange.RevisionID, exchange.RemoteExchangeID, exchange.ExchangeTokenRef,
|
||||
exchange.Status, exchange.RemoteVersion, exchange.ExpiresAt, exchange.LastTraceID,
|
||||
))
|
||||
}
|
||||
|
||||
func (s *Store) IdentityPairingExchange(ctx context.Context, id string) (identity.PairingExchange, error) {
|
||||
exchange, err := scanIdentityPairing(s.pool.QueryRow(ctx, `SELECT `+identityPairingColumns+`
|
||||
FROM gateway_identity_onboarding_exchanges WHERE id=$1::uuid`, id))
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
return identity.PairingExchange{}, identity.ErrRevisionNotFound
|
||||
}
|
||||
return exchange, err
|
||||
}
|
||||
|
||||
func (s *Store) UpdateIdentityPairingExchange(ctx context.Context, id string, expectedVersion int64, update identity.PairingExchangeUpdate) (identity.PairingExchange, error) {
|
||||
exchange, err := scanIdentityPairing(s.pool.QueryRow(ctx, `
|
||||
UPDATE gateway_identity_onboarding_exchanges SET status=$3,remote_version=$4,last_error_category=NULLIF($5,''),
|
||||
auth_center_audit_id=COALESCE(NULLIF($6,''),auth_center_audit_id),version=version+1,updated_at=now()
|
||||
WHERE id=$1::uuid AND version=$2
|
||||
RETURNING `+identityPairingColumns,
|
||||
id, expectedVersion, update.Status, update.RemoteVersion, update.LastErrorCategory, update.AuthCenterAuditID,
|
||||
))
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
return identity.PairingExchange{}, identity.ErrRevisionConflict
|
||||
}
|
||||
return exchange, err
|
||||
}
|
||||
|
||||
func scanIdentityPairing(row scanner) (identity.PairingExchange, error) {
|
||||
var exchange identity.PairingExchange
|
||||
var status string
|
||||
if err := row.Scan(
|
||||
&exchange.ID, &exchange.RevisionID, &exchange.RemoteExchangeID, &exchange.ExchangeTokenRef, &status,
|
||||
&exchange.RemoteVersion, &exchange.ExpiresAt, &exchange.Version, &exchange.LastErrorCategory,
|
||||
&exchange.AuthCenterAuditID, &exchange.LastTraceID, &exchange.CreatedAt, &exchange.UpdatedAt,
|
||||
); err != nil {
|
||||
return identity.PairingExchange{}, err
|
||||
}
|
||||
exchange.Status = identity.PairingStatus(status)
|
||||
return exchange, nil
|
||||
}
|
||||
Reference in New Issue
Block a user