cauthy-auth 0.12.0 → 0.13.0

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/dist/client.d.mts CHANGED
@@ -93,6 +93,13 @@ interface AuthMethods {
93
93
  * page that cannot, or a browser that refuses to here, sends the person.
94
94
  */
95
95
  passkeyOnCauthy: boolean;
96
+ /**
97
+ * The workspace's Cloudflare Turnstile site key, when it asks for the
98
+ * challenge: signing in, creating an account, and asking for a sign-in link
99
+ * or a new password then take its answer (`captchaToken`). The ready-made
100
+ * forms show it themselves.
101
+ */
102
+ captchaSiteKey?: string;
96
103
  }
97
104
  /** A passkey of the signed-in account. Moments are UTC. */
98
105
  interface Passkey {
@@ -134,6 +141,17 @@ interface LinkedIdentity {
134
141
  /** When it was linked, in UTC (ISO 8601). */
135
142
  linkedAt: string;
136
143
  }
144
+ /** How an account takes its second step. */
145
+ interface SecondStep {
146
+ totp: boolean;
147
+ /** The phone codes are sent to, masked (`+57 ••• ••• 4567`). */
148
+ phone: {
149
+ number: string;
150
+ channel: string;
151
+ } | null;
152
+ /** The ways the workspace sends codes to phones. */
153
+ phoneChannels: string[];
154
+ }
137
155
  interface AccountClient {
138
156
  /** The account, or null when nobody is signed in. */
139
157
  get(): Promise<Account | null>;
@@ -172,6 +190,30 @@ interface AccountClient {
172
190
  }): Promise<AuthResult<'renewed', {
173
191
  recoveryCodes: string[];
174
192
  }>>;
193
+ /**
194
+ * How the account takes its second step: an authenticator app, a phone
195
+ * (masked), and the ways the workspace sends codes to phones (`sms`,
196
+ * `whatsapp`; none when it has no Twilio account for it).
197
+ */
198
+ factors(): Promise<SecondStep | null>;
199
+ /** Adds a phone as the second step: a code is sent to it, for `verifyPhone`. */
200
+ addPhone(form: {
201
+ phone: string;
202
+ channel: 'sms' | 'whatsapp';
203
+ password: string;
204
+ }): Promise<AuthResult<'sent', {
205
+ sentTo: string;
206
+ channel: string;
207
+ }>>;
208
+ /** Proves the phone with the code it received. `recoveryCodes` come with a first second step, shown this once. */
209
+ verifyPhone(form: {
210
+ code: string;
211
+ }): Promise<AuthResult<'enabled', {
212
+ recoveryCodes: string[];
213
+ }>>;
214
+ removePhone(form: {
215
+ password: string;
216
+ }): Promise<AuthResult<'removed'>>;
175
217
  };
176
218
  /**
177
219
  * Asks to sign in with another address. Nothing changes yet: the new address
@@ -228,6 +270,7 @@ interface AuthClient {
228
270
  email(form: {
229
271
  email: string;
230
272
  password: string;
273
+ captchaToken?: string;
231
274
  }): Promise<AuthResult<'signed_in', {
232
275
  session: PublicSession;
233
276
  }> | AuthResult<'mfa_required'>>;
@@ -237,9 +280,23 @@ interface AuthClient {
237
280
  }): Promise<AuthResult<'signed_in', {
238
281
  session: PublicSession;
239
282
  }>>;
283
+ /** While the second step is asked for: whether the account has an authenticator app, and the phone (masked) a code can be sent to. */
284
+ mfaMethods(): Promise<AuthResult<'ok', {
285
+ totp: boolean;
286
+ phone: {
287
+ number: string;
288
+ channel: string;
289
+ } | null;
290
+ }>>;
291
+ /** Sends a code to the account's phone, by SMS or WhatsApp; `mfa` takes it. A few per sign-in. */
292
+ mfaSendCode(): Promise<AuthResult<'sent', {
293
+ sentTo: string;
294
+ channel: string;
295
+ }>>;
240
296
  /** Mails a link that signs the person in. The answer is the same whether or not the address has an account. */
241
297
  magicLink(form: {
242
298
  email: string;
299
+ captchaToken?: string;
243
300
  }): Promise<AuthResult<'sent'>>;
244
301
  /** Signs in with the token the mailed link opened the page with (`ml_token`). */
245
302
  magicLinkVerify(form: {
@@ -315,6 +372,7 @@ interface AuthClient {
315
372
  email: string;
316
373
  password: string;
317
374
  name?: string;
375
+ captchaToken?: string;
318
376
  }): Promise<AuthResult<'signed_in', {
319
377
  session: PublicSession;
320
378
  }> | AuthResult<'verification_required', {
@@ -324,6 +382,7 @@ interface AuthClient {
324
382
  /** Mails a link to choose a new password. The answer is the same whether or not the address has an account. */
325
383
  forgotPassword(form: {
326
384
  email: string;
385
+ captchaToken?: string;
327
386
  }): Promise<AuthResult<'sent'>>;
328
387
  /** Sets the new password, with the token the mailed link opened the page with (`token`). */
329
388
  resetPassword(form: {
@@ -360,4 +419,4 @@ interface AuthClientOptions {
360
419
  }
361
420
  declare function createAuthClient(options?: AuthClientOptions): AuthClient;
362
421
 
363
- export { type Account, type AccountClient, type AccountSession, type AuthClient, type AuthClientOptions, type AuthError, AuthField, type AuthMethods, type AuthResult, type LinkedIdentity, type Passkey, type SessionState, type SessionStore, type SessionStoreOptions, canAutofillPasskeys, canUsePasskeys, createAuthClient, createSessionStore };
422
+ export { type Account, type AccountClient, type AccountSession, type AuthClient, type AuthClientOptions, type AuthError, AuthField, type AuthMethods, type AuthResult, type LinkedIdentity, type Passkey, type SecondStep, type SessionState, type SessionStore, type SessionStoreOptions, canAutofillPasskeys, canUsePasskeys, createAuthClient, createSessionStore };
package/dist/client.d.ts CHANGED
@@ -93,6 +93,13 @@ interface AuthMethods {
93
93
  * page that cannot, or a browser that refuses to here, sends the person.
94
94
  */
95
95
  passkeyOnCauthy: boolean;
96
+ /**
97
+ * The workspace's Cloudflare Turnstile site key, when it asks for the
98
+ * challenge: signing in, creating an account, and asking for a sign-in link
99
+ * or a new password then take its answer (`captchaToken`). The ready-made
100
+ * forms show it themselves.
101
+ */
102
+ captchaSiteKey?: string;
96
103
  }
97
104
  /** A passkey of the signed-in account. Moments are UTC. */
98
105
  interface Passkey {
@@ -134,6 +141,17 @@ interface LinkedIdentity {
134
141
  /** When it was linked, in UTC (ISO 8601). */
135
142
  linkedAt: string;
136
143
  }
144
+ /** How an account takes its second step. */
145
+ interface SecondStep {
146
+ totp: boolean;
147
+ /** The phone codes are sent to, masked (`+57 ••• ••• 4567`). */
148
+ phone: {
149
+ number: string;
150
+ channel: string;
151
+ } | null;
152
+ /** The ways the workspace sends codes to phones. */
153
+ phoneChannels: string[];
154
+ }
137
155
  interface AccountClient {
138
156
  /** The account, or null when nobody is signed in. */
139
157
  get(): Promise<Account | null>;
@@ -172,6 +190,30 @@ interface AccountClient {
172
190
  }): Promise<AuthResult<'renewed', {
173
191
  recoveryCodes: string[];
174
192
  }>>;
193
+ /**
194
+ * How the account takes its second step: an authenticator app, a phone
195
+ * (masked), and the ways the workspace sends codes to phones (`sms`,
196
+ * `whatsapp`; none when it has no Twilio account for it).
197
+ */
198
+ factors(): Promise<SecondStep | null>;
199
+ /** Adds a phone as the second step: a code is sent to it, for `verifyPhone`. */
200
+ addPhone(form: {
201
+ phone: string;
202
+ channel: 'sms' | 'whatsapp';
203
+ password: string;
204
+ }): Promise<AuthResult<'sent', {
205
+ sentTo: string;
206
+ channel: string;
207
+ }>>;
208
+ /** Proves the phone with the code it received. `recoveryCodes` come with a first second step, shown this once. */
209
+ verifyPhone(form: {
210
+ code: string;
211
+ }): Promise<AuthResult<'enabled', {
212
+ recoveryCodes: string[];
213
+ }>>;
214
+ removePhone(form: {
215
+ password: string;
216
+ }): Promise<AuthResult<'removed'>>;
175
217
  };
176
218
  /**
177
219
  * Asks to sign in with another address. Nothing changes yet: the new address
@@ -228,6 +270,7 @@ interface AuthClient {
228
270
  email(form: {
229
271
  email: string;
230
272
  password: string;
273
+ captchaToken?: string;
231
274
  }): Promise<AuthResult<'signed_in', {
232
275
  session: PublicSession;
233
276
  }> | AuthResult<'mfa_required'>>;
@@ -237,9 +280,23 @@ interface AuthClient {
237
280
  }): Promise<AuthResult<'signed_in', {
238
281
  session: PublicSession;
239
282
  }>>;
283
+ /** While the second step is asked for: whether the account has an authenticator app, and the phone (masked) a code can be sent to. */
284
+ mfaMethods(): Promise<AuthResult<'ok', {
285
+ totp: boolean;
286
+ phone: {
287
+ number: string;
288
+ channel: string;
289
+ } | null;
290
+ }>>;
291
+ /** Sends a code to the account's phone, by SMS or WhatsApp; `mfa` takes it. A few per sign-in. */
292
+ mfaSendCode(): Promise<AuthResult<'sent', {
293
+ sentTo: string;
294
+ channel: string;
295
+ }>>;
240
296
  /** Mails a link that signs the person in. The answer is the same whether or not the address has an account. */
241
297
  magicLink(form: {
242
298
  email: string;
299
+ captchaToken?: string;
243
300
  }): Promise<AuthResult<'sent'>>;
244
301
  /** Signs in with the token the mailed link opened the page with (`ml_token`). */
245
302
  magicLinkVerify(form: {
@@ -315,6 +372,7 @@ interface AuthClient {
315
372
  email: string;
316
373
  password: string;
317
374
  name?: string;
375
+ captchaToken?: string;
318
376
  }): Promise<AuthResult<'signed_in', {
319
377
  session: PublicSession;
320
378
  }> | AuthResult<'verification_required', {
@@ -324,6 +382,7 @@ interface AuthClient {
324
382
  /** Mails a link to choose a new password. The answer is the same whether or not the address has an account. */
325
383
  forgotPassword(form: {
326
384
  email: string;
385
+ captchaToken?: string;
327
386
  }): Promise<AuthResult<'sent'>>;
328
387
  /** Sets the new password, with the token the mailed link opened the page with (`token`). */
329
388
  resetPassword(form: {
@@ -360,4 +419,4 @@ interface AuthClientOptions {
360
419
  }
361
420
  declare function createAuthClient(options?: AuthClientOptions): AuthClient;
362
421
 
363
- export { type Account, type AccountClient, type AccountSession, type AuthClient, type AuthClientOptions, type AuthError, AuthField, type AuthMethods, type AuthResult, type LinkedIdentity, type Passkey, type SessionState, type SessionStore, type SessionStoreOptions, canAutofillPasskeys, canUsePasskeys, createAuthClient, createSessionStore };
422
+ export { type Account, type AccountClient, type AccountSession, type AuthClient, type AuthClientOptions, type AuthError, AuthField, type AuthMethods, type AuthResult, type LinkedIdentity, type Passkey, type SecondStep, type SessionState, type SessionStore, type SessionStoreOptions, canAutofillPasskeys, canUsePasskeys, createAuthClient, createSessionStore };
package/dist/client.js CHANGED
@@ -317,13 +317,13 @@ function createAuthClient(options = {}) {
317
317
  const said = typeof options.locale === "function" ? options.locale() : options.locale;
318
318
  return (said ?? (typeof document === "undefined" ? "" : document.documentElement?.lang) ?? "").trim();
319
319
  };
320
- async function post(path, form, bad) {
320
+ async function post(path, form, bad, captchaToken) {
321
321
  if (bad) return { ok: false, error: { code: bad.code, message: bad.message, field: bad.field } };
322
322
  try {
323
323
  const res = await doFetch(`${base}${path}`, {
324
324
  method: "POST",
325
325
  credentials: "same-origin",
326
- headers: { "content-type": "application/json", accept: "application/json", ...locale() ? { "x-cauthy-locale": locale() } : {} },
326
+ headers: { "content-type": "application/json", accept: "application/json", ...locale() ? { "x-cauthy-locale": locale() } : {}, ...captchaToken ? { "x-cauthy-captcha": captchaToken } : {} },
327
327
  body: JSON.stringify(form)
328
328
  });
329
329
  const body = await res.json().catch(() => null);
@@ -375,9 +375,11 @@ function createAuthClient(options = {}) {
375
375
  }
376
376
  return {
377
377
  signIn: {
378
- email: (f) => post("/sign-in/email", { email: f.email, password: f.password }, firstProblem(checkEmail(f.email), checkPassword(f.password))),
378
+ email: (f) => post("/sign-in/email", { email: f.email, password: f.password }, firstProblem(checkEmail(f.email), checkPassword(f.password)), f.captchaToken),
379
379
  mfa: (f) => post("/sign-in/mfa", { code: f.code }, checkCode(f.code)),
380
- magicLink: (f) => post("/sign-in/magic-link", { email: f.email }, checkEmail(f.email)),
380
+ mfaMethods: () => post("/sign-in/mfa/methods", {}, null),
381
+ mfaSendCode: () => post("/sign-in/mfa/send", {}, null),
382
+ magicLink: (f) => post("/sign-in/magic-link", { email: f.email }, checkEmail(f.email), f.captchaToken),
381
383
  magicLinkVerify: (f) => post("/sign-in/magic-link/verify", { token: f.token }, null),
382
384
  emailCode: (f) => post("/sign-in/email-code", { code: f.code }, checkCode(f.code)),
383
385
  passkeyUrl,
@@ -458,9 +460,9 @@ function createAuthClient(options = {}) {
458
460
  }
459
461
  },
460
462
  signUp: {
461
- email: (f) => post("/sign-up/email", { email: f.email, password: f.password, name: f.name }, firstProblem(checkName(f.name), checkEmail(f.email), checkNewPassword(f.password)))
463
+ email: (f) => post("/sign-up/email", { email: f.email, password: f.password, name: f.name }, firstProblem(checkName(f.name), checkEmail(f.email), checkNewPassword(f.password)), f.captchaToken)
462
464
  },
463
- forgotPassword: (f) => post("/forgot-password", { email: f.email }, checkEmail(f.email)),
465
+ forgotPassword: (f) => post("/forgot-password", { email: f.email }, checkEmail(f.email), f.captchaToken),
464
466
  resetPassword: (f) => post("/reset-password", { token: f.token, password: f.password }, checkNewPassword(f.password)),
465
467
  verifyEmail: (f) => post("/verify-email", { token: f.token }, null),
466
468
  signOut: () => post("/sign-out", {}, null),
@@ -478,7 +480,14 @@ function createAuthClient(options = {}) {
478
480
  setup: (f) => post("/me/mfa/setup", { password: f.password }, null),
479
481
  verify: (f) => post("/me/mfa/verify", { code: f.code }, checkCode(f.code)),
480
482
  disable: (f) => post("/me/mfa/disable", { password: f.password }, null),
481
- recoveryCodes: (f) => post("/me/mfa/recovery-codes", { password: f.password }, null)
483
+ recoveryCodes: (f) => post("/me/mfa/recovery-codes", { password: f.password }, null),
484
+ async factors() {
485
+ const body = await read("/me/mfa/factors");
486
+ return body && typeof body.totp === "boolean" ? body : null;
487
+ },
488
+ addPhone: (f) => post("/me/mfa/phone", { phone: f.phone, channel: f.channel, password: f.password }, null),
489
+ verifyPhone: (f) => post("/me/mfa/phone/verify", { code: f.code }, checkCode(f.code)),
490
+ removePhone: (f) => post("/me/mfa/phone/remove", { password: f.password }, null)
482
491
  },
483
492
  changeEmail: (f) => post("/me/email", { email: f.email, password: f.password }, checkEmail(f.email)),
484
493
  async identities() {