fix(ssf): 修复 Verification 状态迁移
为已执行旧版 0062 的环境补充不可变修复迁移,恢复 Stream 状态字段和 Verification challenge 表;同时为 timestamptz 参数增加显式类型,避免 pgx 在真实 PostgreSQL 中推断冲突。\n\n验证:pnpm test;pnpm lint;pnpm build;真实 PostgreSQL 集成测试通过。
This commit is contained in:
parent
2ccf041b35
commit
29f24222fd
@ -21,3 +21,21 @@ func TestSecurityEventIdempotencyRepairMigrationExists(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSecurityEventVerificationStateRepairMigrationExists(t *testing.T) {
|
||||
payload, err := os.ReadFile("../../migrations/0065_security_event_verification_state_repair.sql")
|
||||
if err != nil {
|
||||
t.Fatalf("read verification state repair migration: %v", err)
|
||||
}
|
||||
sql := string(payload)
|
||||
for _, statement := range []string{
|
||||
"ADD COLUMN IF NOT EXISTS stream_status",
|
||||
"ADD COLUMN IF NOT EXISTS created_at",
|
||||
"CREATE TABLE IF NOT EXISTS gateway_security_event_verification_challenges",
|
||||
"CREATE INDEX IF NOT EXISTS idx_gateway_security_event_challenges_expiry",
|
||||
} {
|
||||
if !strings.Contains(sql, statement) {
|
||||
t.Fatalf("verification state repair migration is missing %q", statement)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -144,7 +144,7 @@ WHERE issuer=$1 AND audience=$2`, issuer, audience, stateHash, now)
|
||||
}
|
||||
if _, err := tx.Exec(ctx, `
|
||||
INSERT INTO gateway_security_event_verification_challenges(issuer,audience,state_hash,created_at,expires_at)
|
||||
VALUES($1,$2,$3,$4,$4 + interval '180 seconds')
|
||||
VALUES($1,$2,$3,$4::timestamptz,$4::timestamptz + interval '180 seconds')
|
||||
ON CONFLICT(issuer,audience,state_hash) DO NOTHING`, issuer, audience, stateHash, now); err != nil {
|
||||
return err
|
||||
}
|
||||
@ -278,7 +278,7 @@ UPDATE gateway_security_event_stream_state
|
||||
SET mode='introspection_fallback',fallback_since=COALESCE(fallback_since,$3),
|
||||
consecutive_verifications=0,last_error_category='verification_stale',updated_at=now()
|
||||
WHERE issuer=$1 AND audience=$2 AND mode <> 'introspection_fallback'
|
||||
AND COALESCE(last_verification_at,created_at) < $3 - make_interval(secs => $4::double precision)`,
|
||||
AND COALESCE(last_verification_at,created_at) < $3::timestamptz - make_interval(secs => $4::double precision)`,
|
||||
issuer, audience, now, staleAfter.Seconds())
|
||||
return err
|
||||
}
|
||||
|
||||
@ -51,6 +51,9 @@ func TestSecurityEventWatermarkVerificationAndFallbackLifecycle(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
now := time.Now().UTC().Truncate(time.Second)
|
||||
if err := db.AdvanceSecurityEventStreamState(ctx, issuer, audience, now, 180*time.Second); err != nil {
|
||||
t.Fatalf("advance fresh stream state: %v", err)
|
||||
}
|
||||
evaluation, err := db.EvaluateOIDCSecurityEvent(ctx, issuer, audience, subjectIssuer, tenantID, subject, now, now, 180*time.Second)
|
||||
if err != nil || !evaluation.RequireIntrospection || evaluation.Mode != "bootstrap" {
|
||||
t.Fatalf("bootstrap evaluation=%#v error=%v", evaluation, err)
|
||||
|
||||
@ -0,0 +1,37 @@
|
||||
-- 0062 was released before the verification challenge table and the complete
|
||||
-- stream-state columns were added. Installations that already recorded 0062
|
||||
-- need a new immutable migration to repair that drift.
|
||||
ALTER TABLE gateway_security_event_stream_state
|
||||
ADD COLUMN IF NOT EXISTS stream_status text NOT NULL DEFAULT 'unknown',
|
||||
ADD COLUMN IF NOT EXISTS created_at timestamptz NOT NULL DEFAULT now();
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM pg_constraint
|
||||
WHERE conrelid = 'gateway_security_event_stream_state'::regclass
|
||||
AND conname = 'gateway_security_event_stream_state_status'
|
||||
) THEN
|
||||
ALTER TABLE gateway_security_event_stream_state
|
||||
ADD CONSTRAINT gateway_security_event_stream_state_status
|
||||
CHECK (stream_status IN ('unknown','enabled','paused','disabled'));
|
||||
END IF;
|
||||
END
|
||||
$$;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS gateway_security_event_verification_challenges (
|
||||
issuer text NOT NULL,
|
||||
audience text NOT NULL,
|
||||
state_hash bytea NOT NULL,
|
||||
created_at timestamptz NOT NULL,
|
||||
expires_at timestamptz NOT NULL,
|
||||
PRIMARY KEY (issuer, audience, state_hash),
|
||||
FOREIGN KEY (issuer, audience)
|
||||
REFERENCES gateway_security_event_stream_state(issuer, audience) ON DELETE CASCADE,
|
||||
CONSTRAINT gateway_security_event_verification_challenge_hash
|
||||
CHECK (octet_length(state_hash) = 32)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_gateway_security_event_challenges_expiry
|
||||
ON gateway_security_event_verification_challenges(expires_at);
|
||||
Loading…
Reference in New Issue
Block a user