@cavuno/board 4.30.0 → 4.31.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/CHANGELOG.md +17 -0
- package/dist/{_spec-CuSpfit_.d.mts → _spec-BD5My4zL.d.mts} +76 -0
- package/dist/{_spec-CuSpfit_.d.ts → _spec-BD5My4zL.d.ts} +76 -0
- package/dist/{blog-CvQeoBc8.d.ts → blog-CcLmN5Tw.d.ts} +1 -1
- package/dist/{blog-21XnewAu.d.mts → blog-Dh-Ru5QZ.d.mts} +1 -1
- package/dist/{board-t_j16FrX.d.ts → board-BRWS1m6D.d.mts} +16 -3
- package/dist/{board-VrjkKuZ-.d.mts → board-CF66EdYi.d.ts} +16 -3
- package/dist/{breadcrumbs-BpONykkn.d.mts → breadcrumbs-AEupw3fi.d.mts} +2 -2
- package/dist/{breadcrumbs-CJ5eu_ye.d.ts → breadcrumbs-Bu1lN6qA.d.ts} +2 -2
- package/dist/browser/cavuno-board.global.min.js +1 -1
- package/dist/{chunk-EHSB2VAJ.mjs → chunk-WSO6JN5C.mjs} +37 -1
- package/dist/filters.d.mts +2 -2
- package/dist/filters.d.ts +2 -2
- package/dist/format.d.mts +4 -4
- package/dist/format.d.ts +4 -4
- package/dist/index.d.mts +238 -13
- package/dist/index.d.ts +238 -13
- package/dist/index.js +250 -5
- package/dist/index.mjs +222 -6
- package/dist/{jobs-h-ewgbXn.d.mts → jobs-BPTMfkal.d.mts} +1 -1
- package/dist/{jobs-CErce8Tw.d.ts → jobs-BbtjrUo2.d.ts} +1 -1
- package/dist/{search-BefsJD_f.d.mts → search-CIjAdPR-.d.mts} +1 -1
- package/dist/{search-B2LdM6bj.d.ts → search-CqPIHkv8.d.ts} +1 -1
- package/dist/{seo-BFQIA81t.d.mts → seo-BypmVm97.d.mts} +1 -1
- package/dist/{seo-BPDNxOiZ.d.ts → seo-C-NQ0T9w.d.ts} +1 -1
- package/dist/seo-files.d.mts +2 -2
- package/dist/seo-files.d.ts +2 -2
- package/dist/seo.d.mts +6 -6
- package/dist/seo.d.ts +6 -6
- package/dist/server.d.mts +6 -6
- package/dist/server.d.ts +6 -6
- package/dist/server.mjs +1 -1
- package/dist/sitemap.d.mts +6 -6
- package/dist/sitemap.d.ts +6 -6
- package/dist/sitemap.mjs +1 -1
- package/dist/suggest.d.mts +2 -2
- package/dist/suggest.d.ts +2 -2
- package/package.json +1 -1
- package/skills/manifest.json +1 -1
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.
|
|
406
|
+
var SDK_VERSION = "4.31.0";
|
|
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-
|
|
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.
|
|
49
|
+
var SDK_VERSION = "4.31.0";
|
|
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
|
};
|
package/dist/seo-files.d.mts
CHANGED
package/dist/seo-files.d.ts
CHANGED
package/dist/seo.d.mts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { P as PublicBlogPostSummary, B as BlogAuthorEmbed } from './blog-
|
|
2
|
-
import { e as JsonLdObject } from './breadcrumbs-
|
|
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-
|
|
4
|
-
import './_spec-
|
|
5
|
-
import './board-
|
|
6
|
-
import './jobs-
|
|
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
|