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.
@@ -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
- const raw = readBytes(ref, reqPtr, reqLen);
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
- const raw = readBytes(ref, reqPtr, reqLen);
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,
@@ -34,6 +34,7 @@ const PROVIDED_IMPORTS = new Set([
34
34
  'client_ip',
35
35
  'ratelimit_check',
36
36
  'email_send',
37
+ 'email_send_detached',
37
38
  'env_get',
38
39
  'env_get_secure',
39
40
  'analytics_read',
@@ -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.88",
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.53",
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.53",
150
+ "toilscript": ">=0.1.54",
151
151
  "typescript": ">=6.0.0"
152
152
  },
153
153
  "overrides": {