@finueva/auth 0.5.0 → 0.7.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/README.md +50 -4
- package/THIRD_PARTY_NOTICES.md +1 -1
- package/dist/device.d.ts +8 -2
- package/dist/device.js +616 -101
- package/dist/index.d.ts +2 -3
- package/dist/index.js +323 -102
- package/dist/server.d.ts +6 -3
- package/dist/server.js +127 -35
- package/dist/{types-HSdjQWMZ.d.ts → types-B1SZbs6Z.d.ts} +28 -1
- package/package.json +3 -2
- package/dist/errors-Crd93Rah.d.ts +0 -11
package/dist/device.js
CHANGED
|
@@ -1,45 +1,3 @@
|
|
|
1
|
-
//#region ../../node_modules/.pnpm/destr@2.0.5/node_modules/destr/dist/index.mjs
|
|
2
|
-
const suspectProtoRx = /"(?:_|\\u0{2}5[Ff]){2}(?:p|\\u0{2}70)(?:r|\\u0{2}72)(?:o|\\u0{2}6[Ff])(?:t|\\u0{2}74)(?:o|\\u0{2}6[Ff])(?:_|\\u0{2}5[Ff]){2}"\s*:/;
|
|
3
|
-
const suspectConstructorRx = /"(?:c|\\u0063)(?:o|\\u006[Ff])(?:n|\\u006[Ee])(?:s|\\u0073)(?:t|\\u0074)(?:r|\\u0072)(?:u|\\u0075)(?:c|\\u0063)(?:t|\\u0074)(?:o|\\u006[Ff])(?:r|\\u0072)"\s*:/;
|
|
4
|
-
const JsonSigRx = /^\s*["[{]|^\s*-?\d{1,16}(\.\d{1,17})?([Ee][+-]?\d+)?\s*$/;
|
|
5
|
-
function jsonParseTransform(key, value) {
|
|
6
|
-
if (key === "__proto__" || key === "constructor" && value && typeof value === "object" && "prototype" in value) {
|
|
7
|
-
warnKeyDropped(key);
|
|
8
|
-
return;
|
|
9
|
-
}
|
|
10
|
-
return value;
|
|
11
|
-
}
|
|
12
|
-
function warnKeyDropped(key) {
|
|
13
|
-
console.warn(`[destr] Dropping "${key}" key to prevent prototype pollution.`);
|
|
14
|
-
}
|
|
15
|
-
function destr(value, options = {}) {
|
|
16
|
-
if (typeof value !== "string") return value;
|
|
17
|
-
if (value[0] === "\"" && value[value.length - 1] === "\"" && value.indexOf("\\") === -1) return value.slice(1, -1);
|
|
18
|
-
const _value = value.trim();
|
|
19
|
-
if (_value.length <= 9) switch (_value.toLowerCase()) {
|
|
20
|
-
case "true": return true;
|
|
21
|
-
case "false": return false;
|
|
22
|
-
case "undefined": return;
|
|
23
|
-
case "null": return null;
|
|
24
|
-
case "nan": return NaN;
|
|
25
|
-
case "infinity": return Number.POSITIVE_INFINITY;
|
|
26
|
-
case "-infinity": return Number.NEGATIVE_INFINITY;
|
|
27
|
-
}
|
|
28
|
-
if (!JsonSigRx.test(value)) {
|
|
29
|
-
if (options.strict) throw new SyntaxError("[destr] Invalid JSON");
|
|
30
|
-
return value;
|
|
31
|
-
}
|
|
32
|
-
try {
|
|
33
|
-
if (suspectProtoRx.test(value) || suspectConstructorRx.test(value)) {
|
|
34
|
-
if (options.strict) throw new Error("[destr] Possible prototype pollution");
|
|
35
|
-
return JSON.parse(value, jsonParseTransform);
|
|
36
|
-
}
|
|
37
|
-
return JSON.parse(value);
|
|
38
|
-
} catch (error) {
|
|
39
|
-
if (options.strict) throw error;
|
|
40
|
-
return value;
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
1
|
String.fromCharCode;
|
|
44
2
|
const HASH_RE = /#/g;
|
|
45
3
|
const AMPERSAND_RE = /&/g;
|
|
@@ -215,6 +173,49 @@ function stringifyParsedURL(parsed) {
|
|
|
215
173
|
return (parsed.protocol || parsed[protocolRelative] ? (parsed.protocol || "") + "//" : "") + auth + host + pathname + search + hash;
|
|
216
174
|
}
|
|
217
175
|
//#endregion
|
|
176
|
+
//#region ../../node_modules/.pnpm/destr@2.0.5/node_modules/destr/dist/index.mjs
|
|
177
|
+
const suspectProtoRx = /"(?:_|\\u0{2}5[Ff]){2}(?:p|\\u0{2}70)(?:r|\\u0{2}72)(?:o|\\u0{2}6[Ff])(?:t|\\u0{2}74)(?:o|\\u0{2}6[Ff])(?:_|\\u0{2}5[Ff]){2}"\s*:/;
|
|
178
|
+
const suspectConstructorRx = /"(?:c|\\u0063)(?:o|\\u006[Ff])(?:n|\\u006[Ee])(?:s|\\u0073)(?:t|\\u0074)(?:r|\\u0072)(?:u|\\u0075)(?:c|\\u0063)(?:t|\\u0074)(?:o|\\u006[Ff])(?:r|\\u0072)"\s*:/;
|
|
179
|
+
const JsonSigRx = /^\s*["[{]|^\s*-?\d{1,16}(\.\d{1,17})?([Ee][+-]?\d+)?\s*$/;
|
|
180
|
+
function jsonParseTransform(key, value) {
|
|
181
|
+
if (key === "__proto__" || key === "constructor" && value && typeof value === "object" && "prototype" in value) {
|
|
182
|
+
warnKeyDropped(key);
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
return value;
|
|
186
|
+
}
|
|
187
|
+
function warnKeyDropped(key) {
|
|
188
|
+
console.warn(`[destr] Dropping "${key}" key to prevent prototype pollution.`);
|
|
189
|
+
}
|
|
190
|
+
function destr(value, options = {}) {
|
|
191
|
+
if (typeof value !== "string") return value;
|
|
192
|
+
if (value[0] === "\"" && value[value.length - 1] === "\"" && value.indexOf("\\") === -1) return value.slice(1, -1);
|
|
193
|
+
const _value = value.trim();
|
|
194
|
+
if (_value.length <= 9) switch (_value.toLowerCase()) {
|
|
195
|
+
case "true": return true;
|
|
196
|
+
case "false": return false;
|
|
197
|
+
case "undefined": return;
|
|
198
|
+
case "null": return null;
|
|
199
|
+
case "nan": return NaN;
|
|
200
|
+
case "infinity": return Number.POSITIVE_INFINITY;
|
|
201
|
+
case "-infinity": return Number.NEGATIVE_INFINITY;
|
|
202
|
+
}
|
|
203
|
+
if (!JsonSigRx.test(value)) {
|
|
204
|
+
if (options.strict) throw new SyntaxError("[destr] Invalid JSON");
|
|
205
|
+
return value;
|
|
206
|
+
}
|
|
207
|
+
try {
|
|
208
|
+
if (suspectProtoRx.test(value) || suspectConstructorRx.test(value)) {
|
|
209
|
+
if (options.strict) throw new Error("[destr] Possible prototype pollution");
|
|
210
|
+
return JSON.parse(value, jsonParseTransform);
|
|
211
|
+
}
|
|
212
|
+
return JSON.parse(value);
|
|
213
|
+
} catch (error) {
|
|
214
|
+
if (options.strict) throw error;
|
|
215
|
+
return value;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
//#endregion
|
|
218
219
|
//#region ../../node_modules/.pnpm/ofetch@1.5.1/node_modules/ofetch/dist/shared/ofetch.CWycOUEr.mjs
|
|
219
220
|
var FetchError = class extends Error {
|
|
220
221
|
constructor(message, opts) {
|
|
@@ -438,6 +439,9 @@ function createFetch(globalOptions = {}) {
|
|
|
438
439
|
//#region src/errors.ts
|
|
439
440
|
const messages = {
|
|
440
441
|
invalid_credentials: "The credentials are invalid.",
|
|
442
|
+
invalid_auth_input: "The authentication input is invalid.",
|
|
443
|
+
account_conflict: "The account is not available.",
|
|
444
|
+
password_reset_invalid: "The password reset request is invalid.",
|
|
441
445
|
authentication_required: "Authentication is required.",
|
|
442
446
|
request_origin_denied: "The request origin is not allowed.",
|
|
443
447
|
invalid_invitation: "The invitation is invalid.",
|
|
@@ -447,6 +451,7 @@ const messages = {
|
|
|
447
451
|
invalid_workspace_selection: "The workspace selection is invalid.",
|
|
448
452
|
organization_context_too_large: "The organization context is too large.",
|
|
449
453
|
credential_rate_limited: "Credential requests are temporarily limited.",
|
|
454
|
+
auth_request_rate_limited: "Authentication requests are temporarily limited.",
|
|
450
455
|
state_conflict: "The authentication state conflicts with the request.",
|
|
451
456
|
access_denied: "Access is denied.",
|
|
452
457
|
identity_authority_unavailable: "Authentication is temporarily unavailable."
|
|
@@ -465,8 +470,56 @@ var AuthError = class extends Error {
|
|
|
465
470
|
};
|
|
466
471
|
//#endregion
|
|
467
472
|
//#region src/shared.ts
|
|
473
|
+
const DRIVE_CREDENTIAL_LIFETIME_SECONDS = 840;
|
|
468
474
|
const id = /^[A-Za-z0-9_-]{1,128}$/;
|
|
469
475
|
const email = /^[^\s@]+@[^\s@]+$/;
|
|
476
|
+
const MAX_SOCIAL_ID_TOKEN_BYTES = 16 * 1024;
|
|
477
|
+
function normalizeEmail(value) {
|
|
478
|
+
if (typeof value !== "string") throw new TypeError("email is invalid");
|
|
479
|
+
const normalized = value.trim().toLowerCase();
|
|
480
|
+
if (normalized.length === 0 || normalized.length > 320 || !email.test(normalized)) throw new TypeError("email is invalid");
|
|
481
|
+
return normalized;
|
|
482
|
+
}
|
|
483
|
+
function normalizePassword(value, name = "password", minLength = 8) {
|
|
484
|
+
if (typeof value !== "string" || value.length < minLength || value.length > 128) throw new TypeError(`${name} is invalid`);
|
|
485
|
+
return value;
|
|
486
|
+
}
|
|
487
|
+
function normalizeSocialIdTokenInput(value) {
|
|
488
|
+
if (!isObject(value)) throw new TypeError("social ID token input is invalid");
|
|
489
|
+
const keys = Object.keys(value);
|
|
490
|
+
if (keys.length !== 3 && keys.length !== 4 || keys.some((key) => key !== "provider" && key !== "idToken" && key !== "nonce" && key !== "signUp") || value.provider !== "google" && value.provider !== "apple" || typeof value.signUp !== "boolean" || typeof value.idToken !== "string" || value.idToken.length < 1 || new TextEncoder().encode(value.idToken).byteLength > MAX_SOCIAL_ID_TOKEN_BYTES || !/^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/.test(value.idToken) || value.nonce !== void 0 && (typeof value.nonce !== "string" || value.nonce.length < 1 || value.nonce.length > 256 || !/^[\x20-\x7e]+$/.test(value.nonce))) throw new TypeError("social ID token input is invalid");
|
|
491
|
+
return {
|
|
492
|
+
provider: value.provider,
|
|
493
|
+
idToken: value.idToken,
|
|
494
|
+
...value.nonce === void 0 ? {} : { nonce: value.nonce },
|
|
495
|
+
signUp: value.signUp
|
|
496
|
+
};
|
|
497
|
+
}
|
|
498
|
+
function socialIdTokenRequest(value) {
|
|
499
|
+
return {
|
|
500
|
+
provider: value.provider,
|
|
501
|
+
idToken: {
|
|
502
|
+
token: value.idToken,
|
|
503
|
+
...value.nonce === void 0 ? {} : { nonce: value.nonce }
|
|
504
|
+
},
|
|
505
|
+
requestSignUp: value.signUp
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
function normalizeName(value) {
|
|
509
|
+
if (typeof value !== "string") throw new TypeError("name is invalid");
|
|
510
|
+
const normalized = value.trim();
|
|
511
|
+
if (normalized.length === 0 || normalized.length > 256) throw new TypeError("name is invalid");
|
|
512
|
+
return normalized;
|
|
513
|
+
}
|
|
514
|
+
function normalizeProfileInput(value) {
|
|
515
|
+
if (!isObject(value)) throw new TypeError("profile input is invalid");
|
|
516
|
+
const keys = Object.keys(value);
|
|
517
|
+
if (keys.length === 0 || keys.length > 2 || keys.some((key) => key !== "name" && key !== "image") || value.name === void 0 && value.image === void 0) throw new TypeError("profile input is invalid");
|
|
518
|
+
const result = {};
|
|
519
|
+
if (value.name !== void 0) result.name = normalizeName(value.name);
|
|
520
|
+
if (value.image !== void 0) result.image = value.image === null ? null : normalizeProfileImage(value.image);
|
|
521
|
+
return result;
|
|
522
|
+
}
|
|
470
523
|
function normalizeOrigin(value, name) {
|
|
471
524
|
if (value.length === 0 || value.length > 2048 || /[^\x20-\x7e]/.test(value)) throw new TypeError(`${name} must be an ASCII HTTP origin`);
|
|
472
525
|
let url;
|
|
@@ -502,10 +555,48 @@ function normalizeSession(value) {
|
|
|
502
555
|
}
|
|
503
556
|
};
|
|
504
557
|
}
|
|
558
|
+
function normalizeDriveCredential(value, now) {
|
|
559
|
+
if (!isObject(value) || value.tokenType !== "Bearer" || value.expiresIn !== DRIVE_CREDENTIAL_LIFETIME_SECONDS || typeof value.token !== "string" || value.token.length === 0 || new TextEncoder().encode(`Bearer ${value.token}`).byteLength > 16 * 1024) throw authorityUnavailable();
|
|
560
|
+
return {
|
|
561
|
+
tokenType: "Bearer",
|
|
562
|
+
token: value.token,
|
|
563
|
+
expiresAt: new Date(now + value.expiresIn * 1e3)
|
|
564
|
+
};
|
|
565
|
+
}
|
|
566
|
+
function normalizeWorkspaceSelection(value) {
|
|
567
|
+
if (!isObject(value)) throw new AuthError("invalid_workspace_selection", 422);
|
|
568
|
+
const keys = Object.keys(value);
|
|
569
|
+
if (value.type === "personal" && keys.length === 1 && keys[0] === "type") return { type: "personal" };
|
|
570
|
+
if (value.type === "organization" && keys.length === 2 && keys.includes("type") && keys.includes("organizationId") && typeof value.organizationId === "string" && id.test(value.organizationId)) return {
|
|
571
|
+
type: "organization",
|
|
572
|
+
organizationId: value.organizationId
|
|
573
|
+
};
|
|
574
|
+
throw new AuthError("invalid_workspace_selection", 422);
|
|
575
|
+
}
|
|
505
576
|
function fromAuthorityError(value, fallbackStatus, retryAfter) {
|
|
506
577
|
const mapped = mapError(isObject(value) && typeof value.code === "string" ? value.code : void 0, fallbackStatus);
|
|
507
578
|
return new AuthError(mapped.code, mapped.status, parseRetryAfter(retryAfter));
|
|
508
579
|
}
|
|
580
|
+
function fromAccountAuthorityError(value, status, retryAfter) {
|
|
581
|
+
const code = isObject(value) && typeof value.code === "string" ? value.code : void 0;
|
|
582
|
+
if (status === 403 && (code === "INVALID_ORIGIN" || code === "MISSING_OR_NULL_ORIGIN")) return new AuthError("request_origin_denied", 403);
|
|
583
|
+
if (code === "USER_ALREADY_EXISTS_USE_ANOTHER_EMAIL") return new AuthError("account_conflict", 409);
|
|
584
|
+
if (code === "INVALID_TOKEN") return new AuthError("password_reset_invalid", 422);
|
|
585
|
+
if (code === "INVALID_PASSWORD" || code === "INVALID_EMAIL_OR_PASSWORD") return new AuthError("invalid_credentials", 401);
|
|
586
|
+
if (code === "INVALID_EMAIL" || code === "PASSWORD_TOO_SHORT" || code === "PASSWORD_TOO_LONG" || code === "BODY_MUST_BE_AN_OBJECT" || code === "EMAIL_CAN_NOT_BE_UPDATED") return new AuthError("invalid_auth_input", 422);
|
|
587
|
+
if (status === 401) return new AuthError("authentication_required", 401);
|
|
588
|
+
if (status === 403) return new AuthError("access_denied", 403);
|
|
589
|
+
if (status === 409) return new AuthError("state_conflict", 409);
|
|
590
|
+
if (status === 429) return new AuthError("auth_request_rate_limited", 429, parseRetryAfter(retryAfter));
|
|
591
|
+
if (status === 400 || status === 422) return new AuthError("invalid_auth_input", 422);
|
|
592
|
+
return authorityUnavailable();
|
|
593
|
+
}
|
|
594
|
+
function fromSocialIdTokenAuthorityError(value, status, retryAfter) {
|
|
595
|
+
const code = isObject(value) && typeof value.code === "string" ? value.code : void 0;
|
|
596
|
+
if (code === "INVALID_TOKEN" || code === "FAILED_TO_GET_USER_INFO" || code === "USER_EMAIL_NOT_FOUND") return new AuthError("invalid_credentials", 401);
|
|
597
|
+
if (code === "OAUTH_LINK_ERROR") return new AuthError("account_conflict", 409);
|
|
598
|
+
return fromAccountAuthorityError(value, status, retryAfter);
|
|
599
|
+
}
|
|
509
600
|
function authorityUnavailable() {
|
|
510
601
|
return new AuthError("identity_authority_unavailable", 503);
|
|
511
602
|
}
|
|
@@ -568,6 +659,18 @@ function toDate(value) {
|
|
|
568
659
|
const result = value instanceof Date ? value : typeof value === "string" ? new Date(value) : void 0;
|
|
569
660
|
return result && Number.isFinite(result.getTime()) ? result : void 0;
|
|
570
661
|
}
|
|
662
|
+
function normalizeProfileImage(value) {
|
|
663
|
+
if (typeof value !== "string" || value.length === 0 || value.length > 2048 || /[^\x20-\x7e]/.test(value)) throw new TypeError("profile image is invalid");
|
|
664
|
+
let url;
|
|
665
|
+
try {
|
|
666
|
+
url = new URL(value);
|
|
667
|
+
} catch {
|
|
668
|
+
throw new TypeError("profile image is invalid");
|
|
669
|
+
}
|
|
670
|
+
const local = url.hostname === "localhost" || url.hostname.endsWith(".localhost") || url.hostname === "127.0.0.1";
|
|
671
|
+
if (url.protocol !== "https:" && !(local && url.protocol === "http:") || url.username || url.password || url.toString() !== value) throw new TypeError("profile image is invalid");
|
|
672
|
+
return value;
|
|
673
|
+
}
|
|
571
674
|
function isId(value) {
|
|
572
675
|
return typeof value === "string" && id.test(value);
|
|
573
676
|
}
|
|
@@ -584,7 +687,7 @@ async function requestJsonResponse(url, options) {
|
|
|
584
687
|
method: options.method,
|
|
585
688
|
headers: options.headers,
|
|
586
689
|
body: options.body,
|
|
587
|
-
credentials: "include",
|
|
690
|
+
credentials: options.credentials ?? "include",
|
|
588
691
|
retry: 0,
|
|
589
692
|
timeout: options.timeoutMs,
|
|
590
693
|
redirect: "manual",
|
|
@@ -646,8 +749,9 @@ async function readJson(response) {
|
|
|
646
749
|
//#region src/device.ts
|
|
647
750
|
const DEVICE_SCOPE = "openid";
|
|
648
751
|
const GRANT_TYPE = "urn:ietf:params:oauth:grant-type:device_code";
|
|
649
|
-
const MAX_TOKEN_BYTES = 16 * 1024;
|
|
752
|
+
const MAX_TOKEN_BYTES = 16 * 1024 - 7;
|
|
650
753
|
const MAX_POLLING_MS = 900 * 1e3;
|
|
754
|
+
const REFRESH_WINDOW_MS = 120 * 1e3;
|
|
651
755
|
const errorMessages = {
|
|
652
756
|
device_access_denied: "Device authorization was denied.",
|
|
653
757
|
device_authorization_cancelled: "Device authorization was cancelled.",
|
|
@@ -677,7 +781,30 @@ function createDeviceAuth(options) {
|
|
|
677
781
|
let token = null;
|
|
678
782
|
let hydrated = false;
|
|
679
783
|
let hydration = null;
|
|
680
|
-
|
|
784
|
+
let credential;
|
|
785
|
+
let credentialRequest;
|
|
786
|
+
let selectedWorkspaceKey;
|
|
787
|
+
let credentialGeneration = 0;
|
|
788
|
+
let authorityGeneration = 0;
|
|
789
|
+
let authorityTail = Promise.resolve();
|
|
790
|
+
let persistenceTail = Promise.resolve();
|
|
791
|
+
let persistencePending = 0;
|
|
792
|
+
function clearCredential() {
|
|
793
|
+
credential = void 0;
|
|
794
|
+
selectedWorkspaceKey = void 0;
|
|
795
|
+
credentialGeneration += 1;
|
|
796
|
+
}
|
|
797
|
+
function serializeAuthority(operation, invalidateCredential = false) {
|
|
798
|
+
if (invalidateCredential) clearCredential();
|
|
799
|
+
const guardedOperation = () => {
|
|
800
|
+
if (persistencePending > 0) throw new AuthError("state_conflict", 409);
|
|
801
|
+
return operation();
|
|
802
|
+
};
|
|
803
|
+
const request = authorityTail.then(guardedOperation, guardedOperation);
|
|
804
|
+
authorityTail = request.then(() => void 0, () => void 0);
|
|
805
|
+
return request;
|
|
806
|
+
}
|
|
807
|
+
async function readToken() {
|
|
681
808
|
if (!hydrated) {
|
|
682
809
|
hydration ??= hydrate().finally(() => {
|
|
683
810
|
hydration = null;
|
|
@@ -686,17 +813,28 @@ function createDeviceAuth(options) {
|
|
|
686
813
|
}
|
|
687
814
|
if (token && token.expiresAt.getTime() <= now()) {
|
|
688
815
|
token = null;
|
|
816
|
+
authorityGeneration += 1;
|
|
817
|
+
clearCredential();
|
|
689
818
|
await removePersisted();
|
|
690
819
|
}
|
|
691
820
|
return token ? copyToken(token) : null;
|
|
692
821
|
}
|
|
693
822
|
async function hydrate() {
|
|
694
823
|
try {
|
|
695
|
-
const
|
|
696
|
-
|
|
824
|
+
const persisted = options.persistence ? await persistenceCall((signal) => options.persistence.read(signal)) : null;
|
|
825
|
+
let loaded;
|
|
826
|
+
try {
|
|
827
|
+
loaded = normalizeToken(persisted, now());
|
|
828
|
+
} catch {
|
|
829
|
+
token = null;
|
|
830
|
+
if (options.persistence && persisted !== null) await persistenceCall((signal) => options.persistence.delete(signal)).catch(() => void 0);
|
|
831
|
+
throw unavailable();
|
|
832
|
+
}
|
|
833
|
+
if (options.persistence && persisted !== null && !loaded) await persistenceCall((signal) => options.persistence.delete(signal));
|
|
697
834
|
token = loaded;
|
|
698
|
-
} catch {
|
|
835
|
+
} catch (error) {
|
|
699
836
|
token = null;
|
|
837
|
+
if (error instanceof DeviceAuthorizationError) throw error;
|
|
700
838
|
throw unavailable();
|
|
701
839
|
} finally {
|
|
702
840
|
hydrated = true;
|
|
@@ -705,10 +843,11 @@ function createDeviceAuth(options) {
|
|
|
705
843
|
async function requestAuthorization(requestOptions = {}) {
|
|
706
844
|
if (requestOptions.signal?.aborted) throw cancelled();
|
|
707
845
|
try {
|
|
708
|
-
return normalizeAuthorization((await requestJsonResponse(
|
|
846
|
+
return normalizeAuthorization((await requestJsonResponse(joinURL(authOrigin, "api/v1/auth/device/code"), {
|
|
709
847
|
fetch: requestFetch,
|
|
710
848
|
timeoutMs,
|
|
711
849
|
method: "POST",
|
|
850
|
+
credentials: "omit",
|
|
712
851
|
body: {
|
|
713
852
|
client_id: options.clientId,
|
|
714
853
|
scope: DEVICE_SCOPE
|
|
@@ -722,34 +861,35 @@ function createDeviceAuth(options) {
|
|
|
722
861
|
throw unavailable();
|
|
723
862
|
}
|
|
724
863
|
}
|
|
725
|
-
async function pollForToken(authorization,
|
|
726
|
-
if (
|
|
727
|
-
const
|
|
864
|
+
async function pollForToken(authorization, signal) {
|
|
865
|
+
if (signal?.aborted) throw cancelled();
|
|
866
|
+
const previous = await readToken();
|
|
728
867
|
const startedAt = now();
|
|
729
|
-
const deadline = Math.min(
|
|
730
|
-
let intervalMs =
|
|
868
|
+
const deadline = Math.min(authorization.expiresAt.getTime(), startedAt + maxPollingMs);
|
|
869
|
+
let intervalMs = authorization.intervalSeconds * 1e3;
|
|
731
870
|
while (true) {
|
|
732
|
-
await wait(intervalMs,
|
|
871
|
+
await wait(intervalMs, signal, deadline, now);
|
|
733
872
|
const requestBudget = deadline - now();
|
|
734
873
|
if (requestBudget <= 0) throw expired();
|
|
735
874
|
let response;
|
|
736
875
|
try {
|
|
737
|
-
response = await requestJsonResponse(
|
|
876
|
+
response = await requestJsonResponse(joinURL(authOrigin, "api/v1/auth/device/token"), {
|
|
738
877
|
fetch: requestFetch,
|
|
739
878
|
timeoutMs: Math.min(timeoutMs, requestBudget),
|
|
740
879
|
method: "POST",
|
|
880
|
+
credentials: "omit",
|
|
741
881
|
body: {
|
|
742
882
|
grant_type: GRANT_TYPE,
|
|
743
|
-
device_code:
|
|
883
|
+
device_code: authorization.deviceCode,
|
|
744
884
|
client_id: options.clientId
|
|
745
885
|
},
|
|
746
|
-
signal
|
|
886
|
+
signal,
|
|
747
887
|
mapError(value) {
|
|
748
888
|
return new DeviceProtocolError(isObject(value) && typeof value.error === "string" ? value.error : void 0);
|
|
749
889
|
}
|
|
750
890
|
});
|
|
751
891
|
} catch (error) {
|
|
752
|
-
if (
|
|
892
|
+
if (signal?.aborted) throw cancelled();
|
|
753
893
|
if (now() >= deadline) throw expired();
|
|
754
894
|
if (error instanceof DeviceProtocolError) {
|
|
755
895
|
if (error.protocolCode === "authorization_pending") continue;
|
|
@@ -764,43 +904,54 @@ function createDeviceAuth(options) {
|
|
|
764
904
|
throw unavailable();
|
|
765
905
|
}
|
|
766
906
|
if (now() >= deadline) throw expired();
|
|
767
|
-
const
|
|
768
|
-
|
|
769
|
-
|
|
907
|
+
const recoverableAccessToken = extractRedeemedToken(response.value);
|
|
908
|
+
let next;
|
|
909
|
+
try {
|
|
910
|
+
next = normalizeTokenResponse(response.value, now());
|
|
770
911
|
} catch {
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
912
|
+
if (recoverableAccessToken) await revokeToken(recoverableAccessToken);
|
|
913
|
+
await clearToken().catch(() => void 0);
|
|
914
|
+
throw unavailable();
|
|
915
|
+
}
|
|
916
|
+
try {
|
|
917
|
+
await publishReplacement(next, previous);
|
|
918
|
+
} catch {
|
|
919
|
+
await revokeToken(next.accessToken);
|
|
920
|
+
await retainPrevious(previous);
|
|
774
921
|
throw unavailable();
|
|
775
922
|
}
|
|
776
|
-
token = next;
|
|
777
|
-
hydrated = true;
|
|
778
923
|
return copyToken(next);
|
|
779
924
|
}
|
|
780
925
|
}
|
|
781
|
-
async function
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
926
|
+
async function fetchBearerSession(accessToken) {
|
|
927
|
+
return normalizeSession((await requestJsonResponse(joinURL(authOrigin, "api/v1/auth/get-session"), {
|
|
928
|
+
fetch: requestFetch,
|
|
929
|
+
timeoutMs,
|
|
930
|
+
method: "GET",
|
|
931
|
+
credentials: "omit",
|
|
932
|
+
headers: { authorization: `Bearer ${accessToken}` }
|
|
933
|
+
})).value);
|
|
934
|
+
}
|
|
935
|
+
async function authenticated(path, current, body) {
|
|
936
|
+
return requestJsonResponse(joinURL(authOrigin, "api/v1/auth", path), {
|
|
937
|
+
fetch: requestFetch,
|
|
938
|
+
timeoutMs,
|
|
939
|
+
method: body ? "POST" : "GET",
|
|
940
|
+
credentials: "omit",
|
|
941
|
+
headers: { authorization: `Bearer ${current.accessToken}` },
|
|
942
|
+
body,
|
|
943
|
+
mapError: fromAccountAuthorityError
|
|
944
|
+
});
|
|
795
945
|
}
|
|
796
946
|
async function revoke(path) {
|
|
797
|
-
const current = await
|
|
947
|
+
const current = await readToken();
|
|
798
948
|
if (!current) return;
|
|
799
949
|
try {
|
|
800
|
-
await requestJsonResponse(
|
|
950
|
+
await requestJsonResponse(joinURL(authOrigin, "api/v1/auth", path), {
|
|
801
951
|
fetch: requestFetch,
|
|
802
952
|
timeoutMs,
|
|
803
953
|
method: "POST",
|
|
954
|
+
credentials: "omit",
|
|
804
955
|
headers: { authorization: `Bearer ${current.accessToken}` },
|
|
805
956
|
body: path === "revoke-session" ? { token: current.accessToken } : void 0
|
|
806
957
|
});
|
|
@@ -808,8 +959,139 @@ function createDeviceAuth(options) {
|
|
|
808
959
|
throw unavailable();
|
|
809
960
|
}
|
|
810
961
|
token = null;
|
|
962
|
+
hydrated = true;
|
|
963
|
+
authorityGeneration += 1;
|
|
964
|
+
clearCredential();
|
|
965
|
+
await removePersisted();
|
|
966
|
+
}
|
|
967
|
+
async function replaceToken(next) {
|
|
968
|
+
try {
|
|
969
|
+
if (options.persistence) await persistenceCall((signal) => options.persistence.replace(copyToken(next), signal));
|
|
970
|
+
} catch {
|
|
971
|
+
throw unavailable();
|
|
972
|
+
}
|
|
973
|
+
token = copyToken(next);
|
|
974
|
+
hydrated = true;
|
|
975
|
+
authorityGeneration += 1;
|
|
976
|
+
clearCredential();
|
|
977
|
+
}
|
|
978
|
+
async function publishReplacement(next, previous) {
|
|
979
|
+
await replaceToken(next);
|
|
980
|
+
if (previous && previous.accessToken !== next.accessToken) await revokeToken(previous.accessToken);
|
|
981
|
+
}
|
|
982
|
+
async function replaceIssuedToken(issued, previous) {
|
|
983
|
+
let session;
|
|
984
|
+
try {
|
|
985
|
+
const authoritative = await fetchBearerSession(issued.accessToken);
|
|
986
|
+
if (!authoritative || authoritative.user.id !== issued.userId || authoritative.expiresAt.getTime() <= now()) throw authorityUnavailable();
|
|
987
|
+
session = authoritative;
|
|
988
|
+
await publishReplacement({
|
|
989
|
+
accessToken: issued.accessToken,
|
|
990
|
+
tokenType: "Bearer",
|
|
991
|
+
expiresAt: authoritative.expiresAt,
|
|
992
|
+
scope: DEVICE_SCOPE
|
|
993
|
+
}, previous);
|
|
994
|
+
} catch (error) {
|
|
995
|
+
await revokeToken(issued.accessToken);
|
|
996
|
+
await retainPrevious(previous);
|
|
997
|
+
throw error instanceof AuthError ? error : authorityUnavailable();
|
|
998
|
+
}
|
|
999
|
+
return session;
|
|
1000
|
+
}
|
|
1001
|
+
async function replaceIssuedResponse(value, previous) {
|
|
1002
|
+
const accessToken = extractIssuedToken(value);
|
|
1003
|
+
let issued;
|
|
1004
|
+
try {
|
|
1005
|
+
issued = normalizeIssuedSession(value);
|
|
1006
|
+
} catch (error) {
|
|
1007
|
+
if (accessToken) await revokeToken(accessToken);
|
|
1008
|
+
await retainPrevious(previous);
|
|
1009
|
+
throw error instanceof AuthError ? error : authorityUnavailable();
|
|
1010
|
+
}
|
|
1011
|
+
return replaceIssuedToken(issued, previous);
|
|
1012
|
+
}
|
|
1013
|
+
async function revokeToken(accessToken) {
|
|
1014
|
+
await requestJsonResponse(joinURL(authOrigin, "api/v1/auth/revoke-session"), {
|
|
1015
|
+
fetch: requestFetch,
|
|
1016
|
+
timeoutMs,
|
|
1017
|
+
method: "POST",
|
|
1018
|
+
credentials: "omit",
|
|
1019
|
+
headers: { authorization: `Bearer ${accessToken}` },
|
|
1020
|
+
body: { token: accessToken }
|
|
1021
|
+
}).catch(() => void 0);
|
|
1022
|
+
}
|
|
1023
|
+
async function clearToken() {
|
|
1024
|
+
token = null;
|
|
1025
|
+
hydrated = true;
|
|
1026
|
+
authorityGeneration += 1;
|
|
1027
|
+
clearCredential();
|
|
811
1028
|
await removePersisted();
|
|
812
1029
|
}
|
|
1030
|
+
async function retainPrevious(previous) {
|
|
1031
|
+
if (previous) {
|
|
1032
|
+
try {
|
|
1033
|
+
const session = await fetchBearerSession(previous.accessToken);
|
|
1034
|
+
if (session && session.expiresAt.getTime() > now()) {
|
|
1035
|
+
const restored = {
|
|
1036
|
+
...previous,
|
|
1037
|
+
expiresAt: session.expiresAt
|
|
1038
|
+
};
|
|
1039
|
+
if (options.persistence) await replaceToken(restored);
|
|
1040
|
+
else {
|
|
1041
|
+
token = copyToken(previous);
|
|
1042
|
+
hydrated = true;
|
|
1043
|
+
}
|
|
1044
|
+
return;
|
|
1045
|
+
}
|
|
1046
|
+
} catch {}
|
|
1047
|
+
await revokeToken(previous.accessToken);
|
|
1048
|
+
}
|
|
1049
|
+
token = null;
|
|
1050
|
+
hydrated = true;
|
|
1051
|
+
authorityGeneration += 1;
|
|
1052
|
+
clearCredential();
|
|
1053
|
+
await removePersisted().catch(() => void 0);
|
|
1054
|
+
}
|
|
1055
|
+
async function accountRequest(path, body, mapError = fromAccountAuthorityError) {
|
|
1056
|
+
clearCredential();
|
|
1057
|
+
return requestJsonResponse(joinURL(authOrigin, "api/v1/auth", path), {
|
|
1058
|
+
fetch: requestFetch,
|
|
1059
|
+
timeoutMs,
|
|
1060
|
+
method: "POST",
|
|
1061
|
+
credentials: "omit",
|
|
1062
|
+
headers: { "content-type": "application/json; charset=utf-8" },
|
|
1063
|
+
body,
|
|
1064
|
+
mapError
|
|
1065
|
+
});
|
|
1066
|
+
}
|
|
1067
|
+
async function getSession() {
|
|
1068
|
+
const current = await readToken();
|
|
1069
|
+
if (!current) return null;
|
|
1070
|
+
let session;
|
|
1071
|
+
try {
|
|
1072
|
+
session = await fetchBearerSession(current.accessToken);
|
|
1073
|
+
} catch (error) {
|
|
1074
|
+
throw error instanceof AuthError ? error : unavailable();
|
|
1075
|
+
}
|
|
1076
|
+
if (!session || session.expiresAt.getTime() <= now()) {
|
|
1077
|
+
await clearToken();
|
|
1078
|
+
return null;
|
|
1079
|
+
}
|
|
1080
|
+
if (session.expiresAt.getTime() !== current.expiresAt.getTime()) try {
|
|
1081
|
+
await replaceToken({
|
|
1082
|
+
...current,
|
|
1083
|
+
expiresAt: session.expiresAt
|
|
1084
|
+
});
|
|
1085
|
+
} catch {
|
|
1086
|
+
token = null;
|
|
1087
|
+
hydrated = true;
|
|
1088
|
+
authorityGeneration += 1;
|
|
1089
|
+
clearCredential();
|
|
1090
|
+
await removePersisted().catch(() => void 0);
|
|
1091
|
+
throw unavailable();
|
|
1092
|
+
}
|
|
1093
|
+
return session;
|
|
1094
|
+
}
|
|
813
1095
|
async function removePersisted() {
|
|
814
1096
|
if (!options.persistence) return;
|
|
815
1097
|
try {
|
|
@@ -820,30 +1102,236 @@ function createDeviceAuth(options) {
|
|
|
820
1102
|
}
|
|
821
1103
|
return {
|
|
822
1104
|
requestAuthorization,
|
|
823
|
-
pollForToken,
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
1105
|
+
pollForToken(authorization, pollOptions) {
|
|
1106
|
+
let signal;
|
|
1107
|
+
let snapshot;
|
|
1108
|
+
try {
|
|
1109
|
+
signal = normalizePollSignal(pollOptions);
|
|
1110
|
+
if (signal?.aborted) throw cancelled();
|
|
1111
|
+
snapshot = normalizeAuthorizationInput(authorization, now());
|
|
1112
|
+
} catch (error) {
|
|
1113
|
+
return Promise.reject(error);
|
|
1114
|
+
}
|
|
1115
|
+
return serializeAuthority(() => pollForToken(snapshot, signal), true);
|
|
1116
|
+
},
|
|
1117
|
+
getToken: () => serializeAuthority(async () => copyTokenOrNull(await readToken())),
|
|
1118
|
+
getSession: () => serializeAuthority(getSession, true),
|
|
1119
|
+
signUpWithEmail(email, password, name) {
|
|
1120
|
+
let body;
|
|
1121
|
+
try {
|
|
1122
|
+
body = {
|
|
1123
|
+
email: normalizeEmail(email),
|
|
1124
|
+
password: normalizePassword(password, "password", 10),
|
|
1125
|
+
name: normalizeName(name)
|
|
1126
|
+
};
|
|
1127
|
+
} catch (error) {
|
|
1128
|
+
return Promise.reject(error);
|
|
1129
|
+
}
|
|
1130
|
+
return serializeAuthority(async () => {
|
|
1131
|
+
const previous = await readToken();
|
|
1132
|
+
return replaceIssuedResponse((await accountRequest("sign-up/email", body)).value, previous);
|
|
1133
|
+
}, true);
|
|
1134
|
+
},
|
|
1135
|
+
signInWithEmail(email, password) {
|
|
1136
|
+
let body;
|
|
1137
|
+
try {
|
|
1138
|
+
body = {
|
|
1139
|
+
email: normalizeEmail(email),
|
|
1140
|
+
password: normalizePassword(password)
|
|
1141
|
+
};
|
|
1142
|
+
} catch (error) {
|
|
1143
|
+
return Promise.reject(error);
|
|
1144
|
+
}
|
|
1145
|
+
return serializeAuthority(async () => {
|
|
1146
|
+
const previous = await readToken();
|
|
1147
|
+
return replaceIssuedResponse((await accountRequest("sign-in/email", body)).value, previous);
|
|
1148
|
+
}, true);
|
|
1149
|
+
},
|
|
1150
|
+
signInWithSocialIdToken(input) {
|
|
1151
|
+
let body;
|
|
1152
|
+
try {
|
|
1153
|
+
body = socialIdTokenRequest(normalizeSocialIdTokenInput(input));
|
|
1154
|
+
} catch (error) {
|
|
1155
|
+
return Promise.reject(error);
|
|
1156
|
+
}
|
|
1157
|
+
return serializeAuthority(async () => {
|
|
1158
|
+
const previous = await readToken();
|
|
1159
|
+
return replaceIssuedResponse((await accountRequest("sign-in/social", body, fromSocialIdTokenAuthorityError)).value, previous);
|
|
1160
|
+
}, true);
|
|
1161
|
+
},
|
|
1162
|
+
updateProfile(input) {
|
|
1163
|
+
let body;
|
|
1164
|
+
try {
|
|
1165
|
+
body = normalizeProfileInput(input);
|
|
1166
|
+
} catch (error) {
|
|
1167
|
+
return Promise.reject(error);
|
|
1168
|
+
}
|
|
1169
|
+
return serializeAuthority(async () => {
|
|
1170
|
+
const current = await readToken();
|
|
1171
|
+
if (!current) throw new AuthError("authentication_required", 401);
|
|
1172
|
+
const response = await authenticated("update-user", current, body);
|
|
1173
|
+
if (!isObject(response.value) || response.value.status !== true) throw authorityUnavailable();
|
|
1174
|
+
const session = await getSession();
|
|
1175
|
+
if (!session) throw new AuthError("authentication_required", 401);
|
|
1176
|
+
return session;
|
|
1177
|
+
}, true);
|
|
828
1178
|
},
|
|
829
|
-
|
|
830
|
-
|
|
1179
|
+
changePassword(currentPassword, newPassword, revokeOtherSessions = true) {
|
|
1180
|
+
let body;
|
|
1181
|
+
try {
|
|
1182
|
+
if (typeof revokeOtherSessions !== "boolean") throw new TypeError("revokeOtherSessions is invalid");
|
|
1183
|
+
body = {
|
|
1184
|
+
currentPassword: normalizePassword(currentPassword, "currentPassword"),
|
|
1185
|
+
newPassword: normalizePassword(newPassword, "newPassword", 10),
|
|
1186
|
+
revokeOtherSessions
|
|
1187
|
+
};
|
|
1188
|
+
} catch (error) {
|
|
1189
|
+
return Promise.reject(error);
|
|
1190
|
+
}
|
|
1191
|
+
return serializeAuthority(async () => {
|
|
1192
|
+
const current = await readToken();
|
|
1193
|
+
if (!current) throw new AuthError("authentication_required", 401);
|
|
1194
|
+
let response;
|
|
1195
|
+
try {
|
|
1196
|
+
response = await authenticated("change-password", current, body);
|
|
1197
|
+
} catch (error) {
|
|
1198
|
+
await retainPrevious(current);
|
|
1199
|
+
throw error instanceof AuthError ? error : authorityUnavailable();
|
|
1200
|
+
}
|
|
1201
|
+
let replacement;
|
|
1202
|
+
try {
|
|
1203
|
+
replacement = normalizePasswordChange(response.value);
|
|
1204
|
+
} catch (error) {
|
|
1205
|
+
const accessToken = extractIssuedToken(response.value);
|
|
1206
|
+
if (accessToken) await revokeToken(accessToken);
|
|
1207
|
+
await retainPrevious(current);
|
|
1208
|
+
throw error;
|
|
1209
|
+
}
|
|
1210
|
+
if (replacement.accessToken !== null) return replaceIssuedToken({
|
|
1211
|
+
accessToken: replacement.accessToken,
|
|
1212
|
+
userId: replacement.userId
|
|
1213
|
+
}, current);
|
|
1214
|
+
let session;
|
|
1215
|
+
try {
|
|
1216
|
+
session = await fetchBearerSession(current.accessToken);
|
|
1217
|
+
} catch (error) {
|
|
1218
|
+
await retainPrevious(current);
|
|
1219
|
+
throw error instanceof AuthError ? error : authorityUnavailable();
|
|
1220
|
+
}
|
|
1221
|
+
if (!session || session.user.id !== replacement.userId || session.expiresAt.getTime() <= now()) {
|
|
1222
|
+
await clearToken();
|
|
1223
|
+
throw new AuthError("authentication_required", 401);
|
|
1224
|
+
}
|
|
1225
|
+
await replaceToken({
|
|
1226
|
+
...current,
|
|
1227
|
+
expiresAt: session.expiresAt
|
|
1228
|
+
});
|
|
1229
|
+
return session;
|
|
1230
|
+
}, true);
|
|
1231
|
+
},
|
|
1232
|
+
getDriveCredential(input) {
|
|
1233
|
+
let workspace;
|
|
1234
|
+
let forceRefresh;
|
|
1235
|
+
try {
|
|
1236
|
+
workspace = normalizeWorkspaceSelection(input?.workspace);
|
|
1237
|
+
const submittedForceRefresh = input?.forceRefresh;
|
|
1238
|
+
if (submittedForceRefresh !== void 0 && typeof submittedForceRefresh !== "boolean") throw new TypeError("forceRefresh is invalid");
|
|
1239
|
+
forceRefresh = submittedForceRefresh ?? false;
|
|
1240
|
+
} catch (error) {
|
|
1241
|
+
return Promise.reject(error);
|
|
1242
|
+
}
|
|
1243
|
+
const workspaceKey = workspace.type === "personal" ? "personal" : `organization:${workspace.organizationId}`;
|
|
1244
|
+
if (credentialRequest) {
|
|
1245
|
+
if (!forceRefresh && credentialRequest.key === workspaceKey && credentialRequest.generation === credentialGeneration && selectedWorkspaceKey === workspaceKey) return credentialRequest.value;
|
|
1246
|
+
return Promise.reject(new AuthError("state_conflict", 409));
|
|
1247
|
+
}
|
|
1248
|
+
if (selectedWorkspaceKey !== workspaceKey) {
|
|
1249
|
+
credential = void 0;
|
|
1250
|
+
selectedWorkspaceKey = workspaceKey;
|
|
1251
|
+
credentialGeneration += 1;
|
|
1252
|
+
}
|
|
1253
|
+
if (forceRefresh) {
|
|
1254
|
+
credential = void 0;
|
|
1255
|
+
credentialGeneration += 1;
|
|
1256
|
+
}
|
|
1257
|
+
const requestGeneration = credentialGeneration;
|
|
1258
|
+
const request = (async () => {
|
|
1259
|
+
const authority = await serializeAuthority(async () => {
|
|
1260
|
+
const current = await readToken();
|
|
1261
|
+
return {
|
|
1262
|
+
authorityGeneration,
|
|
1263
|
+
token: current
|
|
1264
|
+
};
|
|
1265
|
+
});
|
|
1266
|
+
const current = authority.token;
|
|
1267
|
+
if (!current) throw new AuthError("authentication_required", 401);
|
|
1268
|
+
if (authority.authorityGeneration !== authorityGeneration || requestGeneration !== credentialGeneration || selectedWorkspaceKey !== workspaceKey) throw new AuthError("state_conflict", 409);
|
|
1269
|
+
if (!forceRefresh && credential?.key === workspaceKey && credential.value.expiresAt.getTime() - now() > REFRESH_WINDOW_MS) return credential.value;
|
|
1270
|
+
const next = normalizeDriveCredential((await requestJsonResponse(joinURL(authOrigin, "api/v1/auth/drive-credential"), {
|
|
1271
|
+
fetch: requestFetch,
|
|
1272
|
+
timeoutMs,
|
|
1273
|
+
method: "POST",
|
|
1274
|
+
credentials: "omit",
|
|
1275
|
+
headers: {
|
|
1276
|
+
authorization: `Bearer ${current.accessToken}`,
|
|
1277
|
+
"content-type": "application/json; charset=utf-8"
|
|
1278
|
+
},
|
|
1279
|
+
body: { workspace }
|
|
1280
|
+
})).value, now());
|
|
1281
|
+
if (authority.authorityGeneration !== authorityGeneration || requestGeneration !== credentialGeneration || selectedWorkspaceKey !== workspaceKey) throw new AuthError("state_conflict", 409);
|
|
1282
|
+
credential = {
|
|
1283
|
+
key: workspaceKey,
|
|
1284
|
+
value: next
|
|
1285
|
+
};
|
|
1286
|
+
return next;
|
|
1287
|
+
})().finally(() => {
|
|
1288
|
+
if (credentialRequest?.value === request) credentialRequest = void 0;
|
|
1289
|
+
});
|
|
1290
|
+
credentialRequest = {
|
|
1291
|
+
generation: requestGeneration,
|
|
1292
|
+
key: workspaceKey,
|
|
1293
|
+
value: request
|
|
1294
|
+
};
|
|
1295
|
+
return request;
|
|
1296
|
+
},
|
|
1297
|
+
revokeCurrentDevice: () => serializeAuthority(() => revoke("revoke-session"), true),
|
|
1298
|
+
revokeAllSessions: () => serializeAuthority(() => revoke("revoke-sessions"), true)
|
|
831
1299
|
};
|
|
832
1300
|
async function persistenceCall(operation) {
|
|
833
|
-
const
|
|
834
|
-
|
|
1301
|
+
const operationTimeoutMs = Math.min(timeoutMs, 1e3);
|
|
1302
|
+
persistencePending += 1;
|
|
1303
|
+
const settledRequest = persistenceTail.then(async () => {
|
|
1304
|
+
const controller = new AbortController();
|
|
1305
|
+
const timer = setTimeout(() => controller.abort(), operationTimeoutMs);
|
|
1306
|
+
try {
|
|
1307
|
+
return await operation(controller.signal);
|
|
1308
|
+
} finally {
|
|
1309
|
+
clearTimeout(timer);
|
|
1310
|
+
}
|
|
1311
|
+
}).finally(() => {
|
|
1312
|
+
persistencePending -= 1;
|
|
1313
|
+
});
|
|
1314
|
+
persistenceTail = settledRequest.then(() => void 0, () => void 0);
|
|
1315
|
+
let publicTimer;
|
|
835
1316
|
try {
|
|
836
|
-
return await Promise.race([
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
}, Math.min(timeoutMs, 1e3));
|
|
1317
|
+
return await Promise.race([settledRequest.catch(() => {
|
|
1318
|
+
throw unavailable();
|
|
1319
|
+
}), new Promise((_, reject) => {
|
|
1320
|
+
publicTimer = setTimeout(() => reject(unavailable()), operationTimeoutMs);
|
|
841
1321
|
})]);
|
|
842
1322
|
} finally {
|
|
843
|
-
if (
|
|
1323
|
+
if (publicTimer) clearTimeout(publicTimer);
|
|
844
1324
|
}
|
|
845
1325
|
}
|
|
846
1326
|
}
|
|
1327
|
+
function copyTokenOrNull(value) {
|
|
1328
|
+
return value ? copyToken(value) : null;
|
|
1329
|
+
}
|
|
1330
|
+
function normalizePollSignal(value) {
|
|
1331
|
+
if (value === void 0) return void 0;
|
|
1332
|
+
if (!isObject(value) || Object.keys(value).some((key) => key !== "signal") || value.signal !== void 0 && !(value.signal instanceof AbortSignal)) throw new TypeError("poll options are invalid");
|
|
1333
|
+
return value.signal;
|
|
1334
|
+
}
|
|
847
1335
|
var DeviceProtocolError = class extends Error {
|
|
848
1336
|
protocolCode;
|
|
849
1337
|
constructor(protocolCode) {
|
|
@@ -863,8 +1351,15 @@ function normalizeAuthorization(value, currentTime) {
|
|
|
863
1351
|
};
|
|
864
1352
|
}
|
|
865
1353
|
function normalizeAuthorizationInput(value, currentTime) {
|
|
866
|
-
if (!value || !boundedSecret(value.deviceCode) || typeof value.userCode !== "string" || !exactHttpsOrLocalUri(value.verificationUri) || !exactHttpsOrLocalUri(value.verificationUriComplete) || !(value.expiresAt instanceof Date) || !Number.isFinite(value.expiresAt.getTime()) || value.expiresAt.getTime() <= currentTime || !Number.isSafeInteger(value.intervalSeconds) || value.intervalSeconds < 1 || value.intervalSeconds > 60) throw invalid();
|
|
867
|
-
return
|
|
1354
|
+
if (!value || !boundedSecret(value.deviceCode) || typeof value.userCode !== "string" || !/^[A-Z0-9-]{1,32}$/.test(value.userCode) || !exactHttpsOrLocalUri(value.verificationUri) || !exactHttpsOrLocalUri(value.verificationUriComplete) || !(value.expiresAt instanceof Date) || !Number.isFinite(value.expiresAt.getTime()) || value.expiresAt.getTime() <= currentTime || !Number.isSafeInteger(value.intervalSeconds) || value.intervalSeconds < 1 || value.intervalSeconds > 60) throw invalid();
|
|
1355
|
+
return {
|
|
1356
|
+
deviceCode: value.deviceCode,
|
|
1357
|
+
userCode: value.userCode,
|
|
1358
|
+
verificationUri: value.verificationUri,
|
|
1359
|
+
verificationUriComplete: value.verificationUriComplete,
|
|
1360
|
+
expiresAt: new Date(value.expiresAt),
|
|
1361
|
+
intervalSeconds: value.intervalSeconds
|
|
1362
|
+
};
|
|
868
1363
|
}
|
|
869
1364
|
function normalizeTokenResponse(value, currentTime) {
|
|
870
1365
|
if (!isObject(value) || !boundedSecret(value.access_token) || value.token_type !== "Bearer" || value.scope !== DEVICE_SCOPE || !Number.isSafeInteger(value.expires_in) || value.expires_in < 1 || value.expires_in > 744 * 60 * 60) throw unavailable();
|
|
@@ -875,6 +1370,26 @@ function normalizeTokenResponse(value, currentTime) {
|
|
|
875
1370
|
scope: DEVICE_SCOPE
|
|
876
1371
|
};
|
|
877
1372
|
}
|
|
1373
|
+
function normalizeIssuedSession(value) {
|
|
1374
|
+
if (!isObject(value) || !boundedSecret(value.token) || !isObject(value.user) || typeof value.user.id !== "string" || !/^[A-Za-z0-9_-]{1,128}$/.test(value.user.id)) throw authorityUnavailable();
|
|
1375
|
+
return {
|
|
1376
|
+
accessToken: value.token,
|
|
1377
|
+
userId: value.user.id
|
|
1378
|
+
};
|
|
1379
|
+
}
|
|
1380
|
+
function extractIssuedToken(value) {
|
|
1381
|
+
return isObject(value) && boundedSecret(value.token) ? value.token : void 0;
|
|
1382
|
+
}
|
|
1383
|
+
function extractRedeemedToken(value) {
|
|
1384
|
+
return isObject(value) && boundedSecret(value.access_token) ? value.access_token : void 0;
|
|
1385
|
+
}
|
|
1386
|
+
function normalizePasswordChange(value) {
|
|
1387
|
+
if (!isObject(value) || value.token !== null && !boundedSecret(value.token) || !isObject(value.user) || typeof value.user.id !== "string" || !/^[A-Za-z0-9_-]{1,128}$/.test(value.user.id)) throw authorityUnavailable();
|
|
1388
|
+
return {
|
|
1389
|
+
accessToken: value.token,
|
|
1390
|
+
userId: value.user.id
|
|
1391
|
+
};
|
|
1392
|
+
}
|
|
878
1393
|
function normalizeToken(value, currentTime) {
|
|
879
1394
|
if (value === null) return null;
|
|
880
1395
|
if (!value || !boundedSecret(value.accessToken) || value.tokenType !== "Bearer" || value.scope !== DEVICE_SCOPE || !(value.expiresAt instanceof Date) || !Number.isFinite(value.expiresAt.getTime())) throw unavailable();
|
|
@@ -933,4 +1448,4 @@ function unavailable() {
|
|
|
933
1448
|
return new DeviceAuthorizationError("identity_authority_unavailable");
|
|
934
1449
|
}
|
|
935
1450
|
//#endregion
|
|
936
|
-
export { DeviceAuthorizationError, createDeviceAuth };
|
|
1451
|
+
export { AuthError, DeviceAuthorizationError, createDeviceAuth };
|