Files
caddy-proxy-manager/tests/integration/forward-auth.test.ts
fuomag9 155268a180 Fix 19 ESLint unused-variable errors across source and test files
Remove unused imports, functions, and variables flagged by
@typescript-eslint/no-unused-vars and no-useless-assignment rules.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 00:32:54 +02:00

284 lines
8.0 KiB
TypeScript

import { describe, it, expect, beforeEach } from 'vitest';
import { createHash, randomBytes } from 'node:crypto';
import { createTestDb, type TestDb } from '../helpers/db';
import {
forwardAuthSessions,
forwardAuthExchanges,
forwardAuthAccess,
groups,
users,
proxyHosts
} from '@/src/lib/db/schema';
import { eq } from 'drizzle-orm';
let db: TestDb;
beforeEach(() => {
db = createTestDb();
});
function nowIso() {
return new Date().toISOString();
}
function futureIso(seconds: number) {
return new Date(Date.now() + seconds * 1000).toISOString();
}
function hashToken(raw: string): string {
return createHash('sha256').update(raw).digest('hex');
}
async function insertUser(overrides: Partial<typeof users.$inferInsert> = {}) {
const now = nowIso();
const [user] = await db.insert(users).values({
email: `user${Math.random().toString(36).slice(2)}@localhost`,
name: 'Test User',
role: 'user',
provider: 'credentials',
subject: `test-${Date.now()}-${Math.random()}`,
status: 'active',
createdAt: now,
updatedAt: now,
...overrides,
}).returning();
return user;
}
async function insertProxyHost(overrides: Partial<typeof proxyHosts.$inferInsert> = {}) {
const now = nowIso();
const [host] = await db.insert(proxyHosts).values({
name: 'Test Host',
domains: JSON.stringify(['app.example.com']),
upstreams: JSON.stringify(['backend:8080']),
sslForced: true,
hstsEnabled: true,
hstsSubdomains: false,
allowWebsocket: true,
preserveHostHeader: true,
skipHttpsHostnameValidation: false,
enabled: true,
createdAt: now,
updatedAt: now,
...overrides,
}).returning();
return host;
}
describe('forward auth sessions', () => {
it('creates a session with hashed token', async () => {
const user = await insertUser();
const rawToken = randomBytes(32).toString('hex');
const tokenHash = hashToken(rawToken);
const now = nowIso();
const [session] = await db.insert(forwardAuthSessions).values({
userId: user.id,
tokenHash,
expiresAt: futureIso(3600),
createdAt: now,
}).returning();
expect(session.tokenHash).toBe(tokenHash);
expect(session.userId).toBe(user.id);
});
it('enforces unique token hashes', async () => {
const user = await insertUser();
const tokenHash = hashToken('same-token');
const now = nowIso();
await db.insert(forwardAuthSessions).values({
userId: user.id, tokenHash, expiresAt: futureIso(3600), createdAt: now,
});
await expect(
db.insert(forwardAuthSessions).values({
userId: user.id, tokenHash, expiresAt: futureIso(3600), createdAt: now,
})
).rejects.toThrow();
});
it('cascades user deletion to sessions', async () => {
const user = await insertUser();
const now = nowIso();
await db.insert(forwardAuthSessions).values({
userId: user.id,
tokenHash: hashToken('token1'),
expiresAt: futureIso(3600),
createdAt: now,
});
await db.delete(users).where(eq(users.id, user.id));
const sessions = await db.query.forwardAuthSessions.findMany();
expect(sessions).toHaveLength(0);
});
});
describe('forward auth exchanges', () => {
it('creates an exchange code linked to a session', async () => {
const user = await insertUser();
const now = nowIso();
const [session] = await db.insert(forwardAuthSessions).values({
userId: user.id,
tokenHash: hashToken('session-token'),
expiresAt: futureIso(3600),
createdAt: now,
}).returning();
const rawCode = randomBytes(32).toString('hex');
const [exchange] = await db.insert(forwardAuthExchanges).values({
sessionId: session.id,
codeHash: hashToken(rawCode),
sessionToken: 'raw-session-token',
redirectUri: 'https://app.example.com/path',
expiresAt: futureIso(60),
used: false,
createdAt: now,
}).returning();
expect(exchange.sessionId).toBe(session.id);
expect(exchange.sessionToken).toBe('raw-session-token');
expect(exchange.used).toBe(false);
});
it('cascades session deletion to exchanges', async () => {
const user = await insertUser();
const now = nowIso();
const [session] = await db.insert(forwardAuthSessions).values({
userId: user.id,
tokenHash: hashToken('session2'),
expiresAt: futureIso(3600),
createdAt: now,
}).returning();
await db.insert(forwardAuthExchanges).values({
sessionId: session.id,
codeHash: hashToken('code1'),
sessionToken: 'raw-token',
redirectUri: 'https://app.example.com/',
expiresAt: futureIso(60),
used: false,
createdAt: now,
});
await db.delete(forwardAuthSessions).where(eq(forwardAuthSessions.id, session.id));
const exchanges = await db.query.forwardAuthExchanges.findMany();
expect(exchanges).toHaveLength(0);
});
});
describe('forward auth access', () => {
it('creates user-level access for a proxy host', async () => {
const user = await insertUser();
const host = await insertProxyHost();
const now = nowIso();
const [access] = await db.insert(forwardAuthAccess).values({
proxyHostId: host.id,
userId: user.id,
groupId: null,
createdAt: now,
}).returning();
expect(access.proxyHostId).toBe(host.id);
expect(access.userId).toBe(user.id);
expect(access.groupId).toBeNull();
});
it('creates group-level access for a proxy host', async () => {
const host = await insertProxyHost();
const now = nowIso();
const [group] = await db.insert(groups).values({
name: 'Devs',
createdAt: now,
updatedAt: now,
}).returning();
const [access] = await db.insert(forwardAuthAccess).values({
proxyHostId: host.id,
userId: null,
groupId: group.id,
createdAt: now,
}).returning();
expect(access.groupId).toBe(group.id);
expect(access.userId).toBeNull();
});
it('prevents duplicate user access per host', async () => {
const user = await insertUser();
const host = await insertProxyHost();
const now = nowIso();
await db.insert(forwardAuthAccess).values({
proxyHostId: host.id, userId: user.id, groupId: null, createdAt: now,
});
await expect(
db.insert(forwardAuthAccess).values({
proxyHostId: host.id, userId: user.id, groupId: null, createdAt: now,
})
).rejects.toThrow();
});
it('cascades proxy host deletion to access entries', async () => {
const user = await insertUser();
const host = await insertProxyHost();
const now = nowIso();
await db.insert(forwardAuthAccess).values({
proxyHostId: host.id, userId: user.id, groupId: null, createdAt: now,
});
await db.delete(proxyHosts).where(eq(proxyHosts.id, host.id));
const access = await db.query.forwardAuthAccess.findMany();
expect(access).toHaveLength(0);
});
it('cascades group deletion to access entries', async () => {
const host = await insertProxyHost();
const now = nowIso();
const [group] = await db.insert(groups).values({
name: 'Team', createdAt: now, updatedAt: now,
}).returning();
await db.insert(forwardAuthAccess).values({
proxyHostId: host.id, userId: null, groupId: group.id, createdAt: now,
});
await db.delete(groups).where(eq(groups.id, group.id));
const access = await db.query.forwardAuthAccess.findMany();
expect(access).toHaveLength(0);
});
it('allows both user and group access on same host', async () => {
const user = await insertUser();
const host = await insertProxyHost();
const now = nowIso();
const [group] = await db.insert(groups).values({
name: 'Group', createdAt: now, updatedAt: now,
}).returning();
await db.insert(forwardAuthAccess).values([
{ proxyHostId: host.id, userId: user.id, groupId: null, createdAt: now },
{ proxyHostId: host.id, userId: null, groupId: group.id, createdAt: now },
]);
const access = await db.query.forwardAuthAccess.findMany({
where: (t, { eq }) => eq(t.proxyHostId, host.id),
});
expect(access).toHaveLength(2);
});
});