The conformance board (tests/afi/test_capability_parity.py) is now fully green: 90 capability cells + 4 meta-locks + 3 codegen byte-parity = 97 passed. The gaps the prose table used to launder as "Django-only" / "out of scope" are wired, against the pinned-spec model (single-authored spec, byte-identical conformance across languages) — never per-language reimplementation. FastAPI — edge_manifest + PSR (logic single-sourced in mizan_core.manifest), WebSocket RPC (/ws/ through the shared dispatch), SSR (the framework-agnostic SSRBridge relocated to mizan_core.ssr; Django rides it from there), Shapes (SQLAlchemy projection, same declaration surface as django-readers), Forms (Pydantic schema/validate/submit). Rust (Axum + Tauri + cores/mizan-rust) — X-Mizan-Invalidate header, auth= enforcement, origin HMAC cache, edge manifest + PSR, WebSocket handler / IPC subscription channel, multipart upload, SSR bridge, Shapes, Forms; JWT/MWT mint+verify and cache-key derivation byte-pinned to the Python reference (cache_keys_pin, token_pin, invalidate_header_pin). TypeScript — a KDL IR emitter byte-identical to the Python build_ir (so a TS backend can feed the codegen — the largest gap), multipart upload, session-init, WebSocket transport, SSR bridge, JWT/MWT mint (pinned to Python), Shapes, Forms. Verified in the merged tree: core 25, fastapi 74, django 353/21-skip, mizan-rust (incl. cross-language pins) green, axum 10, tauri 8, mizan-ts 103/2-skip. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
254 lines
8.9 KiB
TypeScript
254 lines
8.9 KiB
TypeScript
/**
|
|
* MWT / JWT token tests — decode round-trip + cross-language byte-parity pins
|
|
* against the live Python mint (`cores/mizan-python`).
|
|
*/
|
|
|
|
import { describe, test, expect } from 'bun:test'
|
|
import { createHmac, createHash } from 'crypto'
|
|
import { execFileSync } from 'child_process'
|
|
import { existsSync } from 'fs'
|
|
import { resolve } from 'path'
|
|
import {
|
|
decodeMwt,
|
|
decodeJwtBearer,
|
|
identityFromMwt,
|
|
signMwt,
|
|
computePermissionKey,
|
|
createAccessToken,
|
|
createRefreshToken,
|
|
type MintUser,
|
|
} from '../src'
|
|
|
|
function b64url(buf: Buffer | string): string {
|
|
return Buffer.from(buf).toString('base64url')
|
|
}
|
|
|
|
/** Mint an HS256 MWT with node crypto, mirroring Python create_mwt. */
|
|
function mint(payload: Record<string, any>, secret: string, kid = 'v1'): string {
|
|
const header = b64url(JSON.stringify({ alg: 'HS256', kid, typ: 'JWT' }))
|
|
const body = b64url(JSON.stringify(payload))
|
|
const sig = createHmac('sha256', secret).update(`${header}.${body}`).digest('base64url')
|
|
return `${header}.${body}.${sig}`
|
|
}
|
|
|
|
const SECRET = 'round-trip-secret'
|
|
const now = Math.floor(Date.now() / 1000)
|
|
|
|
function basePayload(overrides: Record<string, any> = {}) {
|
|
return {
|
|
sub: '7',
|
|
staff: true,
|
|
super: false,
|
|
pkey: 'abc123',
|
|
aud: 'mizan',
|
|
iat: now,
|
|
nbf: now,
|
|
exp: now + 300,
|
|
...overrides,
|
|
}
|
|
}
|
|
|
|
describe('MWT round-trip', () => {
|
|
test('valid token decodes', () => {
|
|
const token = mint(basePayload(), SECRET)
|
|
const p = decodeMwt(token, SECRET)
|
|
expect(p).not.toBeNull()
|
|
expect(p!.sub).toBe('7')
|
|
expect(p!.staff).toBe(true)
|
|
expect(p!.super).toBe(false)
|
|
expect(p!.pkey).toBe('abc123')
|
|
expect(p!.kid).toBe('v1')
|
|
expect(p!.aud).toBe('mizan')
|
|
})
|
|
|
|
test('identityFromMwt maps claims', () => {
|
|
const token = mint(basePayload({ sub: '99', staff: false, super: true }), SECRET)
|
|
const p = decodeMwt(token, SECRET)!
|
|
expect(identityFromMwt(p)).toEqual({
|
|
isAuthenticated: true,
|
|
isStaff: false,
|
|
isSuperuser: true,
|
|
id: 99,
|
|
})
|
|
})
|
|
|
|
test('decodeJwtBearer strips Bearer prefix', () => {
|
|
const token = mint(basePayload(), SECRET)
|
|
const p = decodeJwtBearer(`Bearer ${token}`, SECRET)
|
|
expect(p).not.toBeNull()
|
|
expect(p!.sub).toBe('7')
|
|
})
|
|
|
|
test('null on tampered signature', () => {
|
|
const token = mint(basePayload(), SECRET)
|
|
const tampered = token.slice(0, -2) + (token.endsWith('AA') ? 'BB' : 'AA')
|
|
expect(decodeMwt(tampered, SECRET)).toBeNull()
|
|
})
|
|
|
|
test('null on wrong secret', () => {
|
|
const token = mint(basePayload(), SECRET)
|
|
expect(decodeMwt(token, 'other-secret')).toBeNull()
|
|
})
|
|
|
|
test('null on expired exp', () => {
|
|
const token = mint(basePayload({ exp: now - 10 }), SECRET)
|
|
expect(decodeMwt(token, SECRET)).toBeNull()
|
|
})
|
|
|
|
test('null on future nbf', () => {
|
|
const token = mint(basePayload({ nbf: now + 1000 }), SECRET)
|
|
expect(decodeMwt(token, SECRET)).toBeNull()
|
|
})
|
|
|
|
test('null on wrong aud', () => {
|
|
const token = mint(basePayload({ aud: 'other' }), SECRET)
|
|
expect(decodeMwt(token, SECRET)).toBeNull()
|
|
})
|
|
|
|
test('null on malformed token', () => {
|
|
expect(decodeMwt('not.a.jwt', SECRET)).toBeNull()
|
|
expect(decodeMwt('onlyonepart', SECRET)).toBeNull()
|
|
expect(decodeMwt('', SECRET)).toBeNull()
|
|
})
|
|
})
|
|
|
|
describe('MWT cross-language pin (Python create_mwt)', () => {
|
|
const TOKEN = 'eyJhbGciOiJIUzI1NiIsImtpZCI6InYxIiwidHlwIjoiSldUIn0.eyJzdWIiOiI0MiIsInN0YWZmIjp0cnVlLCJzdXBlciI6ZmFsc2UsInBrZXkiOiIwZTk5OGE5ZmYxNjkwNDYzN2EwM2QyZWEwZmJkYmY5NzQyOTdhOWQxYTVkMjViOGQ0Mjk0ZmE4ODIxMTVlNDU3IiwiYXVkIjoibWl6YW4iLCJpYXQiOjE3MDAwMDAwMDAsIm5iZiI6MTcwMDAwMDAwMCwiZXhwIjo0MTAyNDQ0ODAwfQ._V92JXiLSLXoyuSwbNvvJjwzgmczmC7dvX34kVSLIa8'
|
|
const PIN_SECRET = 'pin-test-secret-mwt'
|
|
|
|
test('decodes the Python-minted token', () => {
|
|
const p = decodeMwt(TOKEN, PIN_SECRET)
|
|
expect(p).not.toBeNull()
|
|
expect(p!.sub).toBe('42')
|
|
expect(p!.staff).toBe(true)
|
|
expect(p!.super).toBe(false)
|
|
expect(p!.pkey).toBe('0e998a9ff16904637a03d2ea0fbdbf974297a9d1a5d25b8d4294fa882115e457')
|
|
expect(p!.kid).toBe('v1')
|
|
expect(p!.aud).toBe('mizan')
|
|
})
|
|
|
|
test('identity from Python-minted token', () => {
|
|
const p = decodeMwt(TOKEN, PIN_SECRET)!
|
|
expect(identityFromMwt(p)).toEqual({
|
|
isAuthenticated: true,
|
|
isStaff: true,
|
|
isSuperuser: false,
|
|
id: 42,
|
|
})
|
|
})
|
|
})
|
|
|
|
// ─── Mint: round-trip + cross-language byte-parity ────────────────────────────
|
|
|
|
const REPO_ROOT = resolve(import.meta.dir, '../../..')
|
|
const MIZAN_PYTHON = resolve(REPO_ROOT, 'cores/mizan-python')
|
|
|
|
const UV_AVAILABLE = (() => {
|
|
try {
|
|
execFileSync('uv', ['--version'], { stdio: 'ignore' })
|
|
return existsSync(resolve(MIZAN_PYTHON, 'pyproject.toml'))
|
|
} catch {
|
|
return false
|
|
}
|
|
})()
|
|
|
|
/**
|
|
* Run a Python snippet against cores/mizan-python and return stdout (trimmed).
|
|
* `time.time` is pinned so the production mint functions are deterministic.
|
|
*/
|
|
function py(snippet: string): string {
|
|
return execFileSync('uv', ['run', '--project', MIZAN_PYTHON, 'python', '-c', snippet], {
|
|
encoding: 'utf-8',
|
|
}).trim()
|
|
}
|
|
|
|
describe('MWT mint — round-trip', () => {
|
|
const SECRET = 'mint-roundtrip-secret'
|
|
|
|
test('signMwt produces a token decodeMwt accepts', () => {
|
|
const user: MintUser = { pk: 7, isStaff: true, isSuperuser: false, permissions: ['a.view', 'a.edit'] }
|
|
const token = signMwt(user, SECRET, { now: Math.floor(Date.now() / 1000) })
|
|
const p = decodeMwt(token, SECRET)
|
|
expect(p).not.toBeNull()
|
|
expect(p!.sub).toBe('7')
|
|
expect(p!.staff).toBe(true)
|
|
expect(p!.super).toBe(false)
|
|
expect(p!.kid).toBe('v1')
|
|
expect(p!.aud).toBe('mizan')
|
|
// pkey is the permission hash, surviving the round-trip.
|
|
expect(p!.pkey).toBe(computePermissionKey(user))
|
|
})
|
|
|
|
test('computePermissionKey matches the documented blob hash', () => {
|
|
const user: MintUser = { pk: 1, isStaff: true, isSuperuser: false, permissions: ['z', 'a'] }
|
|
// "1:0:a,z" — staff:super:sorted-perms.
|
|
const expected = createHash('sha256').update('1:0:a,z', 'utf-8').digest('hex')
|
|
expect(computePermissionKey(user)).toBe(expected)
|
|
})
|
|
})
|
|
|
|
describe('MWT mint — cross-language pin (Python create_mwt)', () => {
|
|
const SECRET = 'pin-mint-secret-mwt'
|
|
const NOW = 1700000000
|
|
|
|
test.skipIf(!UV_AVAILABLE)('TS signMwt byte-identical to Python create_mwt', () => {
|
|
const user: MintUser = {
|
|
pk: 42,
|
|
isStaff: true,
|
|
isSuperuser: false,
|
|
permissions: ['app.view_thing', 'app.change_thing'],
|
|
}
|
|
const tsToken = signMwt(user, SECRET, { ttl: 300, now: NOW })
|
|
|
|
// Drive the REAL create_mwt with time.time pinned to NOW and a
|
|
// user stub whose get_all_permissions returns the same perms.
|
|
const pyToken = py(String.raw`
|
|
import time, sys
|
|
time.time = lambda: ${NOW}
|
|
from mizan_core.mwt import create_mwt
|
|
|
|
class U:
|
|
pk = 42
|
|
is_staff = True
|
|
is_superuser = False
|
|
def get_all_permissions(self):
|
|
return {"app.view_thing", "app.change_thing"}
|
|
|
|
sys.stdout.write(create_mwt(U(), ${JSON.stringify(SECRET)}, ttl=300))
|
|
`)
|
|
expect(tsToken).toBe(pyToken)
|
|
})
|
|
})
|
|
|
|
describe('JWT mint — cross-language pin (Python create_access/refresh_token)', () => {
|
|
const SECRET = 'pin-mint-secret-jwt'
|
|
const NOW = 1700000000
|
|
|
|
const config = { privateKey: SECRET, accessTokenExpiresIn: 300, refreshTokenExpiresIn: 604800 }
|
|
const claims = { userId: 42, sessionKey: 'sess-abc', isStaff: true, isSuperuser: false }
|
|
|
|
test.skipIf(!UV_AVAILABLE)('TS createAccessToken byte-identical to Python', () => {
|
|
const tsToken = createAccessToken(claims, config, NOW)
|
|
const pyToken = py(String.raw`
|
|
import time, sys
|
|
time.time = lambda: ${NOW}
|
|
from mizan_core.auth.jwt import JWTConfig, create_access_token
|
|
cfg = JWTConfig(private_key=${JSON.stringify(SECRET)}, public_key=${JSON.stringify(SECRET)})
|
|
sys.stdout.write(create_access_token(42, "sess-abc", cfg, is_staff=True, is_superuser=False))
|
|
`)
|
|
expect(tsToken).toBe(pyToken)
|
|
})
|
|
|
|
test.skipIf(!UV_AVAILABLE)('TS createRefreshToken byte-identical to Python', () => {
|
|
const tsToken = createRefreshToken(claims, config, NOW)
|
|
const pyToken = py(String.raw`
|
|
import time, sys
|
|
time.time = lambda: ${NOW}
|
|
from mizan_core.auth.jwt import JWTConfig, create_refresh_token
|
|
cfg = JWTConfig(private_key=${JSON.stringify(SECRET)}, public_key=${JSON.stringify(SECRET)})
|
|
sys.stdout.write(create_refresh_token(42, "sess-abc", cfg, is_staff=True, is_superuser=False))
|
|
`)
|
|
expect(tsToken).toBe(pyToken)
|
|
})
|
|
})
|