fix(security): harden auth reset, rate limiter fallback, and CI secrets
- Move CI_AUTH_SECRET from plaintext to ${{ secrets.CI_AUTH_SECRET }}
- Wrap password reset (update + session kill + token mark) in $transaction
to prevent stale sessions on partial failure (CWE-613)
- Rate limiter Redis fallback now uses stricter degraded limits
(maxRequests/10) and logs at error level instead of warn
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -94,7 +94,10 @@ export const authRouter = createTRPCRouter({
|
||||
throw new TRPCError({ code: "NOT_FOUND", message: "Reset link not found." });
|
||||
}
|
||||
if (record.usedAt) {
|
||||
throw new TRPCError({ code: "BAD_REQUEST", message: "This reset link has already been used." });
|
||||
throw new TRPCError({
|
||||
code: "BAD_REQUEST",
|
||||
message: "This reset link has already been used.",
|
||||
});
|
||||
}
|
||||
if (record.expiresAt < new Date()) {
|
||||
throw new TRPCError({ code: "BAD_REQUEST", message: "This reset link has expired." });
|
||||
@@ -103,19 +106,22 @@ export const authRouter = createTRPCRouter({
|
||||
const { hash } = await import("@node-rs/argon2");
|
||||
const passwordHash = await hash(input.password);
|
||||
|
||||
const updatedUser = await ctx.db.user.update({
|
||||
where: { email: record.email },
|
||||
data: { passwordHash },
|
||||
select: { id: true },
|
||||
});
|
||||
// All three operations must succeed atomically: if session deletion
|
||||
// fails after the password is already changed, old sessions could
|
||||
// persist with the new password (CWE-613).
|
||||
await ctx.db.$transaction(async (tx) => {
|
||||
const updatedUser = await tx.user.update({
|
||||
where: { email: record.email },
|
||||
data: { passwordHash },
|
||||
select: { id: true },
|
||||
});
|
||||
|
||||
// Invalidate all active sessions so any session obtained before the
|
||||
// password reset cannot be reused (CWE-613).
|
||||
await ctx.db.activeSession.deleteMany({ where: { userId: updatedUser.id } });
|
||||
await tx.activeSession.deleteMany({ where: { userId: updatedUser.id } });
|
||||
|
||||
await ctx.db.passwordResetToken.update({
|
||||
where: { token: input.token },
|
||||
data: { usedAt: new Date() },
|
||||
await tx.passwordResetToken.update({
|
||||
where: { token: input.token },
|
||||
data: { usedAt: new Date() },
|
||||
});
|
||||
});
|
||||
|
||||
return { success: true };
|
||||
|
||||
Reference in New Issue
Block a user