@cavuno/board 4.30.0 → 4.31.1

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.
Files changed (46) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/dist/{_spec-CuSpfit_.d.mts → _spec-BD5My4zL.d.mts} +76 -0
  3. package/dist/{_spec-CuSpfit_.d.ts → _spec-BD5My4zL.d.ts} +76 -0
  4. package/dist/bin.mjs +31 -8
  5. package/dist/{blog-CvQeoBc8.d.ts → blog-CcLmN5Tw.d.ts} +1 -1
  6. package/dist/{blog-21XnewAu.d.mts → blog-Dh-Ru5QZ.d.mts} +1 -1
  7. package/dist/{board-t_j16FrX.d.ts → board-BRWS1m6D.d.mts} +16 -3
  8. package/dist/{board-VrjkKuZ-.d.mts → board-CF66EdYi.d.ts} +16 -3
  9. package/dist/{breadcrumbs-BpONykkn.d.mts → breadcrumbs-AEupw3fi.d.mts} +2 -2
  10. package/dist/{breadcrumbs-CJ5eu_ye.d.ts → breadcrumbs-Bu1lN6qA.d.ts} +2 -2
  11. package/dist/browser/cavuno-board.global.min.js +1 -1
  12. package/dist/{chunk-EHSB2VAJ.mjs → chunk-WSO6JN5C.mjs} +37 -1
  13. package/dist/doctor.js +31 -8
  14. package/dist/doctor.mjs +31 -8
  15. package/dist/filters.d.mts +2 -2
  16. package/dist/filters.d.ts +2 -2
  17. package/dist/format.d.mts +4 -4
  18. package/dist/format.d.ts +4 -4
  19. package/dist/index.d.mts +238 -13
  20. package/dist/index.d.ts +238 -13
  21. package/dist/index.js +250 -5
  22. package/dist/index.mjs +222 -6
  23. package/dist/{jobs-h-ewgbXn.d.mts → jobs-BPTMfkal.d.mts} +1 -1
  24. package/dist/{jobs-CErce8Tw.d.ts → jobs-BbtjrUo2.d.ts} +1 -1
  25. package/dist/{search-BefsJD_f.d.mts → search-CIjAdPR-.d.mts} +1 -1
  26. package/dist/{search-B2LdM6bj.d.ts → search-CqPIHkv8.d.ts} +1 -1
  27. package/dist/{seo-BFQIA81t.d.mts → seo-BypmVm97.d.mts} +1 -1
  28. package/dist/{seo-BPDNxOiZ.d.ts → seo-C-NQ0T9w.d.ts} +1 -1
  29. package/dist/seo-files.d.mts +2 -2
  30. package/dist/seo-files.d.ts +2 -2
  31. package/dist/seo.d.mts +6 -6
  32. package/dist/seo.d.ts +6 -6
  33. package/dist/server.d.mts +27 -22
  34. package/dist/server.d.ts +27 -22
  35. package/dist/server.js +8 -13
  36. package/dist/server.mjs +9 -14
  37. package/dist/sitemap.d.mts +6 -6
  38. package/dist/sitemap.d.ts +6 -6
  39. package/dist/sitemap.mjs +1 -1
  40. package/dist/suggest.d.mts +2 -2
  41. package/dist/suggest.d.ts +2 -2
  42. package/package.json +1 -1
  43. package/skills/cavuno-board-auth/SKILL.md +1 -1
  44. package/skills/cavuno-board-server-sessions/SKILL.md +5 -5
  45. package/skills/cavuno-board-setup/SKILL.md +1 -1
  46. package/skills/manifest.json +2 -2
package/dist/index.js CHANGED
@@ -27,6 +27,8 @@ __export(src_exports, {
27
27
  BoardClient: () => BoardClient,
28
28
  REFRESH_TOKEN_KEY: () => REFRESH_TOKEN_KEY,
29
29
  SDK_VERSION: () => SDK_VERSION,
30
+ SIGN_IN_REDIRECT_ERROR_CODES: () => SIGN_IN_REDIRECT_ERROR_CODES,
31
+ SSO_LINK_PROOF_BINDING_TTL_MS: () => SSO_LINK_PROOF_BINDING_TTL_MS,
30
32
  createBoardClient: () => createBoardClient,
31
33
  isBoardApiError: () => isBoardApiError,
32
34
  isBoardPasswordRequired: () => isBoardPasswordRequired,
@@ -38,12 +40,17 @@ __export(src_exports, {
38
40
  isOwnMessage: () => isOwnMessage,
39
41
  isRateLimited: () => isRateLimited,
40
42
  isSafeApplicationUrl: () => isSafeApplicationUrl,
43
+ isSignInMethodUnavailable: () => isSignInMethodUnavailable,
44
+ isSsoBrowserMismatch: () => isSsoBrowserMismatch,
45
+ isSsoRequired: () => isSsoRequired,
41
46
  isUnauthorized: () => isUnauthorized,
42
47
  isValidationError: () => isValidationError,
43
48
  lastOwnMessageId: () => lastOwnMessageId,
44
49
  paginate: () => paginate,
50
+ parseOAuthCompletion: () => parseOAuthCompletion,
45
51
  resolveApplyAction: () => resolveApplyAction,
46
- resolveApplyDecision: () => resolveApplyDecision
52
+ resolveApplyDecision: () => resolveApplyDecision,
53
+ ssoLinkProofBindingStore: () => ssoLinkProofBindingStore
47
54
  });
48
55
  module.exports = __toCommonJS(src_exports);
49
56
 
@@ -74,6 +81,19 @@ var BOARD_API_ERROR_CODES = [
74
81
  "board_auth_invalid_token",
75
82
  "board_auth_registration_disabled",
76
83
  "board_auth_token_expired",
84
+ "board_development_origin_not_allowed_for_email",
85
+ "board_development_origin_not_registered",
86
+ "board_development_origin_requires_publishable_key",
87
+ // Board-user SSO sign-in
88
+ "sso_required",
89
+ // Deprecated; retained for older API responses.
90
+ "board_auth_method_unavailable",
91
+ "board_auth_sso_connection_not_found",
92
+ "board_auth_sso_role_unavailable",
93
+ "board_auth_sso_provider_unavailable",
94
+ "board_auth_sso_browser_mismatch",
95
+ "board_auth_sso_not_provisioned",
96
+ "board_auth_sso_identity_linked_elsewhere",
77
97
  "invalid_current_password",
78
98
  "no_password",
79
99
  "same_email",
@@ -107,6 +127,9 @@ var BOARD_API_ERROR_CODES = [
107
127
  "blog_post_not_found",
108
128
  "blog_author_not_found",
109
129
  "blog_tag_not_found",
130
+ // Job aggregator feeds
131
+ "feeds_not_found",
132
+ "feed_not_ready",
110
133
  // Talent directory
111
134
  "talent_not_found",
112
135
  "talent_directory_not_found",
@@ -255,6 +278,23 @@ function isConflict(e) {
255
278
  function isFreeEmailWebsiteError(e) {
256
279
  return isBoardApiError(e) && e.status === 422 && e.code === "employer_free_email_website";
257
280
  }
281
+ function isSsoRequired(e) {
282
+ if (!isBoardApiError(e) || e.status !== 403) return false;
283
+ const ids = e.details?.connectionIds;
284
+ return Array.isArray(ids) && ids.every((id) => typeof id === "string") && (e.code === "sso_required" || isSignInMethodUnavailable(e) && e.details.availableMethods.methods.length === 0);
285
+ }
286
+ function isSignInMethodUnavailable(e) {
287
+ if (!isBoardApiError(e) || e.status !== 403 || e.code !== "board_auth_method_unavailable") {
288
+ return false;
289
+ }
290
+ const available = e.details?.availableMethods;
291
+ if (!available || typeof available !== "object") return false;
292
+ const { methods, ssoConnectionIds } = available;
293
+ return Array.isArray(methods) && methods.every((m) => typeof m === "string") && Array.isArray(ssoConnectionIds) && ssoConnectionIds.every((id) => typeof id === "string");
294
+ }
295
+ function isSsoBrowserMismatch(e) {
296
+ return isBoardApiError(e) && e.status === 403 && e.code === "board_auth_sso_browser_mismatch";
297
+ }
258
298
 
259
299
  // src/query.ts
260
300
  function toSearchParams(query) {
@@ -363,7 +403,7 @@ async function clearSession(storage) {
363
403
  }
364
404
 
365
405
  // src/version.ts
366
- var SDK_VERSION = "4.30.0";
406
+ var SDK_VERSION = "4.31.1";
367
407
 
368
408
  // src/client.ts
369
409
  function isRawBody(body) {
@@ -470,8 +510,144 @@ var BoardClient = class {
470
510
  // src/constants.ts
471
511
  var DEFAULT_CAVUNO_API_URL = "https://api.cavuno.com";
472
512
 
513
+ // src/sso.ts
514
+ var SIGN_IN_REDIRECT_ERROR_CODES = [
515
+ "oauth_cancelled",
516
+ "oauth_failed",
517
+ "oauth_email_unverified",
518
+ "role_disabled",
519
+ "method_unavailable",
520
+ // Deprecated: retained for boards served by earlier API releases.
521
+ "sso_required",
522
+ "sso_state_invalid",
523
+ "sso_cancelled",
524
+ "sso_failed",
525
+ "sso_connection_unavailable",
526
+ "sso_not_provisioned",
527
+ "sso_email_required",
528
+ "sso_account_disabled",
529
+ "sso_identity_linked_elsewhere",
530
+ "sso_link_proof_rate_limited",
531
+ "sso_development_origin_not_allowed_for_email"
532
+ ];
533
+ function toSearchParams2(input) {
534
+ if (input instanceof URLSearchParams) return input;
535
+ if (typeof input === "string") {
536
+ const query = input.includes("?") ? input.slice(input.indexOf("?")) : input;
537
+ return new URLSearchParams(query);
538
+ }
539
+ const params = new URLSearchParams();
540
+ for (const [key, value] of Object.entries(input)) {
541
+ if (value !== void 0) params.set(key, value);
542
+ }
543
+ return params;
544
+ }
545
+ function safeReturnTo(value) {
546
+ if (!value || !value.startsWith("/") || value.startsWith("//") || value.startsWith("/\\") || value.includes("://") || /[\u0000-\u001F\u007F]/.test(value)) {
547
+ return "/";
548
+ }
549
+ return value;
550
+ }
551
+ function roleOf(value) {
552
+ return value === "candidate" || value === "employer" ? value : null;
553
+ }
554
+ function parseOAuthCompletion(input) {
555
+ const params = toSearchParams2(input);
556
+ const get = (key) => {
557
+ const value = params.get(key);
558
+ return value && value.length > 0 ? value : null;
559
+ };
560
+ const returnTo = safeReturnTo(get("returnTo"));
561
+ const role = roleOf(get("role"));
562
+ const error = get("error");
563
+ if (error) return { kind: "error", error, returnTo };
564
+ const linkProof = get("linkProof");
565
+ if (linkProof) return { kind: "link_proof", linkProof, role, returnTo };
566
+ const linkProofBinding = get("linkProofBinding");
567
+ if (get("status") === "sso_link_proof_sent" && linkProofBinding) {
568
+ return { kind: "link_proof_sent", linkProofBinding, role, returnTo };
569
+ }
570
+ const token = get("token");
571
+ if (token) {
572
+ return {
573
+ kind: "token",
574
+ token,
575
+ method: get("method"),
576
+ role,
577
+ returnTo,
578
+ isNewUser: get("isNew") === "1"
579
+ };
580
+ }
581
+ return { kind: "invalid", returnTo };
582
+ }
583
+ var SSO_LINK_PROOF_BINDING_TTL_MS = 25 * 60 * 1e3;
584
+ var BINDING_KEY = "cavuno_board_sso_link_proof_binding";
585
+ function defaultStorage() {
586
+ try {
587
+ const storage = globalThis.localStorage;
588
+ return storage ?? null;
589
+ } catch {
590
+ return null;
591
+ }
592
+ }
593
+ function ssoLinkProofBindingStore(board, options = {}) {
594
+ const key = `${BINDING_KEY}:${scopeToken(board)}`;
595
+ const now = options.now ?? Date.now;
596
+ const storage = () => options.storage ?? defaultStorage();
597
+ const clear = () => {
598
+ try {
599
+ storage()?.removeItem(key);
600
+ } catch {
601
+ }
602
+ };
603
+ return {
604
+ begin() {
605
+ const bytes = globalThis.crypto.getRandomValues(new Uint8Array(32));
606
+ const binding = Array.from(
607
+ bytes,
608
+ (value) => value.toString(16).padStart(2, "0")
609
+ ).join("");
610
+ const target = storage();
611
+ if (!target)
612
+ throw new Error(
613
+ "SSO requires browser storage or an explicit browser proof"
614
+ );
615
+ target.setItem(
616
+ key,
617
+ JSON.stringify({
618
+ binding,
619
+ expiresAt: now() + SSO_LINK_PROOF_BINDING_TTL_MS
620
+ })
621
+ );
622
+ return binding;
623
+ },
624
+ save(_linkProofBinding) {
625
+ },
626
+ read() {
627
+ let raw = null;
628
+ try {
629
+ raw = storage()?.getItem(key) ?? null;
630
+ } catch {
631
+ return null;
632
+ }
633
+ if (!raw) return null;
634
+ try {
635
+ const parsed = JSON.parse(raw);
636
+ if (typeof parsed.binding === "string" && typeof parsed.expiresAt === "number" && parsed.expiresAt > now()) {
637
+ return parsed.binding;
638
+ }
639
+ } catch {
640
+ }
641
+ clear();
642
+ return null;
643
+ },
644
+ clear
645
+ };
646
+ }
647
+
473
648
  // src/namespaces/auth.ts
474
- function authNamespace(client) {
649
+ function authNamespace(client, board = "") {
650
+ const ssoProof = () => ssoLinkProofBindingStore(board);
475
651
  async function persist(session) {
476
652
  await writeSession(client.storage, session);
477
653
  return session;
@@ -717,7 +893,76 @@ function authNamespace(client) {
717
893
  {
718
894
  ...options,
719
895
  method: "POST",
720
- body
896
+ body: {
897
+ ...body,
898
+ browserBinding: body.browserBinding ?? ssoProof().read() ?? void 0
899
+ }
900
+ }
901
+ );
902
+ return persist(session);
903
+ },
904
+ /**
905
+ * Build the authorization URL for one of the board's SSO connections
906
+ * (ids from `board.context()` → `signIn.<role>.ssoConnections`). The SDK
907
+ * returns the URL; the host app owns browser navigation. The provider
908
+ * round trip completes on your `/auth/oauth-complete` page — read it
909
+ * with `parseOAuthCompletion`.
910
+ *
911
+ * @example
912
+ * const { authorizeUrl } = await board.auth.getSsoAuthorizationUrl(
913
+ * connection.id,
914
+ * { role: 'candidate', returnTo: '/account' },
915
+ * );
916
+ * window.location.href = authorizeUrl;
917
+ */
918
+ async getSsoAuthorizationUrl(connectionId, query, options) {
919
+ let browserBindingHash = query?.browserBindingHash;
920
+ if (!browserBindingHash) {
921
+ const secret = ssoProof().begin();
922
+ const digest = await globalThis.crypto.subtle.digest(
923
+ "SHA-256",
924
+ new TextEncoder().encode(secret)
925
+ );
926
+ browserBindingHash = Array.from(
927
+ new Uint8Array(digest),
928
+ (byte) => byte.toString(16).padStart(2, "0")
929
+ ).join("");
930
+ }
931
+ return client.fetch(
932
+ `/auth/sso/${encodeURIComponent(connectionId)}`,
933
+ {
934
+ ...options,
935
+ method: "GET",
936
+ query: { ...query, browserBindingHash }
937
+ }
938
+ );
939
+ },
940
+ /**
941
+ * Finish an SSO sign-in that had to prove the inbox first: send the
942
+ * emailed `linkProof` token with the `linkProofBinding` this browser
943
+ * kept when the sign-in started (`ssoLinkProofBindingStore`). Persists
944
+ * the returned bearer pair. Opened in another browser, it fails with
945
+ * `board_auth_sso_browser_mismatch` (`isSsoBrowserMismatch`) and the
946
+ * link stays usable where the sign-in started.
947
+ *
948
+ * @example
949
+ * const bindings = ssoLinkProofBindingStore(boardKey);
950
+ * await board.auth.consumeSsoLinkProof({
951
+ * token: completion.linkProof,
952
+ * browserBinding: bindings.read() ?? undefined,
953
+ * });
954
+ * bindings.clear();
955
+ */
956
+ async consumeSsoLinkProof(body, options) {
957
+ const session = await client.fetch(
958
+ "/auth/sso/link-proof",
959
+ {
960
+ ...options,
961
+ method: "POST",
962
+ body: {
963
+ ...body,
964
+ browserBinding: body.browserBinding ?? ssoProof().read() ?? void 0
965
+ }
721
966
  }
722
967
  );
723
968
  return persist(session);
@@ -3699,7 +3944,7 @@ function createBoardClient(options) {
3699
3944
  embed: embedNamespace(client),
3700
3945
  companies: companiesNamespace(client),
3701
3946
  blog: blogNamespace(client),
3702
- auth: authNamespace(client),
3947
+ auth: authNamespace(client, options.board),
3703
3948
  me: meNamespace(client),
3704
3949
  password: passwordNamespace(client),
3705
3950
  taxonomy: taxonomyNamespace(client),
package/dist/index.mjs CHANGED
@@ -4,6 +4,7 @@ import {
4
4
  REFRESH_TOKEN_KEY,
5
5
  clearSession,
6
6
  resolveStorage,
7
+ scopeToken,
7
8
  writeSession
8
9
  } from "./chunk-Z4ALWIGS.mjs";
9
10
  import {
@@ -19,9 +20,12 @@ import {
19
20
  isFreeEmailWebsiteError,
20
21
  isNotFound,
21
22
  isRateLimited,
23
+ isSignInMethodUnavailable,
24
+ isSsoBrowserMismatch,
25
+ isSsoRequired,
22
26
  isUnauthorized,
23
27
  isValidationError
24
- } from "./chunk-EHSB2VAJ.mjs";
28
+ } from "./chunk-WSO6JN5C.mjs";
25
29
 
26
30
  // src/query.ts
27
31
  function toSearchParams(query) {
@@ -42,7 +46,7 @@ function toSearchParams(query) {
42
46
  }
43
47
 
44
48
  // src/version.ts
45
- var SDK_VERSION = "4.30.0";
49
+ var SDK_VERSION = "4.31.1";
46
50
 
47
51
  // src/client.ts
48
52
  function isRawBody(body) {
@@ -149,8 +153,144 @@ var BoardClient = class {
149
153
  // src/constants.ts
150
154
  var DEFAULT_CAVUNO_API_URL = "https://api.cavuno.com";
151
155
 
156
+ // src/sso.ts
157
+ var SIGN_IN_REDIRECT_ERROR_CODES = [
158
+ "oauth_cancelled",
159
+ "oauth_failed",
160
+ "oauth_email_unverified",
161
+ "role_disabled",
162
+ "method_unavailable",
163
+ // Deprecated: retained for boards served by earlier API releases.
164
+ "sso_required",
165
+ "sso_state_invalid",
166
+ "sso_cancelled",
167
+ "sso_failed",
168
+ "sso_connection_unavailable",
169
+ "sso_not_provisioned",
170
+ "sso_email_required",
171
+ "sso_account_disabled",
172
+ "sso_identity_linked_elsewhere",
173
+ "sso_link_proof_rate_limited",
174
+ "sso_development_origin_not_allowed_for_email"
175
+ ];
176
+ function toSearchParams2(input) {
177
+ if (input instanceof URLSearchParams) return input;
178
+ if (typeof input === "string") {
179
+ const query = input.includes("?") ? input.slice(input.indexOf("?")) : input;
180
+ return new URLSearchParams(query);
181
+ }
182
+ const params = new URLSearchParams();
183
+ for (const [key, value] of Object.entries(input)) {
184
+ if (value !== void 0) params.set(key, value);
185
+ }
186
+ return params;
187
+ }
188
+ function safeReturnTo(value) {
189
+ if (!value || !value.startsWith("/") || value.startsWith("//") || value.startsWith("/\\") || value.includes("://") || /[\u0000-\u001F\u007F]/.test(value)) {
190
+ return "/";
191
+ }
192
+ return value;
193
+ }
194
+ function roleOf(value) {
195
+ return value === "candidate" || value === "employer" ? value : null;
196
+ }
197
+ function parseOAuthCompletion(input) {
198
+ const params = toSearchParams2(input);
199
+ const get = (key) => {
200
+ const value = params.get(key);
201
+ return value && value.length > 0 ? value : null;
202
+ };
203
+ const returnTo = safeReturnTo(get("returnTo"));
204
+ const role = roleOf(get("role"));
205
+ const error = get("error");
206
+ if (error) return { kind: "error", error, returnTo };
207
+ const linkProof = get("linkProof");
208
+ if (linkProof) return { kind: "link_proof", linkProof, role, returnTo };
209
+ const linkProofBinding = get("linkProofBinding");
210
+ if (get("status") === "sso_link_proof_sent" && linkProofBinding) {
211
+ return { kind: "link_proof_sent", linkProofBinding, role, returnTo };
212
+ }
213
+ const token = get("token");
214
+ if (token) {
215
+ return {
216
+ kind: "token",
217
+ token,
218
+ method: get("method"),
219
+ role,
220
+ returnTo,
221
+ isNewUser: get("isNew") === "1"
222
+ };
223
+ }
224
+ return { kind: "invalid", returnTo };
225
+ }
226
+ var SSO_LINK_PROOF_BINDING_TTL_MS = 25 * 60 * 1e3;
227
+ var BINDING_KEY = "cavuno_board_sso_link_proof_binding";
228
+ function defaultStorage() {
229
+ try {
230
+ const storage = globalThis.localStorage;
231
+ return storage ?? null;
232
+ } catch {
233
+ return null;
234
+ }
235
+ }
236
+ function ssoLinkProofBindingStore(board, options = {}) {
237
+ const key = `${BINDING_KEY}:${scopeToken(board)}`;
238
+ const now = options.now ?? Date.now;
239
+ const storage = () => options.storage ?? defaultStorage();
240
+ const clear = () => {
241
+ try {
242
+ storage()?.removeItem(key);
243
+ } catch {
244
+ }
245
+ };
246
+ return {
247
+ begin() {
248
+ const bytes = globalThis.crypto.getRandomValues(new Uint8Array(32));
249
+ const binding = Array.from(
250
+ bytes,
251
+ (value) => value.toString(16).padStart(2, "0")
252
+ ).join("");
253
+ const target = storage();
254
+ if (!target)
255
+ throw new Error(
256
+ "SSO requires browser storage or an explicit browser proof"
257
+ );
258
+ target.setItem(
259
+ key,
260
+ JSON.stringify({
261
+ binding,
262
+ expiresAt: now() + SSO_LINK_PROOF_BINDING_TTL_MS
263
+ })
264
+ );
265
+ return binding;
266
+ },
267
+ save(_linkProofBinding) {
268
+ },
269
+ read() {
270
+ let raw = null;
271
+ try {
272
+ raw = storage()?.getItem(key) ?? null;
273
+ } catch {
274
+ return null;
275
+ }
276
+ if (!raw) return null;
277
+ try {
278
+ const parsed = JSON.parse(raw);
279
+ if (typeof parsed.binding === "string" && typeof parsed.expiresAt === "number" && parsed.expiresAt > now()) {
280
+ return parsed.binding;
281
+ }
282
+ } catch {
283
+ }
284
+ clear();
285
+ return null;
286
+ },
287
+ clear
288
+ };
289
+ }
290
+
152
291
  // src/namespaces/auth.ts
153
- function authNamespace(client) {
292
+ function authNamespace(client, board = "") {
293
+ const ssoProof = () => ssoLinkProofBindingStore(board);
154
294
  async function persist(session) {
155
295
  await writeSession(client.storage, session);
156
296
  return session;
@@ -396,7 +536,76 @@ function authNamespace(client) {
396
536
  {
397
537
  ...options,
398
538
  method: "POST",
399
- body
539
+ body: {
540
+ ...body,
541
+ browserBinding: body.browserBinding ?? ssoProof().read() ?? void 0
542
+ }
543
+ }
544
+ );
545
+ return persist(session);
546
+ },
547
+ /**
548
+ * Build the authorization URL for one of the board's SSO connections
549
+ * (ids from `board.context()` → `signIn.<role>.ssoConnections`). The SDK
550
+ * returns the URL; the host app owns browser navigation. The provider
551
+ * round trip completes on your `/auth/oauth-complete` page — read it
552
+ * with `parseOAuthCompletion`.
553
+ *
554
+ * @example
555
+ * const { authorizeUrl } = await board.auth.getSsoAuthorizationUrl(
556
+ * connection.id,
557
+ * { role: 'candidate', returnTo: '/account' },
558
+ * );
559
+ * window.location.href = authorizeUrl;
560
+ */
561
+ async getSsoAuthorizationUrl(connectionId, query, options) {
562
+ let browserBindingHash = query?.browserBindingHash;
563
+ if (!browserBindingHash) {
564
+ const secret = ssoProof().begin();
565
+ const digest = await globalThis.crypto.subtle.digest(
566
+ "SHA-256",
567
+ new TextEncoder().encode(secret)
568
+ );
569
+ browserBindingHash = Array.from(
570
+ new Uint8Array(digest),
571
+ (byte) => byte.toString(16).padStart(2, "0")
572
+ ).join("");
573
+ }
574
+ return client.fetch(
575
+ `/auth/sso/${encodeURIComponent(connectionId)}`,
576
+ {
577
+ ...options,
578
+ method: "GET",
579
+ query: { ...query, browserBindingHash }
580
+ }
581
+ );
582
+ },
583
+ /**
584
+ * Finish an SSO sign-in that had to prove the inbox first: send the
585
+ * emailed `linkProof` token with the `linkProofBinding` this browser
586
+ * kept when the sign-in started (`ssoLinkProofBindingStore`). Persists
587
+ * the returned bearer pair. Opened in another browser, it fails with
588
+ * `board_auth_sso_browser_mismatch` (`isSsoBrowserMismatch`) and the
589
+ * link stays usable where the sign-in started.
590
+ *
591
+ * @example
592
+ * const bindings = ssoLinkProofBindingStore(boardKey);
593
+ * await board.auth.consumeSsoLinkProof({
594
+ * token: completion.linkProof,
595
+ * browserBinding: bindings.read() ?? undefined,
596
+ * });
597
+ * bindings.clear();
598
+ */
599
+ async consumeSsoLinkProof(body, options) {
600
+ const session = await client.fetch(
601
+ "/auth/sso/link-proof",
602
+ {
603
+ ...options,
604
+ method: "POST",
605
+ body: {
606
+ ...body,
607
+ browserBinding: body.browserBinding ?? ssoProof().read() ?? void 0
608
+ }
400
609
  }
401
610
  );
402
611
  return persist(session);
@@ -3348,7 +3557,7 @@ function createBoardClient(options) {
3348
3557
  embed: embedNamespace(client),
3349
3558
  companies: companiesNamespace(client),
3350
3559
  blog: blogNamespace(client),
3351
- auth: authNamespace(client),
3560
+ auth: authNamespace(client, options.board),
3352
3561
  me: meNamespace(client),
3353
3562
  password: passwordNamespace(client),
3354
3563
  taxonomy: taxonomyNamespace(client),
@@ -3373,6 +3582,8 @@ export {
3373
3582
  BoardClient,
3374
3583
  REFRESH_TOKEN_KEY,
3375
3584
  SDK_VERSION,
3585
+ SIGN_IN_REDIRECT_ERROR_CODES,
3586
+ SSO_LINK_PROOF_BINDING_TTL_MS,
3376
3587
  createBoardClient,
3377
3588
  isBoardApiError,
3378
3589
  isBoardPasswordRequired,
@@ -3384,10 +3595,15 @@ export {
3384
3595
  isOwnMessage,
3385
3596
  isRateLimited,
3386
3597
  isSafeApplicationUrl,
3598
+ isSignInMethodUnavailable,
3599
+ isSsoBrowserMismatch,
3600
+ isSsoRequired,
3387
3601
  isUnauthorized,
3388
3602
  isValidationError,
3389
3603
  lastOwnMessageId,
3390
3604
  paginate,
3605
+ parseOAuthCompletion,
3391
3606
  resolveApplyAction,
3392
- resolveApplyDecision
3607
+ resolveApplyDecision,
3608
+ ssoLinkProofBindingStore
3393
3609
  };
@@ -1,4 +1,4 @@
1
- import { S as Schemas } from './_spec-CuSpfit_.mjs';
1
+ import { S as Schemas } from './_spec-BD5My4zL.mjs';
2
2
 
3
3
  /**
4
4
  * Optional pagination metadata returned by Board API list operations.
@@ -1,4 +1,4 @@
1
- import { S as Schemas } from './_spec-CuSpfit_.js';
1
+ import { S as Schemas } from './_spec-BD5My4zL.js';
2
2
 
3
3
  /**
4
4
  * Optional pagination metadata returned by Board API list operations.
@@ -1,4 +1,4 @@
1
- import { S as Schemas } from './_spec-CuSpfit_.mjs';
1
+ import { S as Schemas } from './_spec-BD5My4zL.mjs';
2
2
 
3
3
  type Awaitable<T> = T | Promise<T>;
4
4
  /**
@@ -1,4 +1,4 @@
1
- import { S as Schemas } from './_spec-CuSpfit_.js';
1
+ import { S as Schemas } from './_spec-BD5My4zL.js';
2
2
 
3
3
  type Awaitable<T> = T | Promise<T>;
4
4
  /**
@@ -1,4 +1,4 @@
1
- import { S as Schemas } from './_spec-CuSpfit_.mjs';
1
+ import { S as Schemas } from './_spec-BD5My4zL.mjs';
2
2
 
3
3
  /**
4
4
  * The public SEO-infra payload (`board.seo()`) — platform infrastructure a
@@ -1,4 +1,4 @@
1
- import { S as Schemas } from './_spec-CuSpfit_.js';
1
+ import { S as Schemas } from './_spec-BD5My4zL.js';
2
2
 
3
3
  /**
4
4
  * The public SEO-infra payload (`board.seo()`) — platform infrastructure a
@@ -1,5 +1,5 @@
1
- import { B as BoardSeo } from './seo-BFQIA81t.mjs';
2
- import './_spec-CuSpfit_.mjs';
1
+ import { B as BoardSeo } from './seo-BypmVm97.mjs';
2
+ import './_spec-BD5My4zL.mjs';
3
3
 
4
4
  /**
5
5
  * `@cavuno/board/seo-files` — platform-owned crawler/identity file bodies
@@ -1,5 +1,5 @@
1
- import { B as BoardSeo } from './seo-BPDNxOiZ.js';
2
- import './_spec-CuSpfit_.js';
1
+ import { B as BoardSeo } from './seo-C-NQ0T9w.js';
2
+ import './_spec-BD5My4zL.js';
3
3
 
4
4
  /**
5
5
  * `@cavuno/board/seo-files` — platform-owned crawler/identity file bodies
package/dist/seo.d.mts CHANGED
@@ -1,9 +1,9 @@
1
- import { P as PublicBlogPostSummary, B as BlogAuthorEmbed } from './blog-21XnewAu.mjs';
2
- import { e as JsonLdObject } from './breadcrumbs-BpONykkn.mjs';
3
- export { A as ALL_COUNTRY_CODES, B as BreadcrumbItemInput, g as FaqItem, J as JobBreadcrumb, h as JsonLdBoard, N as NumberNotation, i as SENIORITY_ORDER, j as SalaryFaqEntry, k as SalaryJsonLdOptions, l as SalaryOccupationListOptions, b as buildJobBreadcrumbs, m as buildSalaryFaq, o as companyCategorySalaryJsonLd, p as companySalaryJsonLd, q as createBreadcrumbJsonLd, r as createJobPostingJsonLd, s as crossAxisSalaryJsonLd, t as faqJsonLd, c as formatSalaryStat, d as formatSalaryStatRange, u as itemListJsonLd, v as locationSalaryJsonLd, n as normalizeWebsiteUrl, w as skillSalaryJsonLd, x as sortBySeniority, y as titleSalaryJsonLd } from './breadcrumbs-BpONykkn.mjs';
4
- import './_spec-CuSpfit_.mjs';
5
- import './board-VrjkKuZ-.mjs';
6
- import './jobs-h-ewgbXn.mjs';
1
+ import { P as PublicBlogPostSummary, B as BlogAuthorEmbed } from './blog-Dh-Ru5QZ.mjs';
2
+ import { e as JsonLdObject } from './breadcrumbs-AEupw3fi.mjs';
3
+ export { A as ALL_COUNTRY_CODES, B as BreadcrumbItemInput, g as FaqItem, J as JobBreadcrumb, h as JsonLdBoard, N as NumberNotation, i as SENIORITY_ORDER, j as SalaryFaqEntry, k as SalaryJsonLdOptions, l as SalaryOccupationListOptions, b as buildJobBreadcrumbs, m as buildSalaryFaq, o as companyCategorySalaryJsonLd, p as companySalaryJsonLd, q as createBreadcrumbJsonLd, r as createJobPostingJsonLd, s as crossAxisSalaryJsonLd, t as faqJsonLd, c as formatSalaryStat, d as formatSalaryStatRange, u as itemListJsonLd, v as locationSalaryJsonLd, n as normalizeWebsiteUrl, w as skillSalaryJsonLd, x as sortBySeniority, y as titleSalaryJsonLd } from './breadcrumbs-AEupw3fi.mjs';
4
+ import './_spec-BD5My4zL.mjs';
5
+ import './board-BRWS1m6D.mjs';
6
+ import './jobs-BPTMfkal.mjs';
7
7
 
8
8
  /**
9
9
  * Blog structured data on the `@cavuno/board` wire types, transcribed from