mirror of
https://github.com/bulwarkmail/webmail.git
synced 2026-11-01 16:12:02 +00:00
fix: refuse an impersonation link when its use can't be recorded #1066
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
// @vitest-environment node
|
||||
import { mkdtempSync, readdirSync, rmSync, utimesSync } from 'node:fs';
|
||||
import { mkdtempSync, readdirSync, rmSync, utimesSync, writeFileSync } from 'node:fs';
|
||||
import { tmpdir } from 'node:os';
|
||||
import path from 'node:path';
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
@@ -44,6 +44,14 @@ describe('consumeImpersonationJti', () => {
|
||||
expect(await consumeImpersonationJti('jti-1', exp, now)).toBe(false);
|
||||
});
|
||||
|
||||
it('refuses the token when its marker cannot be written', async () => {
|
||||
// A state "dir" that is a file: the marker dir can't be created.
|
||||
const blocked = path.join(stateDir, 'not-a-dir');
|
||||
writeFileSync(blocked, '');
|
||||
process.env.ADMIN_STATE_DIR = blocked;
|
||||
expect(await consumeImpersonationJti('jti-unrecorded', exp, now)).toBe(false);
|
||||
});
|
||||
|
||||
it('prunes markers once their token can no longer verify', async () => {
|
||||
await consumeImpersonationJti('jti-old', exp, now);
|
||||
const dir = path.join(stateDir, 'impersonation-jti');
|
||||
|
||||
@@ -20,7 +20,8 @@ async function pruneExpired(dir: string, nowMs: number): Promise<void> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Record an impersonation token's jti as used; false when it was used before.
|
||||
* Record an impersonation token's jti as used; false when it was used before
|
||||
* or the use can't be recorded.
|
||||
*
|
||||
* The in-process cache alone forgot every jti on restart and knew nothing of
|
||||
* other replicas, so a captured handoff link could be redeemed again within
|
||||
@@ -45,11 +46,12 @@ export async function consumeImpersonationJti(
|
||||
return true;
|
||||
} catch (err) {
|
||||
if ((err as NodeJS.ErrnoException).code === 'EEXIST') return false;
|
||||
// The state directory is documented as always writable; if it is not,
|
||||
// keep impersonation working on the in-process cache.
|
||||
logger.warn('Impersonation replay store unavailable, using the in-process cache only', {
|
||||
// Without the marker a restart or another replica would accept the
|
||||
// token again, so it is refused rather than trusted to this process's
|
||||
// memory.
|
||||
logger.error('Impersonation replay store unavailable, refusing the token', {
|
||||
error: err instanceof Error ? err.message : 'Unknown',
|
||||
});
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user