fix(auth): one-time SSO and MFA tokens, permission changes effective on every replica
- Every JWT carries a jti. The SSO completion token and the MFA challenge (once a code was accepted) are recorded as used in ConsumedToken; a replayed request or copied cookie cannot open a second session. Used ids are cleaned up after they expire. - The token-validity check (active user, active role, password change) reads fresh on every request and refreshes the permission cache, so a deactivation or role change made on another replica applies to the very next request instead of up to 30 s later. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
1 parent
fc6af8737b
commit
45efb08369
16 files changed
+166
-23
No files matched your search
@@ -7,6 +7,7 @@ import { PrismaService } from '../src/prisma/prisma.service';
|
||||
import { encryptMfaSecret } from '../src/auth/mfa/mfa-crypto.util';
|
||||
import { generateTotpSecret } from '../src/auth/mfa/totp.util';
|
||||
import { createTestApp } from './utils/create-test-app';
|
||||
import { OneTimeTokenService } from '../src/auth/one-time-token.service';
|
||||
|
||||
/**
|
||||
* Account-level brute-force protection and one-time TOTP codes, against a
|
||||
@@ -91,4 +92,14 @@ describe('Account guard (e2e)', () => {
|
||||
.send({ mfaToken: (second.body as { mfaToken: string }).mfaToken, code })
|
||||
.expect(401);
|
||||
});
|
||||
|
||||
it('lets a one-time token be used exactly once', async () => {
|
||||
const oneTimeTokens = app.get(OneTimeTokenService);
|
||||
const claims = {
|
||||
jti: `jti-${suffix}`,
|
||||
exp: Math.floor(Date.now() / 1000) + 60,
|
||||
};
|
||||
await expect(oneTimeTokens.consume(claims)).resolves.toBeUndefined();
|
||||
await expect(oneTimeTokens.consume(claims)).rejects.toThrow();
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user