mirror of
https://github.com/luxfi/aml.git
synced 2026-09-24 00:25:20 +00:00
The console now renders through @hanzo/gui — the Hanzo design system — instead of a set of primitives written here over the raw tokens. Layout, surfaces, controls and type are the kit's. Two things deliberately are not: the four state colours, which must not change with a theme because a supervisor's copy of a screenshot has to mean what the screen meant, and the meter, which is drawn in SVG so a width that varies with data is an attribute rather than a style. The kit is imported at the part rather than at the barrel. @hanzo/gui's index re-exports every component it has, including ones built on react-native primitives this bundle never renders — Sheet's pan responder and the spinner's ActivityIndicator are the two that pulled a native runtime into a browser build. It is still one kit at one version. The whole CSP question is now in ui/src/gui.ts, stated once. GuiProvider would write the config's stylesheet as an inline <style>, which style-src governs and which would need 'unsafe-inline' or a hash that moves whenever the theme does. disableInjectCSS turns that off and the same CSS goes on through a constructed stylesheet — CSP does not govern the CSSOM, because the script that reached it had already passed script-src. The kit's per-component rules take the same route into empty <style> holders, which the SHA-256 of the empty string allows and which allows nothing else. ui/e2e is the part that was previously a claim. It builds the bundle, serves it under ui/csp.txt — byte-identical to the chart's HANZO_STATIC_CSP — seeds a session before the first script runs, and drives Chromium through every screen the rail offers. It carries a positive control: a style attribute parsed from markup is asserted to be REFUSED, because "no violations" means nothing without evidence that a violation would have been noticed. Writing it found two things a claim would not have: the kit's theme root does carry a style attribute (React writes it through element.style, so it applies and raises nothing — which is the distinction that matters and is now written down), and a stray server on the default preview port had been answering for a build nobody was testing. origin() is now apiOrigin(protocol, host), a pure function with a table for a test. It requires the first label to be `aml`, so evil.aml.hanzo.ai — a four-label host the old rule read as a domain to derive from, and one that resolves inside *.hanzo.ai — derives nothing. The certificate and the ingress host rule were the only things standing there before. @hanzo/iam is 0.21.5: logout ends the session (credentials + RFC 7009 revocation) and the callback error branch checks state before it reads the query string. The console-side proof of the second is in the harness. Co-authored-by: Hanzo Dev <[email protected]>
68 lines
2.4 KiB
TypeScript
68 lines
2.4 KiB
TypeScript
// Where this console is willing to send an authenticated request.
|
|
//
|
|
// The rule used to be a comment. It is a table now, because the interesting
|
|
// cases are the ones nobody writes down: a four-label host that reads as
|
|
// "aml.hanzo.ai with something in front", a trailing dot, an address literal.
|
|
// Each row is a host somebody could serve this bundle from, and the origin it
|
|
// would then talk to.
|
|
|
|
import { expect, test } from '@playwright/test'
|
|
|
|
import { apiOrigin } from '../src/config'
|
|
|
|
const https = 'https:'
|
|
|
|
test.describe('apiOrigin', () => {
|
|
test('derives api.<domain> from aml.<domain>, and from nothing else', () => {
|
|
const table: Array<[string, string]> = [
|
|
// The console, on the hosts it is served on.
|
|
['aml.hanzo.ai', 'https://api.hanzo.ai'],
|
|
['aml.lux.network', 'https://api.lux.network'],
|
|
['aml.zoo.ngo', 'https://api.zoo.ngo'],
|
|
['aml.pars.network', 'https://api.pars.network'],
|
|
['aml.co.uk', 'https://api.co.uk'],
|
|
|
|
// Same origin: the dev server proxies /v1/aml, so an empty string is the
|
|
// right answer, not a failure.
|
|
['localhost', ''],
|
|
['hanzo.ai', ''],
|
|
['127.0.0.1', ''],
|
|
['10.0.0.7', ''],
|
|
['[::1]', ''],
|
|
['fe80::1', ''],
|
|
|
|
// A host in front of the console's own. Four labels, and the old rule
|
|
// read it as a domain to derive from.
|
|
['evil.aml.hanzo.ai', ''],
|
|
['a.b.c.d.hanzo.ai', ''],
|
|
['aml.hanzo.ai.evil.test', 'https://api.hanzo.ai.evil.test'],
|
|
|
|
// Not this console.
|
|
['api.hanzo.ai', ''],
|
|
['kms.hanzo.ai', ''],
|
|
['AML.hanzo.ai', ''],
|
|
|
|
// Degenerate labels.
|
|
['aml.hanzo.ai.', ''],
|
|
['aml..ai', ''],
|
|
['.aml.hanzo.ai', ''],
|
|
['aml.hanzo.123', ''],
|
|
]
|
|
|
|
for (const [host, want] of table) {
|
|
expect(apiOrigin(https, host), host).toBe(want)
|
|
}
|
|
})
|
|
|
|
test('a host in front of the console gets nothing, whatever the scheme', () => {
|
|
// The one row worth stating twice. `aml.hanzo.ai.evil.test` above IS
|
|
// derived from — correctly: its first label is `aml` and its registrable
|
|
// domain is evil.test, so a console genuinely served there talks to
|
|
// api.hanzo.ai.evil.test and to nothing of ours. What must never derive is
|
|
// a host that puts something in FRONT of ours, because that one resolves
|
|
// inside *.hanzo.ai.
|
|
expect(apiOrigin('http:', 'evil.aml.hanzo.ai')).toBe('')
|
|
expect(apiOrigin('https:', 'evil.aml.hanzo.ai')).toBe('')
|
|
})
|
|
})
|