toiljs 0.0.94 → 0.0.96

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.
@@ -23,13 +23,53 @@ export interface AuthOptions {
23
23
  }
24
24
  export declare function register(username: string, password: string, email: string, opts?: AuthOptions): Promise<void>;
25
25
  export declare function login(username: string, password: string, opts?: AuthOptions): Promise<Uint8Array>;
26
- export declare class EmailNotConfirmedError extends Error {
26
+ export declare enum AuthErrorCode {
27
+ RequestFailed = "request_failed",
28
+ ProtocolError = "protocol_error",
29
+ UsernameTaken = "username_taken",
30
+ EmailInUse = "email_in_use",
31
+ RegistrationRejected = "registration_rejected",
32
+ InvalidCredentials = "invalid_credentials",
33
+ EmailNotConfirmed = "email_not_confirmed",
34
+ TwoFactorRequired = "two_factor_required",
35
+ ServerAuthFailed = "server_auth_failed",
36
+ TwoFactorCodeInvalid = "two_factor_code_invalid",
37
+ TwoFactorSetupFailed = "two_factor_setup_failed",
38
+ ConfirmationInvalid = "confirmation_invalid",
39
+ PasswordResetInvalid = "password_reset_invalid"
40
+ }
41
+ export declare class AuthError extends Error {
42
+ readonly code: AuthErrorCode;
43
+ constructor(code: AuthErrorCode, message: string);
44
+ }
45
+ export declare class UsernameTakenError extends AuthError {
46
+ constructor();
47
+ }
48
+ export declare class EmailInUseError extends AuthError {
49
+ constructor();
50
+ }
51
+ export declare class InvalidCredentialsError extends AuthError {
52
+ constructor();
53
+ }
54
+ export declare class EmailNotConfirmedError extends AuthError {
27
55
  constructor();
28
56
  }
29
- export declare class TwoFactorRequiredError extends Error {
57
+ export declare class TwoFactorRequiredError extends AuthError {
30
58
  readonly twoFaId: string;
31
59
  constructor(twoFaId: string);
32
60
  }
61
+ export declare class ServerAuthFailedError extends AuthError {
62
+ constructor();
63
+ }
64
+ export declare class TwoFactorCodeError extends AuthError {
65
+ constructor();
66
+ }
67
+ export declare class ConfirmationInvalidError extends AuthError {
68
+ constructor();
69
+ }
70
+ export declare class PasswordResetInvalidError extends AuthError {
71
+ constructor();
72
+ }
33
73
  export declare const TwoFactorMethod: {
34
74
  readonly None: 0;
35
75
  readonly Email: 1;
@@ -59,4 +99,15 @@ export declare const Auth: {
59
99
  };
60
100
  readonly buildLoginMessage: typeof buildLoginMessage;
61
101
  readonly LOGIN_CONTEXT: "qauth:login:v1";
102
+ readonly AuthError: typeof AuthError;
103
+ readonly AuthErrorCode: typeof AuthErrorCode;
104
+ readonly UsernameTakenError: typeof UsernameTakenError;
105
+ readonly EmailInUseError: typeof EmailInUseError;
106
+ readonly InvalidCredentialsError: typeof InvalidCredentialsError;
107
+ readonly EmailNotConfirmedError: typeof EmailNotConfirmedError;
108
+ readonly TwoFactorRequiredError: typeof TwoFactorRequiredError;
109
+ readonly ServerAuthFailedError: typeof ServerAuthFailedError;
110
+ readonly TwoFactorCodeError: typeof TwoFactorCodeError;
111
+ readonly ConfirmationInvalidError: typeof ConfirmationInvalidError;
112
+ readonly PasswordResetInvalidError: typeof PasswordResetInvalidError;
62
113
  };
@@ -110,7 +110,7 @@ async function postBinary(baseUrl, path, body) {
110
110
  credentials: 'same-origin',
111
111
  });
112
112
  if (!res.ok)
113
- throw new Error('auth: request failed');
113
+ throw new AuthError(AuthErrorCode.RequestFailed, 'The auth request failed.');
114
114
  return new DataReader(new Uint8Array(await res.arrayBuffer()));
115
115
  }
116
116
  export async function register(username, password, email, opts = {}) {
@@ -121,7 +121,7 @@ export async function register(username, password, email, opts = {}) {
121
121
  const start = await postBinary(baseUrl, '/register/start', new DataWriter().writeString(username).writeBytes(blinded).toBytes());
122
122
  const status = start.readU8();
123
123
  if (status !== 0)
124
- throw new Error('auth: registration unavailable');
124
+ throw new AuthError(AuthErrorCode.RequestFailed, 'Registration is unavailable right now.');
125
125
  const kdf = decodeKdf(start);
126
126
  const evaluated = start.readBytes();
127
127
  const oprfOutput = oprf.finalize(pw, blind, evaluated);
@@ -144,7 +144,7 @@ export async function register(username, password, email, opts = {}) {
144
144
  wipe(seed);
145
145
  }
146
146
  if (publicKey.length !== PUBLIC_KEY_LEN)
147
- throw new Error('auth: bad public key length');
147
+ throw new AuthError(AuthErrorCode.ProtocolError, 'Server returned a bad public key length.');
148
148
  const finish = await postBinary(baseUrl, '/register/finish', new DataWriter()
149
149
  .writeString(username)
150
150
  .writeString(email)
@@ -153,11 +153,11 @@ export async function register(username, password, email, opts = {}) {
153
153
  .toBytes());
154
154
  const finishStatus = finish.readU8();
155
155
  if (finishStatus === 1)
156
- throw new Error('auth: username already registered (log in instead)');
156
+ throw new UsernameTakenError();
157
157
  if (finishStatus === 2)
158
- throw new Error('auth: email already in use');
158
+ throw new EmailInUseError();
159
159
  if (finishStatus !== 0)
160
- throw new Error('auth: registration rejected');
160
+ throw new AuthError(AuthErrorCode.RegistrationRejected, 'Registration was rejected.');
161
161
  }
162
162
  export async function login(username, password, opts = {}) {
163
163
  const baseUrl = opts.baseUrl ?? '/auth';
@@ -173,7 +173,7 @@ export async function login(username, password, opts = {}) {
173
173
  const exp = r.readU64();
174
174
  const evaluated = r.readBytes();
175
175
  if (BigInt(Math.floor(Date.now() / 1000)) >= exp)
176
- throw new Error('auth: challenge expired');
176
+ throw new AuthError(AuthErrorCode.ProtocolError, 'The login challenge expired; retry.');
177
177
  const oprfOutput = oprf.finalize(pw, blind, evaluated);
178
178
  const seed = await deriveSeed(oprfOutput, kdf);
179
179
  const { cipherText, sharedSecret } = ml_kem768.encapsulate(SERVER_KEM_PUBLIC_KEY);
@@ -193,7 +193,7 @@ export async function login(username, password, opts = {}) {
193
193
  wipe(seed);
194
194
  }
195
195
  if (signature.length !== SIGNATURE_LEN)
196
- throw new Error('auth: bad signature length');
196
+ throw new AuthError(AuthErrorCode.ProtocolError, 'Computed a bad signature length.');
197
197
  const res = await postBinary(baseUrl, '/login/finish', new DataWriter().writeBytes(cid).writeBytes(cipherText).writeBytes(signature).toBytes());
198
198
  const loginStatus = res.readU8();
199
199
  if (loginStatus === 2) {
@@ -202,7 +202,7 @@ export async function login(username, password, opts = {}) {
202
202
  }
203
203
  if (loginStatus !== 0 && loginStatus !== 3) {
204
204
  wipe(sharedSecret);
205
- throw new Error('auth: login failed');
205
+ throw new InvalidCredentialsError();
206
206
  }
207
207
  const first = res.readBytes();
208
208
  const serverConfirm = res.readBytes();
@@ -211,45 +211,111 @@ export async function login(username, password, opts = {}) {
211
211
  wipe(sharedSecret);
212
212
  const expected = await hmacSha256(sessionKey, concatBytes(utf8(SERVER_CONFIRM_LABEL), transcriptHash));
213
213
  if (!bytesEqual(expected, serverConfirm))
214
- throw new Error('auth: server authentication failed');
214
+ throw new ServerAuthFailedError();
215
215
  if (loginStatus === 3) {
216
216
  throw new TwoFactorRequiredError(toHex(first));
217
217
  }
218
218
  return first;
219
219
  }
220
- export class EmailNotConfirmedError extends Error {
220
+ export var AuthErrorCode;
221
+ (function (AuthErrorCode) {
222
+ AuthErrorCode["RequestFailed"] = "request_failed";
223
+ AuthErrorCode["ProtocolError"] = "protocol_error";
224
+ AuthErrorCode["UsernameTaken"] = "username_taken";
225
+ AuthErrorCode["EmailInUse"] = "email_in_use";
226
+ AuthErrorCode["RegistrationRejected"] = "registration_rejected";
227
+ AuthErrorCode["InvalidCredentials"] = "invalid_credentials";
228
+ AuthErrorCode["EmailNotConfirmed"] = "email_not_confirmed";
229
+ AuthErrorCode["TwoFactorRequired"] = "two_factor_required";
230
+ AuthErrorCode["ServerAuthFailed"] = "server_auth_failed";
231
+ AuthErrorCode["TwoFactorCodeInvalid"] = "two_factor_code_invalid";
232
+ AuthErrorCode["TwoFactorSetupFailed"] = "two_factor_setup_failed";
233
+ AuthErrorCode["ConfirmationInvalid"] = "confirmation_invalid";
234
+ AuthErrorCode["PasswordResetInvalid"] = "password_reset_invalid";
235
+ })(AuthErrorCode || (AuthErrorCode = {}));
236
+ export class AuthError extends Error {
237
+ code;
238
+ constructor(code, message) {
239
+ super(message);
240
+ this.name = 'AuthError';
241
+ this.code = code;
242
+ }
243
+ }
244
+ export class UsernameTakenError extends AuthError {
245
+ constructor() {
246
+ super(AuthErrorCode.UsernameTaken, 'That username is already registered.');
247
+ this.name = 'UsernameTakenError';
248
+ }
249
+ }
250
+ export class EmailInUseError extends AuthError {
251
+ constructor() {
252
+ super(AuthErrorCode.EmailInUse, 'That email is already in use.');
253
+ this.name = 'EmailInUseError';
254
+ }
255
+ }
256
+ export class InvalidCredentialsError extends AuthError {
257
+ constructor() {
258
+ super(AuthErrorCode.InvalidCredentials, 'Incorrect username or password.');
259
+ this.name = 'InvalidCredentialsError';
260
+ }
261
+ }
262
+ export class EmailNotConfirmedError extends AuthError {
221
263
  constructor() {
222
- super('auth: email not confirmed');
264
+ super(AuthErrorCode.EmailNotConfirmed, 'Your email address is not confirmed yet.');
223
265
  this.name = 'EmailNotConfirmedError';
224
266
  }
225
267
  }
226
- export class TwoFactorRequiredError extends Error {
268
+ export class TwoFactorRequiredError extends AuthError {
227
269
  twoFaId;
228
270
  constructor(twoFaId) {
229
- super('auth: two-factor authentication required');
271
+ super(AuthErrorCode.TwoFactorRequired, 'A second factor is required to sign in.');
230
272
  this.name = 'TwoFactorRequiredError';
231
273
  this.twoFaId = twoFaId;
232
274
  }
233
275
  }
276
+ export class ServerAuthFailedError extends AuthError {
277
+ constructor() {
278
+ super(AuthErrorCode.ServerAuthFailed, 'Server authentication failed (possible MITM).');
279
+ this.name = 'ServerAuthFailedError';
280
+ }
281
+ }
282
+ export class TwoFactorCodeError extends AuthError {
283
+ constructor() {
284
+ super(AuthErrorCode.TwoFactorCodeInvalid, 'The verification code is invalid or expired.');
285
+ this.name = 'TwoFactorCodeError';
286
+ }
287
+ }
288
+ export class ConfirmationInvalidError extends AuthError {
289
+ constructor() {
290
+ super(AuthErrorCode.ConfirmationInvalid, 'This confirmation link is invalid or has expired.');
291
+ this.name = 'ConfirmationInvalidError';
292
+ }
293
+ }
294
+ export class PasswordResetInvalidError extends AuthError {
295
+ constructor() {
296
+ super(AuthErrorCode.PasswordResetInvalid, 'This password-reset link is invalid or has expired.');
297
+ this.name = 'PasswordResetInvalidError';
298
+ }
299
+ }
234
300
  export const TwoFactorMethod = { None: 0, Email: 1 };
235
301
  export async function verifyTwoFactor(twoFaId, code, opts = {}) {
236
302
  const baseUrl = opts.baseUrl ?? '/auth';
237
303
  const res = await postBinary(baseUrl, '/2fa/verify', new DataWriter().writeBytes(fromHex(twoFaId)).writeString(code).toBytes());
238
304
  if (res.readU8() !== 0)
239
- throw new Error('auth: two-factor code invalid or expired');
305
+ throw new TwoFactorCodeError();
240
306
  return res.readBytes();
241
307
  }
242
308
  export async function setupTwoFactor(method, opts = {}) {
243
309
  const baseUrl = opts.baseUrl ?? '/auth';
244
310
  const res = await postBinary(baseUrl, '/2fa/setup', new DataWriter().writeU8(method).toBytes());
245
311
  if (res.readU8() !== 0)
246
- throw new Error('auth: two-factor setup failed');
312
+ throw new AuthError(AuthErrorCode.TwoFactorSetupFailed, 'Enabling or disabling two-factor failed.');
247
313
  }
248
314
  export async function confirmTwoFactorSetup(code, opts = {}) {
249
315
  const baseUrl = opts.baseUrl ?? '/auth';
250
316
  const res = await postBinary(baseUrl, '/2fa/setup/verify', new DataWriter().writeString(code).toBytes());
251
317
  if (res.readU8() !== 0)
252
- throw new Error('auth: two-factor setup confirmation failed');
318
+ throw new AuthError(AuthErrorCode.TwoFactorSetupFailed, 'Confirming the two-factor setup failed.');
253
319
  }
254
320
  export async function twoFactorStatus(opts = {}) {
255
321
  const baseUrl = opts.baseUrl ?? '/auth';
@@ -258,14 +324,14 @@ export async function twoFactorStatus(opts = {}) {
258
324
  credentials: 'same-origin',
259
325
  });
260
326
  if (!res.ok)
261
- throw new Error('auth: request failed');
327
+ throw new AuthError(AuthErrorCode.RequestFailed, 'The auth request failed.');
262
328
  return new DataReader(new Uint8Array(await res.arrayBuffer())).readU8();
263
329
  }
264
330
  export async function confirmEmail(token, opts = {}) {
265
331
  const baseUrl = opts.baseUrl ?? '/auth';
266
332
  const res = await postBinary(baseUrl, '/confirm', new DataWriter().writeBytes(fromHex(token)).toBytes());
267
333
  if (res.readU8() !== 0)
268
- throw new Error('auth: confirmation link invalid or expired');
334
+ throw new ConfirmationInvalidError();
269
335
  }
270
336
  export async function resendConfirmation(email, opts = {}) {
271
337
  const baseUrl = opts.baseUrl ?? '/auth';
@@ -283,7 +349,7 @@ export async function resetPassword(token, newPassword, opts = {}) {
283
349
  const { blind, blinded } = oprf.blind(pw);
284
350
  const start = await postBinary(baseUrl, '/reset/start', new DataWriter().writeBytes(rawToken).writeBytes(blinded).toBytes());
285
351
  if (start.readU8() !== 0)
286
- throw new Error('auth: reset link invalid or expired');
352
+ throw new PasswordResetInvalidError();
287
353
  const username = start.readString();
288
354
  const kdf = decodeKdf(start);
289
355
  const evaluated = start.readBytes();
@@ -307,10 +373,10 @@ export async function resetPassword(token, newPassword, opts = {}) {
307
373
  wipe(seed);
308
374
  }
309
375
  if (publicKey.length !== PUBLIC_KEY_LEN)
310
- throw new Error('auth: bad public key length');
376
+ throw new AuthError(AuthErrorCode.ProtocolError, 'Server returned a bad public key length.');
311
377
  const finish = await postBinary(baseUrl, '/reset/finish', new DataWriter().writeBytes(rawToken).writeBytes(publicKey).writeBytes(regProof).toBytes());
312
378
  if (finish.readU8() !== 0)
313
- throw new Error('auth: password reset rejected');
379
+ throw new PasswordResetInvalidError();
314
380
  }
315
381
  export const Auth = {
316
382
  register,
@@ -326,4 +392,15 @@ export const Auth = {
326
392
  TwoFactorMethod,
327
393
  buildLoginMessage,
328
394
  LOGIN_CONTEXT,
395
+ AuthError,
396
+ AuthErrorCode,
397
+ UsernameTakenError,
398
+ EmailInUseError,
399
+ InvalidCredentialsError,
400
+ EmailNotConfirmedError,
401
+ TwoFactorRequiredError,
402
+ ServerAuthFailedError,
403
+ TwoFactorCodeError,
404
+ ConfirmationInvalidError,
405
+ PasswordResetInvalidError,
329
406
  };
@@ -1,6 +1,6 @@
1
1
  export { mount } from './routing/mount.js';
2
2
  export { Router } from './routing/Router.js';
3
- export { Auth, register as authRegister, login as authLogin, buildLoginMessage, LOGIN_CONTEXT, } from './auth.js';
3
+ export { Auth, register as authRegister, login as authLogin, buildLoginMessage, LOGIN_CONTEXT, AuthError, AuthErrorCode, UsernameTakenError, EmailInUseError, InvalidCredentialsError, EmailNotConfirmedError, TwoFactorRequiredError, ServerAuthFailedError, TwoFactorCodeError, ConfirmationInvalidError, PasswordResetInvalidError, } from './auth.js';
4
4
  export type { KdfParams, AuthOptions } from './auth.js';
5
5
  export { Link } from './navigation/Link.js';
6
6
  export type { LinkProps } from './navigation/Link.js';
@@ -1,6 +1,6 @@
1
1
  export { mount } from './routing/mount.js';
2
2
  export { Router } from './routing/Router.js';
3
- export { Auth, register as authRegister, login as authLogin, buildLoginMessage, LOGIN_CONTEXT, } from './auth.js';
3
+ export { Auth, register as authRegister, login as authLogin, buildLoginMessage, LOGIN_CONTEXT, AuthError, AuthErrorCode, UsernameTakenError, EmailInUseError, InvalidCredentialsError, EmailNotConfirmedError, TwoFactorRequiredError, ServerAuthFailedError, TwoFactorCodeError, ConfirmationInvalidError, PasswordResetInvalidError, } from './auth.js';
4
4
  export { Link } from './navigation/Link.js';
5
5
  export { NavLink, matchActive } from './navigation/NavLink.js';
6
6
  export { navigate, back, forward, refresh, setViewTransitions, setTransitions, href, } from './navigation/navigation.js';
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "toiljs",
3
3
  "type": "module",
4
- "version": "0.0.94",
4
+ "version": "0.0.96",
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": {
package/src/cli/create.ts CHANGED
@@ -146,7 +146,7 @@ function scaffold(
146
146
  '@types/react-dom': '^19.2.3',
147
147
  eslint: '^10.2.0',
148
148
  prettier: '^3.8.1',
149
- toilscript: '^0.1.37',
149
+ toilscript: '^0.1.55',
150
150
  typescript: '^6.0.3',
151
151
  };
152
152
  for (const dep of requiredPackages(features).sort()) {