Spaces:
Sleeping
Sleeping
assafvayner HF Staff
fix(auth): gate admin routes on the decoded path and in the actions themselves
857fbc2 Download tests/unit/auth/guards.test.ts from assafvayner/splitwise: direct link, hf CLI and curl.
- Browser
- Download file 2.66 kB
-
https://huggingface.co/spaces/assafvayner/splitwise/resolve/main/tests/unit/auth/guards.test.ts
- Command line
-
hf download hf://spaces/assafvayner/splitwise/tests/unit/auth/guards.test.ts
-
curl -L -o guards.test.ts https://huggingface.co/spaces/assafvayner/splitwise/resolve/main/tests/unit/auth/guards.test.ts
2.66 kB
| import { mkdtemp, rm } from 'node:fs/promises'; | |
| import { tmpdir } from 'node:os'; | |
| import { join } from 'node:path'; | |
| import { afterEach, beforeEach, describe, expect, it } from 'vitest'; | |
| import { decodePathname } from '../../../src/lib/server/auth/decode-path'; | |
| import { requireAdmin, requireMemberTarget } from '../../../src/lib/server/auth/guards'; | |
| import { LocalFsStore } from '../../../src/lib/server/storage/local-fs-store'; | |
| import { UsersRepo, UserValidationError } from '../../../src/lib/server/users/repository'; | |
| import type { User } from '../../../src/lib/domain/types'; | |
| const localsFor = (user: User | null) => ({ user }) as unknown as App.Locals; | |
| describe('requireAdmin', () => { | |
| const admin = { id: 'u_admin', role: 'admin' } as User; | |
| const member = { id: 'u_bob', role: 'member' } as User; | |
| it('returns the admin', () => { | |
| expect(requireAdmin(localsFor(admin))).toBe(admin); | |
| }); | |
| it('rejects members and anonymous requests with 403', () => { | |
| for (const user of [member, null]) { | |
| expect(() => requireAdmin(localsFor(user))).toThrowError( | |
| expect.objectContaining({ status: 403 }) | |
| ); | |
| } | |
| }); | |
| }); | |
| describe('requireMemberTarget', () => { | |
| let dir: string; | |
| let repo: UsersRepo; | |
| let bobId: string; | |
| const clock = () => new Date('2026-08-29T12:00:00.000Z'); | |
| beforeEach(async () => { | |
| dir = await mkdtemp(join(tmpdir(), 'splitwise-guards-')); | |
| repo = await UsersRepo.load(new LocalFsStore(dir), { clock }); | |
| bobId = (await repo.create({ username: 'bob', displayName: 'Bob', password: 'temppass1' })).id; | |
| }); | |
| afterEach(() => rm(dir, { recursive: true, force: true })); | |
| it('returns the member', () => { | |
| expect(requireMemberTarget(repo, bobId).username).toBe('bob'); | |
| }); | |
| it('rejects unknown ids and the admin account', () => { | |
| for (const id of ['', 'u_nope', 'u_admin']) { | |
| expect(() => requireMemberTarget(repo, id)).toThrowError(UserValidationError); | |
| try { | |
| requireMemberTarget(repo, id); | |
| } catch (e) { | |
| expect((e as UserValidationError).message).toBe('Unknown user'); | |
| expect((e as UserValidationError).field).toBe('id'); | |
| } | |
| } | |
| }); | |
| }); | |
| describe('decodePathname', () => { | |
| it('decodes escapes the way SvelteKit matches routes', () => { | |
| expect(decodePathname('/%61dmin')).toBe('/admin'); | |
| expect(decodePathname('/%61dmin/storage')).toBe('/admin/storage'); | |
| expect(decodePathname('/a%2Fb')).toBe('/a/b'); | |
| expect(decodePathname('/admin')).toBe('/admin'); | |
| }); | |
| it('leaves an encoded percent sign intact', () => { | |
| expect(decodePathname('/x%25y')).toBe('/x%25y'); | |
| }); | |
| it('throws on a malformed escape', () => { | |
| expect(() => decodePathname('/%zz')).toThrowError(URIError); | |
| }); | |
| }); | |