forked from
nekomimi.pet/wisp.place-monorepo
Monorepo for Wisp.place. A static site hosting service built on top of the AT Protocol.
1import { describe, test, expect } from 'bun:test'
2import { verifyRequestOrigin } from './csrf'
3
4describe('verifyRequestOrigin', () => {
5 test('should accept matching origin and host', () => {
6 expect(verifyRequestOrigin('https://example.com', ['example.com'])).toBe(true)
7 expect(verifyRequestOrigin('http://localhost:8000', ['localhost:8000'])).toBe(true)
8 expect(verifyRequestOrigin('https://app.example.com', ['app.example.com'])).toBe(true)
9 })
10
11 test('should accept origin matching one of multiple allowed hosts', () => {
12 const allowedHosts = ['example.com', 'app.example.com', 'localhost:8000']
13 expect(verifyRequestOrigin('https://example.com', allowedHosts)).toBe(true)
14 expect(verifyRequestOrigin('https://app.example.com', allowedHosts)).toBe(true)
15 expect(verifyRequestOrigin('http://localhost:8000', allowedHosts)).toBe(true)
16 })
17
18 test('should reject non-matching origin', () => {
19 expect(verifyRequestOrigin('https://evil.com', ['example.com'])).toBe(false)
20 expect(verifyRequestOrigin('https://fake-example.com', ['example.com'])).toBe(false)
21 expect(verifyRequestOrigin('https://example.com.evil.com', ['example.com'])).toBe(false)
22 })
23
24 test('should reject empty origin', () => {
25 expect(verifyRequestOrigin('', ['example.com'])).toBe(false)
26 })
27
28 test('should reject invalid URL format', () => {
29 expect(verifyRequestOrigin('not-a-url', ['example.com'])).toBe(false)
30 expect(verifyRequestOrigin('javascript:alert(1)', ['example.com'])).toBe(false)
31 expect(verifyRequestOrigin('file:///etc/passwd', ['example.com'])).toBe(false)
32 })
33
34 test('should handle different protocols correctly', () => {
35 // Same host, different protocols should match (we only check host)
36 expect(verifyRequestOrigin('http://example.com', ['example.com'])).toBe(true)
37 expect(verifyRequestOrigin('https://example.com', ['example.com'])).toBe(true)
38 })
39
40 test('should handle port numbers correctly', () => {
41 expect(verifyRequestOrigin('http://localhost:3000', ['localhost:3000'])).toBe(true)
42 expect(verifyRequestOrigin('http://localhost:3000', ['localhost:8000'])).toBe(false)
43 expect(verifyRequestOrigin('http://localhost', ['localhost'])).toBe(true)
44 })
45
46 test('should handle subdomains correctly', () => {
47 expect(verifyRequestOrigin('https://sub.example.com', ['sub.example.com'])).toBe(true)
48 expect(verifyRequestOrigin('https://sub.example.com', ['example.com'])).toBe(false)
49 })
50
51 test('should handle case sensitivity (exact match required)', () => {
52 // URL host is automatically lowercased by URL parser
53 expect(verifyRequestOrigin('https://EXAMPLE.COM', ['example.com'])).toBe(true)
54 expect(verifyRequestOrigin('https://example.com', ['example.com'])).toBe(true)
55 // But allowed hosts are case-sensitive
56 expect(verifyRequestOrigin('https://example.com', ['EXAMPLE.COM'])).toBe(false)
57 })
58
59 test('should handle trailing slashes in origin', () => {
60 expect(verifyRequestOrigin('https://example.com/', ['example.com'])).toBe(true)
61 })
62
63 test('should handle paths in origin (host extraction)', () => {
64 expect(verifyRequestOrigin('https://example.com/path/to/page', ['example.com'])).toBe(true)
65 expect(verifyRequestOrigin('https://evil.com/example.com', ['example.com'])).toBe(false)
66 })
67
68 test('should reject when allowed hosts is empty', () => {
69 expect(verifyRequestOrigin('https://example.com', [])).toBe(false)
70 })
71
72 test('should handle IPv4 addresses', () => {
73 expect(verifyRequestOrigin('http://127.0.0.1:8000', ['127.0.0.1:8000'])).toBe(true)
74 expect(verifyRequestOrigin('http://192.168.1.1', ['192.168.1.1'])).toBe(true)
75 })
76
77 test('should handle IPv6 addresses', () => {
78 expect(verifyRequestOrigin('http://[::1]:8000', ['[::1]:8000'])).toBe(true)
79 expect(verifyRequestOrigin('http://[2001:db8::1]', ['[2001:db8::1]'])).toBe(true)
80 })
81})