toiljs 0.0.55 → 0.0.57
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -0
- package/README.md +72 -14
- package/build/backend/.tsbuildinfo +1 -1
- package/build/cli/.tsbuildinfo +1 -1
- package/build/cli/index.js +293 -142
- package/build/client/.tsbuildinfo +1 -1
- package/build/client/auth.js +1 -1
- package/build/client/components/Image.d.ts +1 -1
- package/build/client/dev/devtools.js +4 -2
- package/build/client/index.d.ts +2 -2
- package/build/client/index.js +2 -2
- package/build/client/routing/Router.js +1 -1
- package/build/client/routing/hooks.js +2 -2
- package/build/client/routing/mount.js +1 -1
- package/build/compiler/.tsbuildinfo +1 -1
- package/build/compiler/docs.js +1 -1
- package/build/compiler/seo.js +1 -3
- package/build/compiler/template-build.d.ts +5 -2
- package/build/compiler/template-build.js +19 -7
- package/build/devserver/.tsbuildinfo +1 -1
- package/build/devserver/cache.js +0 -0
- package/build/devserver/crypto.js +45 -17
- package/build/devserver/database.d.ts +1 -1
- package/build/devserver/database.js +84 -0
- package/build/devserver/email/caps.js +0 -0
- package/build/devserver/email/config.js +7 -2
- package/build/devserver/email/validate.js +1 -4
- package/build/devserver/host.js +18 -1
- package/build/devserver/index.d.ts +1 -1
- package/build/devserver/index.js +3 -2
- package/build/devserver/module.js +51 -12
- package/build/devserver/proxy.js +2 -1
- package/build/io/.tsbuildinfo +1 -1
- package/build/io/codec.d.ts +5 -5
- package/build/io/codec.js +193 -77
- package/examples/basic/client/components/HoneycombBackground.tsx +1 -1
- package/examples/basic/client/public/images/logo.svg +37 -34
- package/examples/basic/client/public/index.html +14 -14
- package/examples/basic/client/routes/auth.tsx +18 -10
- package/examples/basic/client/routes/cookies.tsx +15 -24
- package/examples/basic/client/routes/crypto.tsx +4 -5
- package/examples/basic/client/routes/features/template/template.tsx +1 -1
- package/examples/basic/client/routes/hello.tsx +1 -1
- package/examples/basic/client/routes/pq.tsx +14 -14
- package/examples/basic/client/routes/rest.tsx +1 -3
- package/examples/basic/client/styles/main.css +25 -22
- package/examples/basic/client/toil.tsx +1 -1
- package/examples/basic/server/README.md +8 -8
- package/examples/basic/server/core/AppHandler.ts +4 -7
- package/examples/basic/server/routes/Auth.ts +13 -10
- package/examples/basic/server/routes/EnvDemo.ts +9 -3
- package/examples/basic/server/routes/Guestbook.ts +2 -4
- package/package.json +26 -26
- package/src/backend/index.ts +4 -2
- package/src/cli/create.ts +19 -4
- package/src/cli/diagnostics.ts +48 -0
- package/src/cli/doctor.ts +155 -9
- package/src/cli/notify.ts +1 -6
- package/src/cli/ui.ts +3 -3
- package/src/cli/version-check.ts +5 -1
- package/src/client/auth.ts +33 -10
- package/src/client/components/Form.tsx +2 -2
- package/src/client/components/Image.tsx +1 -1
- package/src/client/components/Script.tsx +1 -1
- package/src/client/components/Slot.tsx +1 -1
- package/src/client/dev/devtools.tsx +126 -55
- package/src/client/dev/error-overlay.tsx +7 -1
- package/src/client/head/metadata.ts +1 -1
- package/src/client/index.ts +13 -2
- package/src/client/routing/Router.tsx +2 -2
- package/src/client/routing/error-boundary.tsx +1 -1
- package/src/client/routing/hooks.ts +5 -3
- package/src/client/routing/loader.ts +2 -2
- package/src/client/routing/mount.tsx +5 -6
- package/src/compiler/docs.ts +1 -1
- package/src/compiler/email-preview.ts +1 -1
- package/src/compiler/generate.ts +1 -1
- package/src/compiler/seo.ts +1 -3
- package/src/compiler/ssg.ts +10 -4
- package/src/compiler/template-build.ts +43 -11
- package/src/compiler/template.ts +1 -4
- package/src/compiler/vite.ts +1 -1
- package/src/devserver/cache.ts +0 -0
- package/src/devserver/crypto.ts +140 -51
- package/src/devserver/database.ts +168 -9
- package/src/devserver/dotenv.ts +10 -2
- package/src/devserver/email/caps.ts +0 -0
- package/src/devserver/email/config.ts +8 -2
- package/src/devserver/email/index.ts +3 -3
- package/src/devserver/email/validate.ts +1 -4
- package/src/devserver/envelope.ts +3 -3
- package/src/devserver/host.ts +46 -6
- package/src/devserver/index.ts +15 -6
- package/src/devserver/module.ts +56 -14
- package/src/devserver/proxy.ts +5 -7
- package/src/io/codec.ts +226 -83
- package/test/devserver-database.test.ts +60 -0
- package/test/devserver-secrets.test.ts +59 -0
- package/test/doctor.test.ts +30 -0
|
@@ -1,37 +1,40 @@
|
|
|
1
1
|
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
-
<svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" version="1.1"
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
<
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
2
|
+
<svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" version="1.1"
|
|
3
|
+
viewBox="0 0 500 500">
|
|
4
|
+
<!-- Generator: Adobe Illustrator 30.4.0, SVG Export Plug-In . SVG Version: 2.1.4 Build 226) -->
|
|
5
|
+
<defs>
|
|
6
|
+
<style>
|
|
7
|
+
.st0 {
|
|
8
|
+
fill: #fff;
|
|
9
|
+
}
|
|
9
10
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
.st1 {
|
|
12
|
+
fill: url(#linear-gradient1);
|
|
13
|
+
}
|
|
13
14
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
15
|
+
.st2 {
|
|
16
|
+
fill: url(#linear-gradient);
|
|
17
|
+
}
|
|
18
|
+
</style>
|
|
19
|
+
<linearGradient id="linear-gradient" x1="43.27" y1="43.27" x2="467.12" y2="467.12"
|
|
20
|
+
gradientUnits="userSpaceOnUse">
|
|
21
|
+
<stop offset="0" stop-color="#6990ff"/>
|
|
22
|
+
<stop offset=".03" stop-color="#6479f9"/>
|
|
23
|
+
<stop offset=".08" stop-color="#5d57f0"/>
|
|
24
|
+
<stop offset=".12" stop-color="#583de9"/>
|
|
25
|
+
<stop offset=".17" stop-color="#542ae3"/>
|
|
26
|
+
<stop offset=".23" stop-color="#521ee0"/>
|
|
27
|
+
<stop offset=".28" stop-color="#521be0"/>
|
|
28
|
+
<stop offset=".66" stop-color="#6900f4"/>
|
|
29
|
+
<stop offset="1" stop-color="#7f00f6"/>
|
|
30
|
+
</linearGradient>
|
|
31
|
+
<linearGradient id="linear-gradient1" x1="149.99" y1="355.49" x2="149.99" y2="0" gradientUnits="userSpaceOnUse">
|
|
32
|
+
<stop offset=".15" stop-color="#6990ff" stop-opacity=".6"/>
|
|
33
|
+
<stop offset=".55" stop-color="#531ae1"/>
|
|
34
|
+
</linearGradient>
|
|
35
|
+
</defs>
|
|
36
|
+
<rect class="st2" width="500" height="500" rx="130" ry="130"/>
|
|
37
|
+
<path class="st1" d="M299.98,0L0,355.49v-225.49C0,58.2,58.2,0,130,0h169.98Z"/>
|
|
38
|
+
<path class="st0"
|
|
39
|
+
d="M106.17,111.11h285.24c9.9,0,16.7,9.96,13.09,19.18l-17.98,45.96c-2.11,5.39-7.31,8.94-13.09,8.94h-74.65c-7.76,0-14.06,6.29-14.06,14.06v214.94c0,7.76-6.29,14.06-14.06,14.06h-45.96c-7.76,0-14.06-6.29-14.06-14.06v-217.25c0-7.76-6.29-14.06-14.06-14.06h-73.66c-5.82,0-11.04-3.59-13.12-9.02l-16.76-43.64c-3.54-9.21,3.26-19.1,13.12-19.1Z"/>
|
|
40
|
+
</svg>
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
<!doctype html>
|
|
2
2
|
<html lang="en">
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8"/>
|
|
5
|
+
<meta content="width=device-width, initial-scale=1" name="viewport"/>
|
|
6
|
+
<meta content="#080D11" name="theme-color"/>
|
|
7
|
+
<link href="/favicon.ico" rel="icon" type="image/x-icon"/>
|
|
8
|
+
<title>ToilJS</title>
|
|
9
|
+
<link href="https://fonts.googleapis.com" rel="preconnect"/>
|
|
10
|
+
<link crossorigin href="https://fonts.gstatic.com" rel="preconnect"/>
|
|
11
|
+
<link
|
|
12
12
|
href="https://fonts.googleapis.com/css2?family=Montserrat:wght@700;800;900&display=swap"
|
|
13
|
-
rel="stylesheet"
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
13
|
+
rel="stylesheet"/>
|
|
14
|
+
</head>
|
|
15
|
+
<body>
|
|
16
|
+
<div id="root"></div>
|
|
17
|
+
</body>
|
|
18
18
|
</html>
|
|
@@ -56,7 +56,7 @@ function readCompanion(): Account | null {
|
|
|
56
56
|
export const metadata: Toil.Metadata = {
|
|
57
57
|
title: 'Auth',
|
|
58
58
|
description:
|
|
59
|
-
'Sessions and the @user / @auth surface: a dev login mints a signed session cookie, the guarded /session/me returns the verified user, and getUser() reads the readable companion.'
|
|
59
|
+
'Sessions and the @user / @auth surface: a dev login mints a signed session cookie, the guarded /session/me returns the verified user, and getUser() reads the readable companion.'
|
|
60
60
|
};
|
|
61
61
|
|
|
62
62
|
/** Encode a bare string the way the server reads it (`DataReader.readString`). */
|
|
@@ -84,7 +84,11 @@ export default function Auth(): React.JSX.Element {
|
|
|
84
84
|
const devLogin = useCallback(async () => {
|
|
85
85
|
setBusy(true);
|
|
86
86
|
try {
|
|
87
|
-
const res = await fetch('/session/dev-login', {
|
|
87
|
+
const res = await fetch('/session/dev-login', {
|
|
88
|
+
method: 'POST',
|
|
89
|
+
credentials: 'same-origin',
|
|
90
|
+
body: encodeString(username) as BodyInit
|
|
91
|
+
});
|
|
88
92
|
setLog(`POST /session/dev-login -> ${res.status} ${(await res.text()).trim()}`);
|
|
89
93
|
refreshCompanion();
|
|
90
94
|
setVerified(null);
|
|
@@ -127,10 +131,10 @@ export default function Auth(): React.JSX.Element {
|
|
|
127
131
|
<main style={{ maxWidth: 640, margin: '0 auto', padding: '2rem 1rem', lineHeight: 1.5 }}>
|
|
128
132
|
<h1>Auth and sessions</h1>
|
|
129
133
|
<p>
|
|
130
|
-
A dev login mints an HMAC-signed <code>__Host-toil_sess</code> session cookie plus a
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
+
A dev login mints an HMAC-signed <code>__Host-toil_sess</code> session cookie plus a readable{' '}
|
|
135
|
+
<code>__Secure-toil_user</code> companion. The guarded <code>/session/me</code> route ({' '}
|
|
136
|
+
<code>@auth</code> ) re-verifies the signed session; <code>getUser()</code> reads only the companion
|
|
137
|
+
(display-only, untrusted). Needs the server running.
|
|
134
138
|
</p>
|
|
135
139
|
|
|
136
140
|
<section style={{ display: 'flex', gap: '0.5rem', alignItems: 'center', flexWrap: 'wrap' }}>
|
|
@@ -162,9 +166,13 @@ export default function Auth(): React.JSX.Element {
|
|
|
162
166
|
{companion ? (
|
|
163
167
|
<pre>
|
|
164
168
|
{JSON.stringify(
|
|
165
|
-
{
|
|
169
|
+
{
|
|
170
|
+
username: companion.username,
|
|
171
|
+
admin: companion.admin,
|
|
172
|
+
score: String(companion.score)
|
|
173
|
+
},
|
|
166
174
|
null,
|
|
167
|
-
2
|
|
175
|
+
2
|
|
168
176
|
)}
|
|
169
177
|
</pre>
|
|
170
178
|
) : (
|
|
@@ -194,8 +202,8 @@ export default function Auth(): React.JSX.Element {
|
|
|
194
202
|
) : null}
|
|
195
203
|
|
|
196
204
|
<p style={{ marginTop: '1.5rem', fontSize: '0.85em', opacity: 0.7 }}>
|
|
197
|
-
The full post-quantum register/login (ML-DSA-44, password-derived) needs an account
|
|
198
|
-
|
|
205
|
+
The full post-quantum register/login (ML-DSA-44, password-derived) needs an account store and is stubbed
|
|
206
|
+
in <code>server/routes/Auth.ts</code>. See <code>docs/auth.md</code>.
|
|
199
207
|
</p>
|
|
200
208
|
</main>
|
|
201
209
|
);
|
|
@@ -3,14 +3,14 @@
|
|
|
3
3
|
// surface plus HMAC signing and AES-256-GCM encryption, running in the server wasm.
|
|
4
4
|
// These controls call the `/api/cookies/*` routes in `server/core/AppHandler.ts`.
|
|
5
5
|
// Needs the server running to respond.
|
|
6
|
-
import {
|
|
6
|
+
import { type CSSProperties, useState } from 'react';
|
|
7
7
|
|
|
8
8
|
import { useBrowserValue } from '../lib/useBrowserValue';
|
|
9
9
|
|
|
10
10
|
export const metadata: Toil.Metadata = {
|
|
11
11
|
title: 'Cookies',
|
|
12
12
|
description:
|
|
13
|
-
'Server-side cookies as a global: the Cookie builder, parsing, HMAC signing, and AES-256-GCM encryption, running in the server wasm.'
|
|
13
|
+
'Server-side cookies as a global: the Cookie builder, parsing, HMAC signing, and AES-256-GCM encryption, running in the server wasm.'
|
|
14
14
|
};
|
|
15
15
|
|
|
16
16
|
interface SetResp {
|
|
@@ -39,7 +39,7 @@ const card: CSSProperties = {
|
|
|
39
39
|
borderRadius: 8,
|
|
40
40
|
padding: '12px 16px',
|
|
41
41
|
margin: '12px 0',
|
|
42
|
-
background: '#0c1218'
|
|
42
|
+
background: '#0c1218'
|
|
43
43
|
};
|
|
44
44
|
const label: CSSProperties = { opacity: 0.7, fontSize: '0.8rem', marginTop: 6 };
|
|
45
45
|
|
|
@@ -94,22 +94,18 @@ export default function CookiesDemo() {
|
|
|
94
94
|
readJs();
|
|
95
95
|
});
|
|
96
96
|
const doSeal = (): Promise<void> =>
|
|
97
|
-
guard(async () =>
|
|
98
|
-
setSeal(await getJSON<SealResp>('/api/cookies/seal?v=' + encodeURIComponent(sealInput))),
|
|
99
|
-
);
|
|
97
|
+
guard(async () => setSeal(await getJSON<SealResp>('/api/cookies/seal?v=' + encodeURIComponent(sealInput))));
|
|
100
98
|
|
|
101
99
|
return (
|
|
102
100
|
<main style={{ maxWidth: 760 }}>
|
|
103
101
|
<h1>Cookies</h1>
|
|
104
102
|
<p>
|
|
105
|
-
<code>Cookie</code>, <code>Cookies</code>, and <code>SecureCookies</code> are globals in
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
103
|
+
<code>Cookie</code>, <code>Cookies</code>, and <code>SecureCookies</code> are globals in the server (no
|
|
104
|
+
import), exactly like <code>crypto</code>: the full RFC 6265bis surface plus HMAC signing and
|
|
105
|
+
AES-256-GCM encryption, running in the server wasm. See <code>server/core/AppHandler.ts</code>. Needs
|
|
106
|
+
the server running (<code>toiljs dev</code>).
|
|
109
107
|
</p>
|
|
110
|
-
|
|
111
108
|
{err ? <p style={{ color: '#ff6b6b', ...mono }}>{err}</p> : null}
|
|
112
|
-
|
|
113
109
|
<h2>1. Everything you can do</h2>
|
|
114
110
|
<p>Every attribute and cookie type, with the exact `Set-Cookie` string it serializes to.</p>
|
|
115
111
|
<button onClick={showGallery}>Show the gallery</button>
|
|
@@ -123,12 +119,11 @@ export default function CookiesDemo() {
|
|
|
123
119
|
))}
|
|
124
120
|
</div>
|
|
125
121
|
) : null}
|
|
126
|
-
|
|
127
122
|
<h2>2. Set cookies</h2>
|
|
128
123
|
<p>
|
|
129
124
|
Stores three real cookies: a plain <code>visits</code> counter, an HMAC-signed{' '}
|
|
130
|
-
<code>__Host-session</code>, and an AES-GCM-encrypted <code>secret</code>. The last two
|
|
131
|
-
|
|
125
|
+
<code>__Host-session</code>, and an AES-GCM-encrypted <code>secret</code>. The last two are{' '}
|
|
126
|
+
<code>HttpOnly</code>, so JavaScript cannot read them, only the server can.
|
|
132
127
|
</p>
|
|
133
128
|
<button onClick={doSet}>Set cookies</button>
|
|
134
129
|
{setResp ? (
|
|
@@ -141,12 +136,11 @@ export default function CookiesDemo() {
|
|
|
141
136
|
))}
|
|
142
137
|
</div>
|
|
143
138
|
) : null}
|
|
144
|
-
|
|
145
139
|
<h2>3. What JS sees vs what the server sees</h2>
|
|
146
140
|
<p>
|
|
147
|
-
<code>document.cookie</code> only exposes non-<code>HttpOnly</code> cookies, so the
|
|
148
|
-
|
|
149
|
-
|
|
141
|
+
<code>document.cookie</code> only exposes non-<code>HttpOnly</code> cookies, so the signed session and
|
|
142
|
+
encrypted secret are hidden from it. The server parses all of them and verifies/decrypts the protected
|
|
143
|
+
ones.
|
|
150
144
|
</p>
|
|
151
145
|
<button onClick={readJs}>Read document.cookie</button>{' '}
|
|
152
146
|
<button onClick={doInspect}>Ask the server (/inspect)</button>
|
|
@@ -163,7 +157,6 @@ export default function CookiesDemo() {
|
|
|
163
157
|
<div style={mono}>secret (AES-GCM-decrypted): {inspect.secret ?? 'null (missing or tampered)'}</div>
|
|
164
158
|
</div>
|
|
165
159
|
) : null}
|
|
166
|
-
|
|
167
160
|
<h2>4. Clear</h2>
|
|
168
161
|
<button onClick={doClear}>Clear the demo cookies</button>
|
|
169
162
|
{cleared ? (
|
|
@@ -175,12 +168,11 @@ export default function CookiesDemo() {
|
|
|
175
168
|
))}
|
|
176
169
|
</div>
|
|
177
170
|
) : null}
|
|
178
|
-
|
|
179
171
|
<h2>5. Sign & encrypt a value</h2>
|
|
180
172
|
<p>
|
|
181
173
|
<code>SecureCookies.signed(key)</code> (HMAC-SHA256, readable but tamper-proof) and{' '}
|
|
182
|
-
<code>SecureCookies.encrypted(key)</code> (AES-256-GCM, confidential). Both bind the
|
|
183
|
-
|
|
174
|
+
<code>SecureCookies.encrypted(key)</code> (AES-256-GCM, confidential). Both bind the value to the cookie
|
|
175
|
+
name, and a tampered signature fails to verify.
|
|
184
176
|
</p>
|
|
185
177
|
<input
|
|
186
178
|
value={sealInput}
|
|
@@ -198,7 +190,6 @@ export default function CookiesDemo() {
|
|
|
198
190
|
<div style={mono}>tampered signature verifies? {String(seal.tamperVerifies)}</div>
|
|
199
191
|
</div>
|
|
200
192
|
) : null}
|
|
201
|
-
|
|
202
193
|
<p style={{ marginTop: 24 }}>
|
|
203
194
|
<Toil.Link href="/features">Back to features</Toil.Link>
|
|
204
195
|
</p>
|
|
@@ -37,11 +37,10 @@ export default function CryptoDemo() {
|
|
|
37
37
|
<main>
|
|
38
38
|
<h1>Web Crypto</h1>
|
|
39
39
|
<p>
|
|
40
|
-
<code>crypto</code> is a global in the server (no import), synchronous, the same
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
<code
|
|
44
|
-
server running to respond.
|
|
40
|
+
<code>crypto</code> is a global in the server (no import), synchronous, the same SubtleCrypto-style API
|
|
41
|
+
as the browser, running in the server wasm via metered host functions. These buttons call the
|
|
42
|
+
server's <code>/api/hash</code> and <code>/api/uuid</code> routes (see{' '}
|
|
43
|
+
<code>server/HelloHandler.ts</code>). Needs the server running to respond.
|
|
45
44
|
</p>
|
|
46
45
|
<button onClick={onHash}>SHA-256</button> <button onClick={onUuid}>random UUID</button>
|
|
47
46
|
<ul style={{ marginTop: 16, listStyle: 'none', padding: 0 }}>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { type ReactNode, useState } from 'react';
|
|
2
2
|
|
|
3
3
|
// A template wraps a segment like a layout, but RE-MOUNTS on every navigation within it (a layout
|
|
4
4
|
// persists). This counter increments each time the template mounts, so navigating between the two
|
|
@@ -19,7 +19,7 @@ interface HelloData {
|
|
|
19
19
|
|
|
20
20
|
export const loader = ({ params }: { params: Record<string, string> }): HelloData => ({
|
|
21
21
|
name: params.name ?? 'world',
|
|
22
|
-
items: ['alpha', 'beta', 'gamma']
|
|
22
|
+
items: ['alpha', 'beta', 'gamma']
|
|
23
23
|
});
|
|
24
24
|
|
|
25
25
|
export default function Hello(): React.JSX.Element {
|
|
@@ -49,7 +49,7 @@ interface VerifiedUser {
|
|
|
49
49
|
export const metadata: Toil.Metadata = {
|
|
50
50
|
title: 'Post-quantum auth',
|
|
51
51
|
description:
|
|
52
|
-
'A server-keyed-salt OPRF + ML-DSA-44 (FIPS 204) auth + ML-KEM-768 (FIPS 203) mutual auth. The password never leaves the browser.'
|
|
52
|
+
'A server-keyed-salt OPRF + ML-DSA-44 (FIPS 204) auth + ML-KEM-768 (FIPS 203) mutual auth. The password never leaves the browser.'
|
|
53
53
|
};
|
|
54
54
|
|
|
55
55
|
type Note = { kind: 'ok' | 'err'; text: string } | null;
|
|
@@ -73,7 +73,7 @@ export default function Pq(): React.JSX.Element {
|
|
|
73
73
|
await Auth.register(username, password);
|
|
74
74
|
setNote({
|
|
75
75
|
kind: 'ok',
|
|
76
|
-
text: 'registered: the server stored only your public key and a proof-of-possession. Now log in to run the ML-KEM-768 mutual-auth step.'
|
|
76
|
+
text: 'registered: the server stored only your public key and a proof-of-possession. Now log in to run the ML-KEM-768 mutual-auth step.'
|
|
77
77
|
});
|
|
78
78
|
} catch (e) {
|
|
79
79
|
setNote({ kind: 'err', text: e instanceof Error ? e.message : String(e) });
|
|
@@ -91,7 +91,7 @@ export default function Pq(): React.JSX.Element {
|
|
|
91
91
|
await Auth.login(username, password);
|
|
92
92
|
setNote({
|
|
93
93
|
kind: 'ok',
|
|
94
|
-
text: 'logged in: ML-KEM-768 mutual auth verified (the server proved it holds the KEM secret key).'
|
|
94
|
+
text: 'logged in: ML-KEM-768 mutual auth verified (the server proved it holds the KEM secret key).'
|
|
95
95
|
});
|
|
96
96
|
refreshCompanion();
|
|
97
97
|
} catch (e) {
|
|
@@ -151,11 +151,7 @@ export default function Pq(): React.JSX.Element {
|
|
|
151
151
|
</label>
|
|
152
152
|
<label>
|
|
153
153
|
Password
|
|
154
|
-
<input
|
|
155
|
-
value={password}
|
|
156
|
-
onChange={(e) => setPassword(e.target.value)}
|
|
157
|
-
style={{ width: '100%' }}
|
|
158
|
-
/>
|
|
154
|
+
<input value={password} onChange={(e) => setPassword(e.target.value)} style={{ width: '100%' }} />
|
|
159
155
|
</label>
|
|
160
156
|
<div style={{ display: 'flex', gap: 8 }}>
|
|
161
157
|
<button onClick={doRegister} disabled={busy}>
|
|
@@ -167,8 +163,8 @@ export default function Pq(): React.JSX.Element {
|
|
|
167
163
|
</div>
|
|
168
164
|
<p style={{ fontSize: '0.8rem', opacity: 0.7, margin: 0 }}>
|
|
169
165
|
Demo: pre-filled <code>ada</code> / <code>correct horse battery staple</code>. Register once, then
|
|
170
|
-
log in. A wrong password fails at login (the derived key won't match the stored one). Storage
|
|
171
|
-
the DEV-only in-process KV (<code>src/devserver/kv.ts</code>); a real deployment wires an atomic
|
|
166
|
+
log in. A wrong password fails at login (the derived key won't match the stored one). Storage
|
|
167
|
+
is the DEV-only in-process KV (<code>src/devserver/kv.ts</code>); a real deployment wires an atomic
|
|
172
168
|
store, <code>server/routes/Auth.ts</code>.
|
|
173
169
|
</p>
|
|
174
170
|
</div>
|
|
@@ -188,9 +184,13 @@ export default function Pq(): React.JSX.Element {
|
|
|
188
184
|
<div style={{ fontSize: '0.8em', opacity: 0.7 }}>readable companion, untrusted</div>
|
|
189
185
|
<pre>
|
|
190
186
|
{JSON.stringify(
|
|
191
|
-
{
|
|
187
|
+
{
|
|
188
|
+
username: companion.username,
|
|
189
|
+
admin: companion.admin,
|
|
190
|
+
score: String(companion.score)
|
|
191
|
+
},
|
|
192
192
|
null,
|
|
193
|
-
2
|
|
193
|
+
2
|
|
194
194
|
)}
|
|
195
195
|
</pre>
|
|
196
196
|
</div>
|
|
@@ -220,8 +220,8 @@ export default function Pq(): React.JSX.Element {
|
|
|
220
220
|
)}
|
|
221
221
|
|
|
222
222
|
<p style={{ marginTop: 24, opacity: 0.7, fontSize: '0.9rem' }}>
|
|
223
|
-
This is the full augmented-PAKE chain: OPRF keyed salt + ML-DSA client auth + ML-KEM mutual auth, with
|
|
224
|
-
atomic single-use challenge consume. The OPRF layer is classical ristretto255 (the one non-PQ piece);
|
|
223
|
+
This is the full augmented-PAKE chain: OPRF keyed salt + ML-DSA client auth + ML-KEM mutual auth, with
|
|
224
|
+
an atomic single-use challenge consume. The OPRF layer is classical ristretto255 (the one non-PQ piece);
|
|
225
225
|
auth and key agreement are post-quantum. Plain sessions are on the{' '}
|
|
226
226
|
<Toil.Link href="/auth">Auth</Toil.Link> page.
|
|
227
227
|
</p>
|
|
@@ -17,7 +17,7 @@ export default function RestDemo() {
|
|
|
17
17
|
// page, and the total is still there.
|
|
18
18
|
const [book, setBook] = useState<{ total: bigint; entries: { author: string; message: string }[] }>({
|
|
19
19
|
total: 0n,
|
|
20
|
-
entries: []
|
|
20
|
+
entries: []
|
|
21
21
|
});
|
|
22
22
|
|
|
23
23
|
// POST /players -> typed Promise<Player>, body is a @data class. The server returns the
|
|
@@ -116,7 +116,6 @@ export default function RestDemo() {
|
|
|
116
116
|
<li key={i}>{line}</li>
|
|
117
117
|
))}
|
|
118
118
|
</ul>
|
|
119
|
-
|
|
120
119
|
<h2>Guestbook (persisted via ToilDB)</h2>
|
|
121
120
|
<p>
|
|
122
121
|
The same <code>Server.REST.*</code> client, but the route stores each signature in a ToilDB{' '}
|
|
@@ -133,7 +132,6 @@ export default function RestDemo() {
|
|
|
133
132
|
</li>
|
|
134
133
|
))}
|
|
135
134
|
</ul>
|
|
136
|
-
|
|
137
135
|
<Toil.Link href="/">Back home</Toil.Link>
|
|
138
136
|
</main>
|
|
139
137
|
);
|
|
@@ -23,10 +23,9 @@ body {
|
|
|
23
23
|
min-height: 100vh;
|
|
24
24
|
background: var(--bg);
|
|
25
25
|
color: var(--text);
|
|
26
|
-
font-family:
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
sans-serif;
|
|
26
|
+
font-family: system-ui,
|
|
27
|
+
-apple-system,
|
|
28
|
+
sans-serif;
|
|
30
29
|
line-height: 1.6;
|
|
31
30
|
}
|
|
32
31
|
|
|
@@ -34,6 +33,7 @@ a {
|
|
|
34
33
|
color: var(--accent);
|
|
35
34
|
text-decoration: none;
|
|
36
35
|
}
|
|
36
|
+
|
|
37
37
|
a:hover {
|
|
38
38
|
color: var(--accent3);
|
|
39
39
|
}
|
|
@@ -99,6 +99,7 @@ a:hover {
|
|
|
99
99
|
.nav-links a {
|
|
100
100
|
color: var(--muted);
|
|
101
101
|
}
|
|
102
|
+
|
|
102
103
|
.nav-links a:hover {
|
|
103
104
|
color: var(--text);
|
|
104
105
|
}
|
|
@@ -117,9 +118,8 @@ a:hover {
|
|
|
117
118
|
border-radius: 6px;
|
|
118
119
|
font-size: 0.9rem;
|
|
119
120
|
color: var(--muted) !important;
|
|
120
|
-
transition:
|
|
121
|
-
|
|
122
|
-
background 150ms;
|
|
121
|
+
transition: color 150ms,
|
|
122
|
+
background 150ms;
|
|
123
123
|
}
|
|
124
124
|
|
|
125
125
|
.nav-center-link:hover {
|
|
@@ -182,10 +182,9 @@ a:hover {
|
|
|
182
182
|
transform: scale(1.1);
|
|
183
183
|
filter: blur(18px) saturate(1.4);
|
|
184
184
|
opacity: 0.65;
|
|
185
|
-
transition:
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
transform 300ms;
|
|
185
|
+
transition: opacity 300ms,
|
|
186
|
+
filter 300ms,
|
|
187
|
+
transform 300ms;
|
|
189
188
|
}
|
|
190
189
|
|
|
191
190
|
.hero-logo:hover .hero-logo-glow {
|
|
@@ -248,9 +247,8 @@ a:hover {
|
|
|
248
247
|
border-radius: 999px;
|
|
249
248
|
font-size: 0.88rem;
|
|
250
249
|
color: var(--muted);
|
|
251
|
-
transition:
|
|
252
|
-
|
|
253
|
-
color 200ms;
|
|
250
|
+
transition: border-color 200ms,
|
|
251
|
+
color 200ms;
|
|
254
252
|
}
|
|
255
253
|
|
|
256
254
|
.feature-badge svg {
|
|
@@ -289,10 +287,9 @@ a:hover {
|
|
|
289
287
|
font-weight: 600;
|
|
290
288
|
font-family: inherit;
|
|
291
289
|
cursor: pointer;
|
|
292
|
-
transition:
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
border-color 200ms;
|
|
290
|
+
transition: filter 200ms,
|
|
291
|
+
background 200ms,
|
|
292
|
+
border-color 200ms;
|
|
296
293
|
text-decoration: none !important;
|
|
297
294
|
}
|
|
298
295
|
|
|
@@ -422,10 +419,9 @@ code {
|
|
|
422
419
|
display: flex;
|
|
423
420
|
flex-direction: column;
|
|
424
421
|
gap: 0.6rem;
|
|
425
|
-
transition:
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
box-shadow 200ms;
|
|
422
|
+
transition: border-color 200ms,
|
|
423
|
+
transform 200ms,
|
|
424
|
+
box-shadow 200ms;
|
|
429
425
|
}
|
|
430
426
|
|
|
431
427
|
.gs-card {
|
|
@@ -436,6 +432,7 @@ code {
|
|
|
436
432
|
.gs-card--accent1 {
|
|
437
433
|
border-top: 2px solid var(--accent);
|
|
438
434
|
}
|
|
435
|
+
|
|
439
436
|
.gs-card--accent1:hover {
|
|
440
437
|
box-shadow: 0 8px 32px rgba(37, 99, 255, 0.15);
|
|
441
438
|
}
|
|
@@ -443,6 +440,7 @@ code {
|
|
|
443
440
|
.gs-card--accent2 {
|
|
444
441
|
border-top: 2px solid var(--accent2);
|
|
445
442
|
}
|
|
443
|
+
|
|
446
444
|
.gs-card--accent2:hover {
|
|
447
445
|
box-shadow: 0 8px 32px rgba(124, 58, 237, 0.15);
|
|
448
446
|
}
|
|
@@ -450,6 +448,7 @@ code {
|
|
|
450
448
|
.gs-card--accent3 {
|
|
451
449
|
border-top: 2px solid var(--accent3);
|
|
452
450
|
}
|
|
451
|
+
|
|
453
452
|
.gs-card--accent3:hover {
|
|
454
453
|
box-shadow: 0 8px 32px rgba(34, 227, 171, 0.12);
|
|
455
454
|
}
|
|
@@ -457,6 +456,7 @@ code {
|
|
|
457
456
|
.gs-card--accent4 {
|
|
458
457
|
border-top: 2px solid #f59e0b;
|
|
459
458
|
}
|
|
459
|
+
|
|
460
460
|
.gs-card--accent4:hover {
|
|
461
461
|
box-shadow: 0 8px 32px rgba(245, 158, 11, 0.12);
|
|
462
462
|
}
|
|
@@ -464,6 +464,7 @@ code {
|
|
|
464
464
|
.gs-card--flat {
|
|
465
465
|
border-top: 2px solid var(--accent);
|
|
466
466
|
}
|
|
467
|
+
|
|
467
468
|
.gs-card--flat:hover {
|
|
468
469
|
border-color: var(--accent);
|
|
469
470
|
box-shadow: 0 8px 32px rgba(37, 99, 255, 0.12);
|
|
@@ -485,10 +486,12 @@ code {
|
|
|
485
486
|
background: rgba(124, 58, 237, 0.1);
|
|
486
487
|
color: var(--accent2);
|
|
487
488
|
}
|
|
489
|
+
|
|
488
490
|
.gs-card--accent3 .gs-card-icon {
|
|
489
491
|
background: rgba(34, 227, 171, 0.1);
|
|
490
492
|
color: var(--accent3);
|
|
491
493
|
}
|
|
494
|
+
|
|
492
495
|
.gs-card--accent4 .gs-card-icon {
|
|
493
496
|
background: rgba(245, 158, 11, 0.1);
|
|
494
497
|
color: #f59e0b;
|
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
Your ToilScript backend, compiled to a single WebAssembly module. One folder per concern:
|
|
4
4
|
|
|
5
|
-
| Folder
|
|
6
|
-
|
|
7
|
-
| `main.ts`
|
|
8
|
-
| `core/`
|
|
9
|
-
| `models/`
|
|
10
|
-
| `routes/`
|
|
11
|
-
| `services/`
|
|
12
|
-
| `scheduled/` | Reserved for scheduled tasks (see its README).
|
|
5
|
+
| Folder | What lives here |
|
|
6
|
+
|--------------|----------------------------------------------------------------------------------|
|
|
7
|
+
| `main.ts` | The entry point: wires the handler and imports the surface modules. |
|
|
8
|
+
| `core/` | The request handler and shared app logic (state, helpers). |
|
|
9
|
+
| `models/` | `@data` classes, the typed wire model shared by HTTP and RPC. One type per file. |
|
|
10
|
+
| `routes/` | `@rest` controllers (HTTP). One controller per file, named after its class. |
|
|
11
|
+
| `services/` | `@service` classes and free `@remote` functions (typed RPC). |
|
|
12
|
+
| `scheduled/` | Reserved for scheduled tasks (see its README). |
|
|
13
13
|
|
|
14
14
|
Conventions:
|
|
15
15
|
|
|
@@ -104,7 +104,7 @@ export class AppHandler extends ToilHandler {
|
|
|
104
104
|
.partitioned()
|
|
105
105
|
.priority('Medium')
|
|
106
106
|
.extension('CustomFlag')
|
|
107
|
-
.serialize()
|
|
107
|
+
.serialize()
|
|
108
108
|
);
|
|
109
109
|
|
|
110
110
|
let json = '{';
|
|
@@ -127,10 +127,10 @@ export class AppHandler extends ToilHandler {
|
|
|
127
127
|
|
|
128
128
|
const visits = Cookie.create('visits', next).path('/').sameSite(SameSite.Lax).maxAge(86400);
|
|
129
129
|
const session = SecureCookies.signed(this.demoKey()).seal(
|
|
130
|
-
Cookie.create('session', 'user-42').httpOnly().sameSite(SameSite.Strict).asHostPrefixed()
|
|
130
|
+
Cookie.create('session', 'user-42').httpOnly().sameSite(SameSite.Strict).asHostPrefixed()
|
|
131
131
|
);
|
|
132
132
|
const secret = SecureCookies.encrypted(this.demoKey()).seal(
|
|
133
|
-
Cookie.create('secret', 'top-secret-value').httpOnly().path('/')
|
|
133
|
+
Cookie.create('secret', 'top-secret-value').httpOnly().path('/')
|
|
134
134
|
);
|
|
135
135
|
|
|
136
136
|
const json =
|
|
@@ -189,10 +189,7 @@ export class AppHandler extends ToilHandler {
|
|
|
189
189
|
'","' +
|
|
190
190
|
this.esc(this.clearString('secret')) +
|
|
191
191
|
'"]}';
|
|
192
|
-
return Response.json(json)
|
|
193
|
-
.clearCookie('visits')
|
|
194
|
-
.clearCookie('__Host-session')
|
|
195
|
-
.clearCookie('secret');
|
|
192
|
+
return Response.json(json).clearCookie('visits').clearCookie('__Host-session').clearCookie('secret');
|
|
196
193
|
}
|
|
197
194
|
|
|
198
195
|
// SEAL: sign and encrypt a value (from `?v=`), then recover both and show
|