- sessionKey, dateFormat, FileuploadField (protected thumbnails and keyboard reorder backstops), RelationChildModal, RelationPivotModal, RelationManager row actions and create-screen deferral, date and time list cells; typed deferred relation-schema and file-list fixtures - scripts/check-phase12.2.sh: go, security (named tests, refuses missing or skipped), spa, openapi, dist, docs, hygiene and app stages, a detector self-test, one PASS or FAIL line per stage under --all
47 lines
1.7 KiB
TypeScript
47 lines
1.7 KiB
TypeScript
// Form session keys (Phase 12.2, D-02): 32 random bytes from
|
|
// crypto.getRandomValues as unpadded base64url, 43 characters, never the
|
|
// same twice, and only ever sent in the two headers.
|
|
import { describe, expect, it, vi } from 'vitest'
|
|
import { CHILD_SESSION_HEADER, SESSION_HEADER, newSessionKey } from '../../src/app/sessionKey'
|
|
|
|
const SERVER_PATTERN = /^[A-Za-z0-9_-]{32,128}$/
|
|
|
|
describe('newSessionKey', () => {
|
|
it('is 43 base64url characters the server accepts', () => {
|
|
for (let i = 0; i < 50; i++) {
|
|
const key = newSessionKey()
|
|
expect(key).toHaveLength(43)
|
|
expect(key).toMatch(/^[A-Za-z0-9_-]+$/)
|
|
expect(key).toMatch(SERVER_PATTERN)
|
|
expect(key).not.toContain('=')
|
|
}
|
|
})
|
|
|
|
it('draws 32 bytes from crypto.getRandomValues and encodes them', () => {
|
|
const spy = vi.spyOn(globalThis.crypto, 'getRandomValues').mockImplementation(<T extends ArrayBufferView | null>(array: T): T => {
|
|
const bytes = array as unknown as Uint8Array
|
|
bytes.fill(0xfb)
|
|
bytes[31] = 0xff
|
|
return array
|
|
})
|
|
const key = newSessionKey()
|
|
expect(spy).toHaveBeenCalledTimes(1)
|
|
const arg = spy.mock.calls[0]![0] as unknown as Uint8Array
|
|
expect(arg).toBeInstanceOf(Uint8Array)
|
|
expect(arg.byteLength).toBe(32)
|
|
// 0xfb bytes give "+" and "/" in standard base64: base64url turns them
|
|
// into "-" and "_".
|
|
expect(key).toBe('-_v7-_v7-_v7-_v7-_v7-_v7-_v7-_v7-_v7-_v7-_8')
|
|
})
|
|
|
|
it('differs on every call', () => {
|
|
const keys = new Set(Array.from({ length: 200 }, () => newSessionKey()))
|
|
expect(keys.size).toBe(200)
|
|
})
|
|
|
|
it('names the two headers', () => {
|
|
expect(SESSION_HEADER).toBe('X-Session-Key')
|
|
expect(CHILD_SESSION_HEADER).toBe('X-Child-Session-Key')
|
|
})
|
|
})
|