修复认证挑战与可信代理安全边界(07-17-auth-challenge-trusted-proxy-hardening)
这个提交包含在:
+28
@@ -0,0 +1,28 @@
|
||||
ALTER TABLE "employees"
|
||||
ADD COLUMN "email_login_code_failed_count" INTEGER NOT NULL DEFAULT 0,
|
||||
ADD COLUMN "email_login_code_failed_first_at" TIMESTAMP(3),
|
||||
ADD COLUMN "email_login_code_locked_until" TIMESTAMP(3),
|
||||
ADD COLUMN "email_login_code_last_requested_at" TIMESTAMP(3),
|
||||
ADD COLUMN "password_reset_failed_count" INTEGER NOT NULL DEFAULT 0,
|
||||
ADD COLUMN "password_reset_failed_first_at" TIMESTAMP(3),
|
||||
ADD COLUMN "password_reset_locked_until" TIMESTAMP(3),
|
||||
ADD COLUMN "password_reset_request_count" INTEGER NOT NULL DEFAULT 0,
|
||||
ADD COLUMN "password_reset_request_first_at" TIMESTAMP(3),
|
||||
ADD COLUMN "password_reset_last_requested_at" TIMESTAMP(3);
|
||||
|
||||
ALTER TABLE "password_reset_codes"
|
||||
ADD COLUMN "invalidated_at" TIMESTAMP(3),
|
||||
ADD COLUMN "attempts_count" INTEGER NOT NULL DEFAULT 0;
|
||||
|
||||
CREATE INDEX "password_reset_codes_employee_id_invalidated_at_created_at_idx"
|
||||
ON "password_reset_codes"("employee_id", "invalidated_at", "created_at");
|
||||
|
||||
-- Existing codes were generated and consumed under the weaker legacy contract.
|
||||
-- Invalidate them at rollout instead of carrying unsafe compatibility forward.
|
||||
UPDATE "password_reset_codes"
|
||||
SET "invalidated_at" = CURRENT_TIMESTAMP
|
||||
WHERE "used_at" IS NULL AND "invalidated_at" IS NULL;
|
||||
|
||||
UPDATE "email_login_codes"
|
||||
SET "used_at" = CURRENT_TIMESTAMP
|
||||
WHERE "used_at" IS NULL;
|
||||
@@ -605,6 +605,16 @@ model Employee {
|
||||
login_failed_count Int @default(0)
|
||||
login_failed_first_at DateTime?
|
||||
login_locked_until DateTime?
|
||||
email_login_code_failed_count Int @default(0)
|
||||
email_login_code_failed_first_at DateTime?
|
||||
email_login_code_locked_until DateTime?
|
||||
email_login_code_last_requested_at DateTime?
|
||||
password_reset_failed_count Int @default(0)
|
||||
password_reset_failed_first_at DateTime?
|
||||
password_reset_locked_until DateTime?
|
||||
password_reset_request_count Int @default(0)
|
||||
password_reset_request_first_at DateTime?
|
||||
password_reset_last_requested_at DateTime?
|
||||
archived_at DateTime?
|
||||
|
||||
created_at DateTime @default(now())
|
||||
@@ -723,6 +733,8 @@ model PasswordResetCode {
|
||||
code_hash String
|
||||
expires_at DateTime
|
||||
used_at DateTime?
|
||||
invalidated_at DateTime?
|
||||
attempts_count Int @default(0)
|
||||
created_at DateTime @default(now())
|
||||
created_ip String?
|
||||
created_user_agent String?
|
||||
@@ -730,6 +742,7 @@ model PasswordResetCode {
|
||||
employee Employee @relation(fields: [employee_id], references: [id], onDelete: Restrict)
|
||||
|
||||
@@index([employee_id, created_at])
|
||||
@@index([employee_id, invalidated_at, created_at])
|
||||
@@index([expires_at])
|
||||
@@map("password_reset_codes")
|
||||
}
|
||||
|
||||
在新工单中引用
屏蔽一个用户