mirror of
https://github.com/bulwarkmail/webmail.git
synced 2026-10-31 00:12:39 +00:00
fix: stop sending the webmail's OAuth client secret to user-chosen mail servers
With custom JMAP endpoints allowed, the password+TOTP login posted the admin's client secret to `/auth/token` on whatever server the user named. The secret now only goes to admin-configured servers.
This commit is contained in:
@@ -65,3 +65,41 @@ describe('totp-token-exchange error detail', () => {
|
||||
expect((await res.json()).detail).toBe(SECRET_BODY);
|
||||
});
|
||||
});
|
||||
|
||||
describe('totp-token-exchange client secret', () => {
|
||||
// The webmail's OAuth client secret belongs to the admin's own server. A
|
||||
// user-chosen server (custom JMAP endpoints) must never receive it.
|
||||
function stalwart() {
|
||||
const bodies: string[] = [];
|
||||
const handler = vi.fn(async (url: unknown, init?: { body?: unknown }) => {
|
||||
bodies.push(String(init?.body ?? ''));
|
||||
return String(url).endsWith('/api/auth')
|
||||
? Response.json({ type: 'authenticated', client_code: 'code-1' })
|
||||
: Response.json({ access_token: 'at', refresh_token: 'rt', expires_in: 3600 });
|
||||
});
|
||||
return { bodies, handler };
|
||||
}
|
||||
|
||||
it('does not send the client secret to a user-chosen server', async () => {
|
||||
config.allowCustomJmapEndpoint = true;
|
||||
config.oauthClientSecret = 'webmail-client-secret';
|
||||
const { bodies, handler } = stalwart();
|
||||
upstream.mockImplementation(handler);
|
||||
const { POST } = await import('@/app/api/auth/totp-token-exchange/route');
|
||||
const res = await POST(login('https://somewhere.example'));
|
||||
expect(res.status).toBe(200);
|
||||
expect(bodies).toHaveLength(2);
|
||||
expect(bodies.join(' ')).not.toContain('webmail-client-secret');
|
||||
});
|
||||
|
||||
it('still sends it to the configured server', async () => {
|
||||
config.jmapServerUrl = 'https://mail.example.org';
|
||||
config.oauthClientSecret = 'webmail-client-secret';
|
||||
const { bodies, handler } = stalwart();
|
||||
vi.stubGlobal('fetch', handler);
|
||||
const { POST } = await import('@/app/api/auth/totp-token-exchange/route');
|
||||
const res = await POST(login('https://mail.example.org'));
|
||||
expect(res.status).toBe(200);
|
||||
expect(new URLSearchParams(bodies[1]).get('client_secret')).toBe('webmail-client-secret');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -166,7 +166,9 @@ export async function stalwartPasswordLogin(
|
||||
code_verifier: verifier,
|
||||
});
|
||||
// Confidential clients still send their secret; harmless for public clients.
|
||||
if (opts.clientSecret) tokenParams.set('client_secret', opts.clientSecret);
|
||||
// The secret is the admin's, for the admin's server: a user-chosen server
|
||||
// (allowCustomJmapEndpoint) never receives it.
|
||||
if (opts.clientSecret && opts.trusted) tokenParams.set('client_secret', opts.clientSecret);
|
||||
|
||||
let tokens: { access_token?: string; expires_in?: number; refresh_token?: string };
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user