toiljs 0.0.88 → 0.0.89
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 +5 -0
- package/build/client/.tsbuildinfo +1 -1
- package/build/client/auth.d.ts +12 -1
- package/build/client/auth.js +82 -4
- package/build/devserver/.tsbuildinfo +1 -1
- package/build/devserver/analytics/index.js +41 -20
- package/build/devserver/config/ratelimit.d.ts +1 -0
- package/build/devserver/config/ratelimit.js +3 -0
- package/build/devserver/email/index.d.ts +10 -0
- package/build/devserver/email/index.js +16 -0
- package/build/devserver/runtime/host.js +33 -49
- package/build/devserver/runtime/module.js +1 -0
- package/examples/basic/client/routes/pq.tsx +12 -2
- package/examples/basic/server/main.ts +0 -1
- package/examples/basic/server/routes/Session.ts +5 -11
- package/package.json +3 -3
- package/server/auth/AuthController.ts +467 -24
- package/server/globals/auth.ts +44 -2
- package/server/globals/email.ts +54 -1
- package/src/client/auth.ts +130 -4
- package/src/devserver/analytics/index.ts +61 -30
- package/src/devserver/config/ratelimit.ts +7 -0
- package/src/devserver/email/index.ts +44 -0
- package/src/devserver/runtime/host.ts +58 -64
- package/src/devserver/runtime/module.ts +1 -0
- package/test/analytics-dev.test.ts +34 -12
- package/test/devserver-email.test.ts +48 -1
- package/test/pqauth-e2e.test.ts +18 -9
- package/test/pqauth-email.test.ts +333 -0
- package/examples/basic/server/routes/Auth.ts +0 -268
|
@@ -2,7 +2,7 @@ import { devEnvGet, devEnvGetSecure } from '../config/env.js';
|
|
|
2
2
|
import { ratelimitCheck } from '../config/ratelimit.js';
|
|
3
3
|
import { buildAnalyticsImports } from '../analytics/index.js';
|
|
4
4
|
import { buildDatabaseImports, freshDbState } from '../db/index.js';
|
|
5
|
-
import { EmailStatus, getEmailService } from '../email/index.js';
|
|
5
|
+
import { EmailStatus, getEmailService, recordSentEmail } from '../email/index.js';
|
|
6
6
|
import { parseEmailBlob } from '../email/wire.js';
|
|
7
7
|
import { buildCryptoImports, freshCryptoState } from './crypto.js';
|
|
8
8
|
const MAX_TOTAL_HEADERS_BYTES = 64 * 1024;
|
|
@@ -90,6 +90,34 @@ function envLookup(ref, keyPtr, keyLen, outPtr, outCap, secure) {
|
|
|
90
90
|
bytes.copy(m, outPtr);
|
|
91
91
|
return bytes.length;
|
|
92
92
|
}
|
|
93
|
+
function devEmailSend(ref, reqPtr, reqLen) {
|
|
94
|
+
const raw = readBytes(ref, reqPtr, reqLen);
|
|
95
|
+
const svc = getEmailService();
|
|
96
|
+
if (svc === null) {
|
|
97
|
+
const parsed = parseEmailBlob(raw);
|
|
98
|
+
if (parsed !== null)
|
|
99
|
+
recordSentEmail(parsed);
|
|
100
|
+
const to = parsed?.to ?? '<unparsed>';
|
|
101
|
+
process.stdout.write(` ✉ dev email_send -> ${to} (no email config; not sent)\n`);
|
|
102
|
+
return EmailStatus.Sent;
|
|
103
|
+
}
|
|
104
|
+
const { status, parsed } = svc.prepare(raw);
|
|
105
|
+
if (parsed === null) {
|
|
106
|
+
process.stdout.write(` ✉ dev email_send -> ${EmailStatus[status]}\n`);
|
|
107
|
+
return status;
|
|
108
|
+
}
|
|
109
|
+
recordSentEmail(parsed);
|
|
110
|
+
void svc
|
|
111
|
+
.deliver(parsed)
|
|
112
|
+
.then((s) => {
|
|
113
|
+
const label = s === EmailStatus.Sent ? 'sent' : EmailStatus[s];
|
|
114
|
+
process.stdout.write(` ✉ dev email_send -> ${parsed.to} (${label})\n`);
|
|
115
|
+
})
|
|
116
|
+
.catch((e) => {
|
|
117
|
+
process.stdout.write(` ✉ dev email_send -> ${parsed.to} (error: ${String(e)})\n`);
|
|
118
|
+
});
|
|
119
|
+
return EmailStatus.Sent;
|
|
120
|
+
}
|
|
93
121
|
export function buildEnvImports(ref, _state) {
|
|
94
122
|
return {
|
|
95
123
|
abort: (msgPtr, filePtr, line, col) => {
|
|
@@ -99,30 +127,8 @@ export function buildEnvImports(ref, _state) {
|
|
|
99
127
|
env_get_secure: (keyPtr, keyLen, outPtr, outCap) => envLookup(ref, keyPtr, keyLen, outPtr, outCap, true),
|
|
100
128
|
thread_spawn: (_startArg) => -1,
|
|
101
129
|
'Date.now': () => BigInt(Date.now()),
|
|
102
|
-
email_send: (reqPtr, reqLen) =>
|
|
103
|
-
|
|
104
|
-
const svc = getEmailService();
|
|
105
|
-
if (svc === null) {
|
|
106
|
-
const to = parseEmailBlob(raw)?.to ?? '<unparsed>';
|
|
107
|
-
process.stdout.write(` ✉ dev email_send -> ${to} (no email config; not sent)\n`);
|
|
108
|
-
return EmailStatus.Sent;
|
|
109
|
-
}
|
|
110
|
-
const { status, parsed } = svc.prepare(raw);
|
|
111
|
-
if (parsed === null) {
|
|
112
|
-
process.stdout.write(` ✉ dev email_send -> ${EmailStatus[status]}\n`);
|
|
113
|
-
return status;
|
|
114
|
-
}
|
|
115
|
-
void svc
|
|
116
|
-
.deliver(parsed)
|
|
117
|
-
.then((s) => {
|
|
118
|
-
const label = s === EmailStatus.Sent ? 'sent' : EmailStatus[s];
|
|
119
|
-
process.stdout.write(` ✉ dev email_send -> ${parsed.to} (${label})\n`);
|
|
120
|
-
})
|
|
121
|
-
.catch((e) => {
|
|
122
|
-
process.stdout.write(` ✉ dev email_send -> ${parsed.to} (error: ${String(e)})\n`);
|
|
123
|
-
});
|
|
124
|
-
return EmailStatus.Sent;
|
|
125
|
-
},
|
|
130
|
+
email_send: (reqPtr, reqLen) => devEmailSend(ref, reqPtr, reqLen),
|
|
131
|
+
email_send_detached: (reqPtr, reqLen) => devEmailSend(ref, reqPtr, reqLen),
|
|
126
132
|
};
|
|
127
133
|
}
|
|
128
134
|
export function buildHostImports(ref, state) {
|
|
@@ -175,30 +181,8 @@ export function buildHostImports(ref, state) {
|
|
|
175
181
|
const d = ratelimitCheck(routeId, strategy, limit, window, identity, Date.now());
|
|
176
182
|
return d.allowed ? 1 : -Math.max(1, d.retryAfterSecs);
|
|
177
183
|
},
|
|
178
|
-
email_send: (reqPtr, reqLen) =>
|
|
179
|
-
|
|
180
|
-
const svc = getEmailService();
|
|
181
|
-
if (svc === null) {
|
|
182
|
-
const to = parseEmailBlob(raw)?.to ?? '<unparsed>';
|
|
183
|
-
process.stdout.write(` ✉ dev email_send -> ${to} (no email config; not sent)\n`);
|
|
184
|
-
return EmailStatus.Sent;
|
|
185
|
-
}
|
|
186
|
-
const { status, parsed } = svc.prepare(raw);
|
|
187
|
-
if (parsed === null) {
|
|
188
|
-
process.stdout.write(` ✉ dev email_send -> ${EmailStatus[status]}\n`);
|
|
189
|
-
return status;
|
|
190
|
-
}
|
|
191
|
-
void svc
|
|
192
|
-
.deliver(parsed)
|
|
193
|
-
.then((s) => {
|
|
194
|
-
const label = s === EmailStatus.Sent ? 'sent' : EmailStatus[s];
|
|
195
|
-
process.stdout.write(` ✉ dev email_send -> ${parsed.to} (${label})\n`);
|
|
196
|
-
})
|
|
197
|
-
.catch((e) => {
|
|
198
|
-
process.stdout.write(` ✉ dev email_send -> ${parsed.to} (error: ${String(e)})\n`);
|
|
199
|
-
});
|
|
200
|
-
return EmailStatus.Sent;
|
|
201
|
-
},
|
|
184
|
+
email_send: (reqPtr, reqLen) => devEmailSend(ref, reqPtr, reqLen),
|
|
185
|
+
email_send_detached: (reqPtr, reqLen) => devEmailSend(ref, reqPtr, reqLen),
|
|
202
186
|
env_get: (keyPtr, keyLen, outPtr, outCap) => envLookup(ref, keyPtr, keyLen, outPtr, outCap, false),
|
|
203
187
|
env_get_secure: (keyPtr, keyLen, outPtr, outCap) => envLookup(ref, keyPtr, keyLen, outPtr, outCap, true),
|
|
204
188
|
thread_spawn: (_startArg) => -1,
|
|
@@ -56,6 +56,7 @@ type Note = { kind: 'ok' | 'err'; text: string } | null;
|
|
|
56
56
|
|
|
57
57
|
export default function Pq(): React.JSX.Element {
|
|
58
58
|
const [username, setUsername] = useState('ada');
|
|
59
|
+
const [email, setEmail] = useState('ada@example.com');
|
|
59
60
|
const [password, setPassword] = useState('correct horse battery staple');
|
|
60
61
|
const [busy, setBusy] = useState(false);
|
|
61
62
|
const [note, setNote] = useState<Note>(null);
|
|
@@ -70,7 +71,7 @@ export default function Pq(): React.JSX.Element {
|
|
|
70
71
|
setNote(null);
|
|
71
72
|
setVerified(null);
|
|
72
73
|
try {
|
|
73
|
-
await Auth.register(username, password);
|
|
74
|
+
await Auth.register(username, password, email);
|
|
74
75
|
setNote({
|
|
75
76
|
kind: 'ok',
|
|
76
77
|
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.'
|
|
@@ -80,7 +81,7 @@ export default function Pq(): React.JSX.Element {
|
|
|
80
81
|
} finally {
|
|
81
82
|
setBusy(false);
|
|
82
83
|
}
|
|
83
|
-
}, [username, password]);
|
|
84
|
+
}, [username, password, email]);
|
|
84
85
|
|
|
85
86
|
const doLogin = useCallback(async () => {
|
|
86
87
|
setBusy(true);
|
|
@@ -149,6 +150,15 @@ export default function Pq(): React.JSX.Element {
|
|
|
149
150
|
Username
|
|
150
151
|
<input value={username} onChange={(e) => setUsername(e.target.value)} style={{ width: '100%' }} />
|
|
151
152
|
</label>
|
|
153
|
+
<label>
|
|
154
|
+
Email
|
|
155
|
+
<input
|
|
156
|
+
type="email"
|
|
157
|
+
value={email}
|
|
158
|
+
onChange={(e) => setEmail(e.target.value)}
|
|
159
|
+
style={{ width: '100%' }}
|
|
160
|
+
/>
|
|
161
|
+
</label>
|
|
152
162
|
<label>
|
|
153
163
|
Password
|
|
154
164
|
<input value={password} onChange={(e) => setPassword(e.target.value)} style={{ width: '100%' }} />
|
|
@@ -6,7 +6,6 @@ import { AppHandler } from './core/AppHandler';
|
|
|
6
6
|
// Surface modules: @rest routes and @service/@remote RPC. `toiljs build` discovers every
|
|
7
7
|
// decorated file under server/ on its own; importing them here keeps a direct `toilscript`
|
|
8
8
|
// run (which only sees the toilconfig entries) building the exact same server.
|
|
9
|
-
import './routes/Auth';
|
|
10
9
|
import './routes/Players';
|
|
11
10
|
import './routes/Leaderboard';
|
|
12
11
|
import './routes/Guestbook';
|
|
@@ -24,23 +24,17 @@ import { DataReader, DataWriter } from 'data';
|
|
|
24
24
|
*/
|
|
25
25
|
|
|
26
26
|
// @user: the authenticated-user shape. Exactly one per program.
|
|
27
|
+
//
|
|
28
|
+
// This app opts into built-in auth (`server: { auth: true }`), so the build runs in EXTEND mode: it
|
|
29
|
+
// injects the reserved `toilUserId` + `username` identity fields into THIS class (via `--authUser`).
|
|
30
|
+
// Only the app-specific extras (`admin`, `score`) are declared here — declaring `username`/`toilUserId`
|
|
31
|
+
// would collide with the injected fields and error. The shipped `/auth/*` controller reuses this shape.
|
|
27
32
|
@user
|
|
28
33
|
class Account {
|
|
29
|
-
username: string = '';
|
|
30
34
|
admin: bool = false;
|
|
31
35
|
score: u64 = 0;
|
|
32
36
|
}
|
|
33
37
|
|
|
34
|
-
/** Encode the @user session payload for `username` (admin if `root`). Exported
|
|
35
|
-
* as a FUNCTION (not the class, which would warn AS235 "only ... become WASM
|
|
36
|
-
* exports") so the PQ login can mint a session via the same generated codec. */
|
|
37
|
-
export function encodeSessionUser(username: string): Uint8Array {
|
|
38
|
-
const u = new Account();
|
|
39
|
-
u.username = username;
|
|
40
|
-
u.admin = username == 'root';
|
|
41
|
-
return u.encode();
|
|
42
|
-
}
|
|
43
|
-
|
|
44
38
|
@rest('session')
|
|
45
39
|
class Session {
|
|
46
40
|
/** POST /session/dev-login body: str(username) -> sets the session cookie.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "toiljs",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.89",
|
|
5
5
|
"author": "Dacely",
|
|
6
6
|
"description": "The modern React framework: a file-based React frontend and a ToilScript-compiled WebAssembly backend.",
|
|
7
7
|
"repository": {
|
|
@@ -136,7 +136,7 @@
|
|
|
136
136
|
"nodemailer": "^9.0.3",
|
|
137
137
|
"picocolors": "^1.1.1",
|
|
138
138
|
"sharp": "^0.35.3",
|
|
139
|
-
"toilscript": "^0.1.
|
|
139
|
+
"toilscript": "^0.1.54",
|
|
140
140
|
"typescript-eslint": "^8.62.1",
|
|
141
141
|
"vite": "^8.1.3",
|
|
142
142
|
"vite-imagetools": "^10.0.1",
|
|
@@ -147,7 +147,7 @@
|
|
|
147
147
|
"prettier": ">=3.0.0",
|
|
148
148
|
"react": ">=18.0.0",
|
|
149
149
|
"react-dom": ">=18.0.0",
|
|
150
|
-
"toilscript": ">=0.1.
|
|
150
|
+
"toilscript": ">=0.1.54",
|
|
151
151
|
"typescript": ">=6.0.0"
|
|
152
152
|
},
|
|
153
153
|
"overrides": {
|