mirror of
https://github.com/bulwarkmail/webmail.git
synced 2026-11-02 08:15:55 +00:00
137 lines
4.0 KiB
TypeScript
137 lines
4.0 KiB
TypeScript
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
|
|
vi.mock('next/server', () => ({
|
|
NextResponse: {
|
|
json: (data: unknown, init?: { status?: number }) => ({
|
|
json: async () => data,
|
|
status: init?.status ?? 200,
|
|
}),
|
|
},
|
|
}));
|
|
|
|
const cookieSet = vi.fn();
|
|
const cookieValues = new Map<string, string>();
|
|
vi.mock('next/headers', () => ({
|
|
cookies: async () => ({
|
|
set: cookieSet,
|
|
get: (name: string) => (cookieValues.has(name) ? { value: cookieValues.get(name) } : undefined),
|
|
}),
|
|
}));
|
|
|
|
vi.mock('@/lib/logger', () => ({
|
|
logger: { warn: vi.fn(), error: vi.fn(), debug: vi.fn(), info: vi.fn() },
|
|
}));
|
|
|
|
vi.mock('@/lib/auth/crypto', () => ({
|
|
encryptPayload: () => 'encrypted',
|
|
}));
|
|
|
|
vi.mock('@/lib/oauth/pkce-server', () => ({
|
|
generateCodeVerifierServer: () => 'verifier',
|
|
generateCodeChallengeServer: () => 'challenge',
|
|
generateStateServer: () => 'state-123',
|
|
}));
|
|
|
|
const getRequiredConfig = vi.fn((_serverId?: string | null) => ({
|
|
clientId: 'client',
|
|
discoveryUrl: 'https://idp.example.com',
|
|
}));
|
|
vi.mock('@/lib/oauth/token-exchange', () => ({
|
|
getRequiredConfig: (serverId?: string | null) => getRequiredConfig(serverId),
|
|
getDiscoveryValidator: () => undefined,
|
|
}));
|
|
|
|
vi.mock('@/lib/oauth/discovery', () => ({
|
|
discoverOAuth: async () => ({
|
|
issuer: 'https://idp.example.com',
|
|
authorization_endpoint: 'https://idp.example.com/authorize',
|
|
token_endpoint: 'https://idp.example.com/token',
|
|
}),
|
|
}));
|
|
|
|
vi.mock('@/lib/oauth/tokens', () => ({
|
|
getOauthScopes: () => 'openid email',
|
|
refreshTokenServerCookieName: (slot: number) => `jmap_rt_server_${slot}`,
|
|
}));
|
|
|
|
vi.mock('@/lib/oauth/cookie-config', () => ({
|
|
getCookieOptions: () => ({ httpOnly: true }),
|
|
}));
|
|
|
|
vi.mock('@/lib/auth/session-secret', () => ({
|
|
hasSessionSecret: () => true,
|
|
}));
|
|
|
|
vi.mock('@/lib/admin/config-manager', () => ({
|
|
configManager: { get: () => '' },
|
|
}));
|
|
|
|
function mockRequest(body: Record<string, unknown>): unknown {
|
|
return {
|
|
json: async () => body,
|
|
headers: {
|
|
get: (k: string) =>
|
|
k === 'origin' ? 'https://mail.example.com' : k === 'host' ? 'mail.example.com' : null,
|
|
},
|
|
nextUrl: { origin: 'https://mail.example.com' },
|
|
};
|
|
}
|
|
|
|
async function callRoute(body: Record<string, unknown>) {
|
|
const { POST } = await import('@/app/api/auth/sso/start/route');
|
|
const res = (await POST(mockRequest(body) as Parameters<typeof POST>[0])) as unknown as {
|
|
status: number;
|
|
json: () => Promise<{ authorize_url: string }>;
|
|
};
|
|
return { status: res.status, body: await res.json() };
|
|
}
|
|
|
|
describe('sso start route', () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
cookieValues.clear();
|
|
});
|
|
|
|
it('re-authenticates against the paired slot\'s server', async () => {
|
|
cookieValues.set('jmap_rt_server_2', 'server-b');
|
|
const { status } = await callRoute({
|
|
redirect_uri: 'https://mail.example.com/auth/callback',
|
|
purpose: 'reauth',
|
|
slot: 2,
|
|
});
|
|
|
|
expect(status).toBe(200);
|
|
expect(getRequiredConfig).toHaveBeenCalledWith('server-b');
|
|
});
|
|
|
|
it('ignores the slot cookie for a regular login', async () => {
|
|
cookieValues.set('jmap_rt_server_0', 'server-b');
|
|
await callRoute({ redirect_uri: 'https://mail.example.com/auth/callback' });
|
|
|
|
expect(getRequiredConfig).toHaveBeenCalledWith(null);
|
|
});
|
|
|
|
it('forces prompt=login without max_age for re-authentication', async () => {
|
|
const { status, body } = await callRoute({
|
|
redirect_uri: 'https://mail.example.com/auth/callback',
|
|
purpose: 'reauth',
|
|
});
|
|
|
|
expect(status).toBe(200);
|
|
const url = new URL(body.authorize_url);
|
|
expect(url.searchParams.get('prompt')).toBe('login');
|
|
expect(url.searchParams.has('max_age')).toBe(false);
|
|
});
|
|
|
|
it('sends neither prompt nor max_age for a regular login', async () => {
|
|
const { status, body } = await callRoute({
|
|
redirect_uri: 'https://mail.example.com/auth/callback',
|
|
});
|
|
|
|
expect(status).toBe(200);
|
|
const url = new URL(body.authorize_url);
|
|
expect(url.searchParams.has('prompt')).toBe(false);
|
|
expect(url.searchParams.has('max_age')).toBe(false);
|
|
});
|
|
});
|