@work-shape/catalyst-api-client 0.8.0-rc.10 → 0.8.0-rc.11

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/index.d.ts CHANGED
@@ -25,13 +25,61 @@ interface AuthResult {
25
25
  /** The sign-in library's code for a refusal, such as `INVALID_EMAIL_OR_PASSWORD`. */
26
26
  code?: string;
27
27
  message?: string;
28
+ /** The HTTP status of a refusal; 429 is the instance's limit on attempts. */
29
+ status?: number;
30
+ }
31
+ /** The second factor of a sign-in: a code from the authenticator app, or a backup code. */
32
+ type SecondFactorKind = "app" | "backup";
33
+ /** A sign-in: refused, signed in, or waiting for the second factor (TWO-FACTOR-1). */
34
+ interface SignInResult extends AuthResult {
35
+ /** The password was right and the account asks for a code before there is a session. */
36
+ secondFactor?: true;
37
+ }
38
+ /** What an authenticator app reads, and the ten backup codes, shown once. */
39
+ interface TwoFactorSetup {
40
+ totpUri: string;
41
+ backupCodes: string[];
28
42
  }
29
43
  declare function getAuthSession(apiBaseUrl: string): Promise<AuthSession | null>;
30
44
  declare function signInWithEmail(input: {
31
45
  apiBaseUrl: string;
32
46
  email: string;
33
47
  password: string;
48
+ }): Promise<SignInResult>;
49
+ /**
50
+ * The second sign-in step: a code from the authenticator app, or a backup code, which signs in
51
+ * once. `trustBrowser` skips the code in this browser for 30 days from now.
52
+ */
53
+ declare function verifySecondFactor(input: {
54
+ apiBaseUrl: string;
55
+ code: string;
56
+ kind: SecondFactorKind;
57
+ trustBrowser?: boolean;
58
+ }): Promise<AuthResult>;
59
+ /**
60
+ * Starts the setup of two-factor sign-in with the password. The factor is on only after the
61
+ * first code from the app is confirmed with `verifySecondFactor`.
62
+ */
63
+ declare function startTwoFactorSetup(input: {
64
+ apiBaseUrl: string;
65
+ password: string;
66
+ }): Promise<AuthResult & {
67
+ setup?: TwoFactorSetup;
68
+ }>;
69
+ /** Turns two-factor sign-in off with the password; every trusted browser is forgotten. */
70
+ declare function turnOffTwoFactor(input: {
71
+ apiBaseUrl: string;
72
+ password: string;
34
73
  }): Promise<AuthResult>;
74
+ /** Replaces all ten backup codes with the password. The new ones are shown once. */
75
+ declare function regenerateBackupCodes(input: {
76
+ apiBaseUrl: string;
77
+ password: string;
78
+ }): Promise<AuthResult & {
79
+ backupCodes?: string[];
80
+ }>;
81
+ /** Every browser of the person asks for the code again at its next sign-in. */
82
+ declare function forgetTrustedBrowsers(apiBaseUrl: string): Promise<AuthResult>;
35
83
  /**
36
84
  * For a tool outside a browser, which keeps no cookies: signs in and returns a fetch that
37
85
  * carries the session cookie and the origin the instance trusts on every request.
@@ -248,4 +296,4 @@ declare function listAll<Item>(readPage: (paging: {
248
296
  nextCursor?: string;
249
297
  }>): Promise<Item[]>;
250
298
 
251
- export { AUTH_MOUNT_PATH, type ApiClient, type ApiClientFor, type ApiClientOptions, ApiError, type ApiOperationMethods, ApiResponseShapeError, type AuthResult, type AuthSession, type OperationInput, type OperationOutcome, completedOutput, createApiClient, createApiClientFor, getAuthSession, listAll, refusalError, signInForSessionFetch, signInWithEmail, signOut, transportError, unreadableResponseError };
299
+ export { AUTH_MOUNT_PATH, type ApiClient, type ApiClientFor, type ApiClientOptions, ApiError, type ApiOperationMethods, ApiResponseShapeError, type AuthResult, type AuthSession, type OperationInput, type OperationOutcome, type SecondFactorKind, type SignInResult, type TwoFactorSetup, completedOutput, createApiClient, createApiClientFor, forgetTrustedBrowsers, getAuthSession, listAll, refusalError, regenerateBackupCodes, signInForSessionFetch, signInWithEmail, signOut, startTwoFactorSetup, transportError, turnOffTwoFactor, unreadableResponseError, verifySecondFactor };
package/dist/index.js CHANGED
@@ -10,6 +10,11 @@ var authFailureSchema = z.object({
10
10
  message: z.string().optional(),
11
11
  error: z.object({ message: z.string().optional() }).optional()
12
12
  });
13
+ var signInAnswerSchema = z.object({ twoFactorRedirect: z.boolean().optional() });
14
+ var twoFactorSetupSchema = z.object({
15
+ totpURI: z.string(),
16
+ backupCodes: z.array(z.string())
17
+ });
13
18
  async function getAuthSession(apiBaseUrl) {
14
19
  const response = await fetch(authUrl(apiBaseUrl, "/get-session"), {
15
20
  credentials: "include"
@@ -33,13 +38,64 @@ async function signInWithEmail(input) {
33
38
  })
34
39
  });
35
40
  if (response.ok) {
36
- return { ok: true };
41
+ const answer = signInAnswerSchema.safeParse(await response.json().catch(() => void 0));
42
+ return answer.data?.twoFactorRedirect === true ? { ok: true, secondFactor: true } : { ok: true };
37
43
  }
44
+ return failureOf(response, "Sign in failed");
45
+ }
46
+ async function verifySecondFactor(input) {
47
+ const path = input.kind === "app" ? "/two-factor/verify-totp" : "/two-factor/verify-backup-code";
48
+ const response = await postAuth(input.apiBaseUrl, path, {
49
+ code: input.code.replaceAll(/\s/gu, ""),
50
+ trustDevice: input.trustBrowser === true
51
+ });
52
+ return response.ok ? { ok: true } : failureOf(response, "The code was not accepted");
53
+ }
54
+ async function startTwoFactorSetup(input) {
55
+ const response = await postAuth(input.apiBaseUrl, "/two-factor/enable", {
56
+ password: input.password
57
+ });
58
+ if (!response.ok) {
59
+ return failureOf(response, "Two-factor sign-in could not be set up");
60
+ }
61
+ const setup = twoFactorSetupSchema.parse(await response.json());
62
+ return { ok: true, setup: { totpUri: setup.totpURI, backupCodes: setup.backupCodes } };
63
+ }
64
+ async function turnOffTwoFactor(input) {
65
+ const response = await postAuth(input.apiBaseUrl, "/two-factor/disable", {
66
+ password: input.password
67
+ });
68
+ return response.ok ? { ok: true } : failureOf(response, "Two-factor sign-in stayed on");
69
+ }
70
+ async function regenerateBackupCodes(input) {
71
+ const response = await postAuth(input.apiBaseUrl, "/two-factor/generate-backup-codes", {
72
+ password: input.password
73
+ });
74
+ if (!response.ok) {
75
+ return failureOf(response, "No new backup codes were made");
76
+ }
77
+ const { backupCodes } = twoFactorSetupSchema.pick({ backupCodes: true }).parse(await response.json());
78
+ return { ok: true, backupCodes };
79
+ }
80
+ async function forgetTrustedBrowsers(apiBaseUrl) {
81
+ const response = await postAuth(apiBaseUrl, "/two-factor/forget-trusted-browsers", {});
82
+ return response.ok ? { ok: true } : failureOf(response, "The browsers are still trusted");
83
+ }
84
+ function postAuth(apiBaseUrl, path, body) {
85
+ return fetch(authUrl(apiBaseUrl, path), {
86
+ method: "POST",
87
+ credentials: "include",
88
+ headers: { "content-type": "application/json" },
89
+ body: JSON.stringify(body)
90
+ });
91
+ }
92
+ async function failureOf(response, fallback) {
38
93
  const failure = authFailureSchema.safeParse(await response.json().catch(() => void 0));
39
94
  return {
40
95
  ok: false,
96
+ status: response.status,
41
97
  ...failure.data?.code === void 0 ? {} : { code: failure.data.code },
42
- message: failure.data?.message ?? failure.data?.error?.message ?? "Sign in failed"
98
+ message: failure.data?.message ?? failure.data?.error?.message ?? fallback
43
99
  };
44
100
  }
45
101
  async function signInForSessionFetch(input) {
@@ -52,6 +108,12 @@ async function signInForSessionFetch(input) {
52
108
  if (!response.ok) {
53
109
  throw new Error(`Sign-in as '${input.email}' was refused with status ${response.status}`);
54
110
  }
111
+ const answer = signInAnswerSchema.safeParse(
112
+ await response.clone().json().catch(() => void 0)
113
+ );
114
+ if (answer.data?.twoFactorRedirect === true) {
115
+ throw new Error(`'${input.email}' signs in with a second factor; use an API key for this tool`);
116
+ }
55
117
  const cookie = response.headers.getSetCookie().map((value) => value.split(";", 1)[0]).join("; ");
56
118
  return (resource, init) => {
57
119
  const request = new Request(resource, init);
@@ -433,12 +495,17 @@ export {
433
495
  completedOutput,
434
496
  createApiClient,
435
497
  createApiClientFor,
498
+ forgetTrustedBrowsers,
436
499
  getAuthSession,
437
500
  listAll,
438
501
  refusalError,
502
+ regenerateBackupCodes,
439
503
  signInForSessionFetch,
440
504
  signInWithEmail,
441
505
  signOut,
506
+ startTwoFactorSetup,
442
507
  transportError,
443
- unreadableResponseError
508
+ turnOffTwoFactor,
509
+ unreadableResponseError,
510
+ verifySecondFactor
444
511
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@work-shape/catalyst-api-client",
3
- "version": "0.8.0-rc.10",
3
+ "version": "0.8.0-rc.11",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -29,7 +29,7 @@
29
29
  },
30
30
  "dependencies": {
31
31
  "zod": "4.4.3",
32
- "@work-shape/catalyst-api-contract": "0.8.0-rc.10"
32
+ "@work-shape/catalyst-api-contract": "0.8.0-rc.11"
33
33
  },
34
34
  "devDependencies": {
35
35
  "@types/node": "^24.13.1",