Files

130 lines
4.9 KiB
TypeScript

import { expect, test } from './fixtures';
import { login } from './helpers/auth';
test.describe('Auth', () => {
test('POST /api/auth/login returns a token and the user', async ({ api, seed }) => {
const response = await api.post('/api/auth/login', {
username: seed.admin.username,
password: seed.admin.password,
});
expect(response.status).toBe(200);
expect(response.body).toMatchObject({
token: expect.any(String),
user: {
id: seed.admin.id,
username: seed.admin.username,
role: 'admin',
createdAt: expect.any(String),
isAdmin: true,
teams: expect.any(Array),
},
});
expect(response.body).not.toHaveProperty('requiresTwoFactor');
expect(response.body.user).not.toHaveProperty('password');
});
test('POST /api/auth/login matches usernames case-insensitively', async ({ api, seed }) => {
const response = await api.post('/api/auth/login', {
username: seed.user.username.toUpperCase(),
password: seed.user.password,
});
expect(response.status).toBe(200);
expect(response.body.token).toEqual(expect.any(String));
expect(response.body.user).toMatchObject({ id: seed.user.id, role: 'user', isAdmin: false });
// The response echoes the submitted username verbatim; the lookup itself is case-insensitive.
expect(response.body.user.username.toLowerCase()).toBe(seed.user.username);
expect(response.body.user.teams.map((t: any) => t.id)).toContain(seed.team.id);
// The session resolves to the stored (lowercase) username.
const me = await api.bearer(response.body.token).get('/api/me');
expect(me.status).toBe(200);
expect(me.body.user).toMatchObject({ id: seed.user.id, username: seed.user.username });
});
test('POST /api/auth/login rejects bad credentials', async ({ api, seed }) => {
const wrongPassword = await api.post('/api/auth/login', {
username: seed.admin.username,
password: 'not-the-password',
});
const unknownUser = await api.post('/api/auth/login', {
username: 'no-such-user',
password: seed.admin.password,
});
expect(wrongPassword.status).toBe(401);
expect(wrongPassword.body.error).toMatchObject({
code: 'incorrect-username-password',
status: 401,
});
expect(unknownUser.status).toBe(401);
expect(unknownUser.body.error.code).toBe('incorrect-username-password');
});
test('POST /api/auth/login validates the body', async ({ api, seed }) => {
const missingPassword = await api.post('/api/auth/login', { username: seed.admin.username });
const missingUsername = await api.post('/api/auth/login', { password: seed.admin.password });
const empty = await api.post('/api/auth/login', {});
expect(missingPassword.status).toBe(400);
expect(missingUsername.status).toBe(400);
expect(empty.status).toBe(400);
expect(empty.body.error).toMatchObject({ status: 400 });
});
test('POST /api/auth/verify returns the current session', async ({ admin, user, seed }) => {
const response = await admin.post('/api/auth/verify');
const asUser = await user.post('/api/auth/verify');
expect(response.status).toBe(200);
// The body is the flat user record plus its teams.
expect(response.body).toMatchObject({
id: seed.admin.id,
username: seed.admin.username,
role: 'admin',
createdAt: expect.any(String),
isAdmin: true,
twoFactorRequired: expect.any(Boolean),
teams: expect.any(Array),
});
expect(response.body).not.toHaveProperty('password');
expect(asUser.status).toBe(200);
expect(asUser.body.id).toBe(seed.user.id);
expect(asUser.body.teams.map((t: any) => t.id)).toContain(seed.team.id);
});
test('POST /api/auth/verify rejects anonymous, invalid and API key callers', async ({
api,
apiKey,
}) => {
const anonymous = await api.post('/api/auth/verify');
const garbage = await api.bearer('not-a-token').post('/api/auth/verify');
const viaApiKey = await apiKey.post('/api/auth/verify');
expect(anonymous.status).toBe(401);
expect(garbage.status).toBe(401);
// API keys are blocked on /api/auth/*.
expect(viaApiKey.status).toBe(401);
});
test('POST /api/auth/logout acknowledges the logout', async ({ api, seed }) => {
// Use a dedicated session so the shared worker tokens are never touched.
const token = await login(api, seed.viewer);
const response = await api.bearer(token).post('/api/auth/logout');
expect(response.status).toBe(200);
expect(response.body).toEqual({ ok: true });
// Without Redis the stateless token remains valid, so no follow-up 401 is asserted.
});
test('POST /api/auth/logout requires a session', async ({ api, apiKey }) => {
const anonymous = await api.post('/api/auth/logout');
const viaApiKey = await apiKey.post('/api/auth/logout');
expect(anonymous.status).toBe(401);
expect(viaApiKey.status).toBe(401);
});
});