- Include dist-release/ and release/ for direct colleague use - Add beginner-friendly installation guide - Update .gitignore to track distribution builds
3336 lines
116 KiB
JavaScript
3336 lines
116 KiB
JavaScript
"use strict";
|
|
(() => {
|
|
// src/shared/auth-config.ts
|
|
var defaultAuthConfig = {
|
|
apiResource: "https://talent-search.intelligrow.cn",
|
|
appId: "i4jkllbvih0554r4n0fd3",
|
|
enableDevAuthPanel: false,
|
|
logtoEndpoint: "https://login-api.intelligrow.cn",
|
|
scopes: ["openid", "profile", "offline_access", "talent-search:read"]
|
|
};
|
|
function readAuthConfig(overrides = {}) {
|
|
const nextConfig = {
|
|
...defaultAuthConfig,
|
|
...overrides
|
|
};
|
|
if (!nextConfig.logtoEndpoint.trim()) {
|
|
throw new Error("auth config logtoEndpoint is required");
|
|
}
|
|
if (!nextConfig.appId.trim()) {
|
|
throw new Error("auth config appId is required");
|
|
}
|
|
if (!nextConfig.apiResource.trim()) {
|
|
throw new Error("auth config apiResource is required");
|
|
}
|
|
return nextConfig;
|
|
}
|
|
|
|
// src/background/auth/state.ts
|
|
function createLoggedOutAuthState(config) {
|
|
return {
|
|
isAuthenticated: false,
|
|
resource: config?.apiResource ?? null
|
|
};
|
|
}
|
|
function createLoggedInAuthState(claims, config) {
|
|
return {
|
|
accessTokenExpiresAt: null,
|
|
isAuthenticated: true,
|
|
resource: config?.apiResource ?? null,
|
|
scopes: config?.scopes ?? [],
|
|
tokenAvailable: true,
|
|
userInfo: {
|
|
email: readStringClaim(claims, "email"),
|
|
name: readStringClaim(claims, "name"),
|
|
sub: readStringClaim(claims, "sub"),
|
|
username: readStringClaim(claims, "username")
|
|
}
|
|
};
|
|
}
|
|
function readStringClaim(claims, key) {
|
|
const value = claims?.[key];
|
|
return typeof value === "string" ? value : void 0;
|
|
}
|
|
|
|
// src/background/auth/controller.ts
|
|
function createAuthController(options) {
|
|
const config = options.config ?? readAuthConfig();
|
|
return {
|
|
async getAccessToken() {
|
|
return options.authClient.getAccessToken(config.apiResource);
|
|
},
|
|
async getAuthState() {
|
|
const isAuthenticated = await options.authClient.isAuthenticated();
|
|
if (!isAuthenticated) {
|
|
return createLoggedOutAuthState(config);
|
|
}
|
|
const claims = await options.authClient.getIdTokenClaims();
|
|
return createLoggedInAuthState(claims, config);
|
|
},
|
|
async signIn() {
|
|
await options.authClient.signIn();
|
|
},
|
|
async signOut() {
|
|
await options.authClient.signOut();
|
|
}
|
|
};
|
|
}
|
|
|
|
// node_modules/map-obj/index.js
|
|
var isObject = (value) => typeof value === "object" && value !== null;
|
|
var isObjectCustom = (value) => isObject(value) && !(value instanceof RegExp) && !(value instanceof Error) && !(value instanceof Date);
|
|
var mapObjectSkip = /* @__PURE__ */ Symbol("mapObjectSkip");
|
|
var _mapObject = (object, mapper, options, isSeen = /* @__PURE__ */ new WeakMap()) => {
|
|
options = {
|
|
deep: false,
|
|
target: {},
|
|
...options
|
|
};
|
|
if (isSeen.has(object)) {
|
|
return isSeen.get(object);
|
|
}
|
|
isSeen.set(object, options.target);
|
|
const { target } = options;
|
|
delete options.target;
|
|
const mapArray = (array) => array.map((element) => isObjectCustom(element) ? _mapObject(element, mapper, options, isSeen) : element);
|
|
if (Array.isArray(object)) {
|
|
return mapArray(object);
|
|
}
|
|
for (const [key, value] of Object.entries(object)) {
|
|
const mapResult = mapper(key, value, object);
|
|
if (mapResult === mapObjectSkip) {
|
|
continue;
|
|
}
|
|
let [newKey, newValue, { shouldRecurse = true } = {}] = mapResult;
|
|
if (newKey === "__proto__") {
|
|
continue;
|
|
}
|
|
if (options.deep && shouldRecurse && isObjectCustom(newValue)) {
|
|
newValue = Array.isArray(newValue) ? mapArray(newValue) : _mapObject(newValue, mapper, options, isSeen);
|
|
}
|
|
target[newKey] = newValue;
|
|
}
|
|
return target;
|
|
};
|
|
function mapObject(object, mapper, options) {
|
|
if (!isObject(object)) {
|
|
throw new TypeError(`Expected an object, got \`${object}\` (${typeof object})`);
|
|
}
|
|
return _mapObject(object, mapper, options);
|
|
}
|
|
|
|
// node_modules/camelcase/index.js
|
|
var UPPERCASE = /[\p{Lu}]/u;
|
|
var LOWERCASE = /[\p{Ll}]/u;
|
|
var LEADING_CAPITAL = /^[\p{Lu}](?![\p{Lu}])/gu;
|
|
var IDENTIFIER = /([\p{Alpha}\p{N}_]|$)/u;
|
|
var SEPARATORS = /[_.\- ]+/;
|
|
var LEADING_SEPARATORS = new RegExp("^" + SEPARATORS.source);
|
|
var SEPARATORS_AND_IDENTIFIER = new RegExp(SEPARATORS.source + IDENTIFIER.source, "gu");
|
|
var NUMBERS_AND_IDENTIFIER = new RegExp("\\d+" + IDENTIFIER.source, "gu");
|
|
var preserveCamelCase = (string, toLowerCase, toUpperCase, preserveConsecutiveUppercase2) => {
|
|
let isLastCharLower = false;
|
|
let isLastCharUpper = false;
|
|
let isLastLastCharUpper = false;
|
|
let isLastLastCharPreserved = false;
|
|
for (let index = 0; index < string.length; index++) {
|
|
const character = string[index];
|
|
isLastLastCharPreserved = index > 2 ? string[index - 3] === "-" : true;
|
|
if (isLastCharLower && UPPERCASE.test(character)) {
|
|
string = string.slice(0, index) + "-" + string.slice(index);
|
|
isLastCharLower = false;
|
|
isLastLastCharUpper = isLastCharUpper;
|
|
isLastCharUpper = true;
|
|
index++;
|
|
} else if (isLastCharUpper && isLastLastCharUpper && LOWERCASE.test(character) && (!isLastLastCharPreserved || preserveConsecutiveUppercase2)) {
|
|
string = string.slice(0, index - 1) + "-" + string.slice(index - 1);
|
|
isLastLastCharUpper = isLastCharUpper;
|
|
isLastCharUpper = false;
|
|
isLastCharLower = true;
|
|
} else {
|
|
isLastCharLower = toLowerCase(character) === character && toUpperCase(character) !== character;
|
|
isLastLastCharUpper = isLastCharUpper;
|
|
isLastCharUpper = toUpperCase(character) === character && toLowerCase(character) !== character;
|
|
}
|
|
}
|
|
return string;
|
|
};
|
|
var preserveConsecutiveUppercase = (input, toLowerCase) => {
|
|
LEADING_CAPITAL.lastIndex = 0;
|
|
return input.replaceAll(LEADING_CAPITAL, (match) => toLowerCase(match));
|
|
};
|
|
var postProcess = (input, toUpperCase) => {
|
|
SEPARATORS_AND_IDENTIFIER.lastIndex = 0;
|
|
NUMBERS_AND_IDENTIFIER.lastIndex = 0;
|
|
return input.replaceAll(NUMBERS_AND_IDENTIFIER, (match, pattern, offset) => ["_", "-"].includes(input.charAt(offset + match.length)) ? match : toUpperCase(match)).replaceAll(SEPARATORS_AND_IDENTIFIER, (_, identifier) => toUpperCase(identifier));
|
|
};
|
|
function camelCase(input, options) {
|
|
if (!(typeof input === "string" || Array.isArray(input))) {
|
|
throw new TypeError("Expected the input to be `string | string[]`");
|
|
}
|
|
options = {
|
|
pascalCase: false,
|
|
preserveConsecutiveUppercase: false,
|
|
...options
|
|
};
|
|
if (Array.isArray(input)) {
|
|
input = input.map((x) => x.trim()).filter((x) => x.length).join("-");
|
|
} else {
|
|
input = input.trim();
|
|
}
|
|
if (input.length === 0) {
|
|
return "";
|
|
}
|
|
const toLowerCase = options.locale === false ? (string) => string.toLowerCase() : (string) => string.toLocaleLowerCase(options.locale);
|
|
const toUpperCase = options.locale === false ? (string) => string.toUpperCase() : (string) => string.toLocaleUpperCase(options.locale);
|
|
if (input.length === 1) {
|
|
if (SEPARATORS.test(input)) {
|
|
return "";
|
|
}
|
|
return options.pascalCase ? toUpperCase(input) : toLowerCase(input);
|
|
}
|
|
const hasUpperCase = input !== toLowerCase(input);
|
|
if (hasUpperCase) {
|
|
input = preserveCamelCase(input, toLowerCase, toUpperCase, options.preserveConsecutiveUppercase);
|
|
}
|
|
input = input.replace(LEADING_SEPARATORS, "");
|
|
input = options.preserveConsecutiveUppercase ? preserveConsecutiveUppercase(input, toLowerCase) : toLowerCase(input);
|
|
if (options.pascalCase) {
|
|
input = toUpperCase(input.charAt(0)) + input.slice(1);
|
|
}
|
|
return postProcess(input, toUpperCase);
|
|
}
|
|
|
|
// node_modules/quick-lru/index.js
|
|
var QuickLRU = class extends Map {
|
|
constructor(options = {}) {
|
|
super();
|
|
if (!(options.maxSize && options.maxSize > 0)) {
|
|
throw new TypeError("`maxSize` must be a number greater than 0");
|
|
}
|
|
if (typeof options.maxAge === "number" && options.maxAge === 0) {
|
|
throw new TypeError("`maxAge` must be a number greater than 0");
|
|
}
|
|
this.maxSize = options.maxSize;
|
|
this.maxAge = options.maxAge || Number.POSITIVE_INFINITY;
|
|
this.onEviction = options.onEviction;
|
|
this.cache = /* @__PURE__ */ new Map();
|
|
this.oldCache = /* @__PURE__ */ new Map();
|
|
this._size = 0;
|
|
}
|
|
// TODO: Use private class methods when targeting Node.js 16.
|
|
_emitEvictions(cache2) {
|
|
if (typeof this.onEviction !== "function") {
|
|
return;
|
|
}
|
|
for (const [key, item] of cache2) {
|
|
this.onEviction(key, item.value);
|
|
}
|
|
}
|
|
_deleteIfExpired(key, item) {
|
|
if (typeof item.expiry === "number" && item.expiry <= Date.now()) {
|
|
if (typeof this.onEviction === "function") {
|
|
this.onEviction(key, item.value);
|
|
}
|
|
return this.delete(key);
|
|
}
|
|
return false;
|
|
}
|
|
_getOrDeleteIfExpired(key, item) {
|
|
const deleted = this._deleteIfExpired(key, item);
|
|
if (deleted === false) {
|
|
return item.value;
|
|
}
|
|
}
|
|
_getItemValue(key, item) {
|
|
return item.expiry ? this._getOrDeleteIfExpired(key, item) : item.value;
|
|
}
|
|
_peek(key, cache2) {
|
|
const item = cache2.get(key);
|
|
return this._getItemValue(key, item);
|
|
}
|
|
_set(key, value) {
|
|
this.cache.set(key, value);
|
|
this._size++;
|
|
if (this._size >= this.maxSize) {
|
|
this._size = 0;
|
|
this._emitEvictions(this.oldCache);
|
|
this.oldCache = this.cache;
|
|
this.cache = /* @__PURE__ */ new Map();
|
|
}
|
|
}
|
|
_moveToRecent(key, item) {
|
|
this.oldCache.delete(key);
|
|
this._set(key, item);
|
|
}
|
|
*_entriesAscending() {
|
|
for (const item of this.oldCache) {
|
|
const [key, value] = item;
|
|
if (!this.cache.has(key)) {
|
|
const deleted = this._deleteIfExpired(key, value);
|
|
if (deleted === false) {
|
|
yield item;
|
|
}
|
|
}
|
|
}
|
|
for (const item of this.cache) {
|
|
const [key, value] = item;
|
|
const deleted = this._deleteIfExpired(key, value);
|
|
if (deleted === false) {
|
|
yield item;
|
|
}
|
|
}
|
|
}
|
|
get(key) {
|
|
if (this.cache.has(key)) {
|
|
const item = this.cache.get(key);
|
|
return this._getItemValue(key, item);
|
|
}
|
|
if (this.oldCache.has(key)) {
|
|
const item = this.oldCache.get(key);
|
|
if (this._deleteIfExpired(key, item) === false) {
|
|
this._moveToRecent(key, item);
|
|
return item.value;
|
|
}
|
|
}
|
|
}
|
|
set(key, value, { maxAge = this.maxAge } = {}) {
|
|
const expiry = typeof maxAge === "number" && maxAge !== Number.POSITIVE_INFINITY ? Date.now() + maxAge : void 0;
|
|
if (this.cache.has(key)) {
|
|
this.cache.set(key, {
|
|
value,
|
|
expiry
|
|
});
|
|
} else {
|
|
this._set(key, { value, expiry });
|
|
}
|
|
return this;
|
|
}
|
|
has(key) {
|
|
if (this.cache.has(key)) {
|
|
return !this._deleteIfExpired(key, this.cache.get(key));
|
|
}
|
|
if (this.oldCache.has(key)) {
|
|
return !this._deleteIfExpired(key, this.oldCache.get(key));
|
|
}
|
|
return false;
|
|
}
|
|
peek(key) {
|
|
if (this.cache.has(key)) {
|
|
return this._peek(key, this.cache);
|
|
}
|
|
if (this.oldCache.has(key)) {
|
|
return this._peek(key, this.oldCache);
|
|
}
|
|
}
|
|
delete(key) {
|
|
const deleted = this.cache.delete(key);
|
|
if (deleted) {
|
|
this._size--;
|
|
}
|
|
return this.oldCache.delete(key) || deleted;
|
|
}
|
|
clear() {
|
|
this.cache.clear();
|
|
this.oldCache.clear();
|
|
this._size = 0;
|
|
}
|
|
resize(newSize) {
|
|
if (!(newSize && newSize > 0)) {
|
|
throw new TypeError("`maxSize` must be a number greater than 0");
|
|
}
|
|
const items = [...this._entriesAscending()];
|
|
const removeCount = items.length - newSize;
|
|
if (removeCount < 0) {
|
|
this.cache = new Map(items);
|
|
this.oldCache = /* @__PURE__ */ new Map();
|
|
this._size = items.length;
|
|
} else {
|
|
if (removeCount > 0) {
|
|
this._emitEvictions(items.slice(0, removeCount));
|
|
}
|
|
this.oldCache = new Map(items.slice(removeCount));
|
|
this.cache = /* @__PURE__ */ new Map();
|
|
this._size = 0;
|
|
}
|
|
this.maxSize = newSize;
|
|
}
|
|
*keys() {
|
|
for (const [key] of this) {
|
|
yield key;
|
|
}
|
|
}
|
|
*values() {
|
|
for (const [, value] of this) {
|
|
yield value;
|
|
}
|
|
}
|
|
*[Symbol.iterator]() {
|
|
for (const item of this.cache) {
|
|
const [key, value] = item;
|
|
const deleted = this._deleteIfExpired(key, value);
|
|
if (deleted === false) {
|
|
yield [key, value.value];
|
|
}
|
|
}
|
|
for (const item of this.oldCache) {
|
|
const [key, value] = item;
|
|
if (!this.cache.has(key)) {
|
|
const deleted = this._deleteIfExpired(key, value);
|
|
if (deleted === false) {
|
|
yield [key, value.value];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
*entriesDescending() {
|
|
let items = [...this.cache];
|
|
for (let i = items.length - 1; i >= 0; --i) {
|
|
const item = items[i];
|
|
const [key, value] = item;
|
|
const deleted = this._deleteIfExpired(key, value);
|
|
if (deleted === false) {
|
|
yield [key, value.value];
|
|
}
|
|
}
|
|
items = [...this.oldCache];
|
|
for (let i = items.length - 1; i >= 0; --i) {
|
|
const item = items[i];
|
|
const [key, value] = item;
|
|
if (!this.cache.has(key)) {
|
|
const deleted = this._deleteIfExpired(key, value);
|
|
if (deleted === false) {
|
|
yield [key, value.value];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
*entriesAscending() {
|
|
for (const [key, value] of this._entriesAscending()) {
|
|
yield [key, value.value];
|
|
}
|
|
}
|
|
get size() {
|
|
if (!this._size) {
|
|
return this.oldCache.size;
|
|
}
|
|
let oldCacheSize = 0;
|
|
for (const key of this.oldCache.keys()) {
|
|
if (!this.cache.has(key)) {
|
|
oldCacheSize++;
|
|
}
|
|
}
|
|
return Math.min(this._size + oldCacheSize, this.maxSize);
|
|
}
|
|
entries() {
|
|
return this.entriesAscending();
|
|
}
|
|
forEach(callbackFunction, thisArgument = this) {
|
|
for (const [key, value] of this.entriesAscending()) {
|
|
callbackFunction.call(thisArgument, value, key, this);
|
|
}
|
|
}
|
|
get [Symbol.toStringTag]() {
|
|
return JSON.stringify([...this.entriesAscending()]);
|
|
}
|
|
};
|
|
|
|
// node_modules/camelcase-keys/index.js
|
|
var has = (array, key) => array.some((element) => {
|
|
if (typeof element === "string") {
|
|
return element === key;
|
|
}
|
|
element.lastIndex = 0;
|
|
return element.test(key);
|
|
});
|
|
var cache = new QuickLRU({ maxSize: 1e5 });
|
|
var isObject2 = (value) => typeof value === "object" && value !== null && !(value instanceof RegExp) && !(value instanceof Error) && !(value instanceof Date);
|
|
var transform = (input, options = {}) => {
|
|
if (!isObject2(input)) {
|
|
return input;
|
|
}
|
|
const {
|
|
exclude,
|
|
pascalCase = false,
|
|
stopPaths,
|
|
deep = false,
|
|
preserveConsecutiveUppercase: preserveConsecutiveUppercase2 = false
|
|
} = options;
|
|
const stopPathsSet = new Set(stopPaths);
|
|
const makeMapper = (parentPath) => (key, value) => {
|
|
if (deep && isObject2(value)) {
|
|
const path = parentPath === void 0 ? key : `${parentPath}.${key}`;
|
|
if (!stopPathsSet.has(path)) {
|
|
value = mapObject(value, makeMapper(path));
|
|
}
|
|
}
|
|
if (!(exclude && has(exclude, key))) {
|
|
const cacheKey = pascalCase ? `${key}_` : key;
|
|
if (cache.has(cacheKey)) {
|
|
key = cache.get(cacheKey);
|
|
} else {
|
|
const returnValue = camelCase(key, { pascalCase, locale: false, preserveConsecutiveUppercase: preserveConsecutiveUppercase2 });
|
|
if (key.length < 100) {
|
|
cache.set(cacheKey, returnValue);
|
|
}
|
|
key = returnValue;
|
|
}
|
|
}
|
|
return [key, value];
|
|
};
|
|
return mapObject(input, makeMapper(void 0));
|
|
};
|
|
function camelcaseKeys(input, options) {
|
|
if (Array.isArray(input)) {
|
|
return Object.keys(input).map((key) => transform(input[key], options));
|
|
}
|
|
return transform(input, options);
|
|
}
|
|
|
|
// node_modules/@logto/js/lib/consts/openid.js
|
|
var ReservedScope;
|
|
(function(ReservedScope2) {
|
|
ReservedScope2["OpenId"] = "openid";
|
|
ReservedScope2["OfflineAccess"] = "offline_access";
|
|
})(ReservedScope || (ReservedScope = {}));
|
|
var ReservedResource;
|
|
(function(ReservedResource2) {
|
|
ReservedResource2["Organization"] = "urn:logto:resource:organizations";
|
|
})(ReservedResource || (ReservedResource = {}));
|
|
var UserScope;
|
|
(function(UserScope2) {
|
|
UserScope2["Profile"] = "profile";
|
|
UserScope2["Email"] = "email";
|
|
UserScope2["Phone"] = "phone";
|
|
UserScope2["Address"] = "address";
|
|
UserScope2["CustomData"] = "custom_data";
|
|
UserScope2["Identities"] = "identities";
|
|
UserScope2["Roles"] = "roles";
|
|
UserScope2["Organizations"] = "urn:logto:scope:organizations";
|
|
UserScope2["OrganizationRoles"] = "urn:logto:scope:organization_roles";
|
|
UserScope2["Sessions"] = "urn:logto:scope:sessions";
|
|
})(UserScope || (UserScope = {}));
|
|
var idTokenClaims = Object.freeze({
|
|
[UserScope.Profile]: ["name", "picture", "username"],
|
|
[UserScope.Email]: ["email", "email_verified"],
|
|
[UserScope.Phone]: ["phone_number", "phone_number_verified"],
|
|
[UserScope.Address]: [],
|
|
[UserScope.Roles]: ["roles"],
|
|
[UserScope.Organizations]: ["organizations"],
|
|
[UserScope.OrganizationRoles]: ["organization_roles"],
|
|
[UserScope.CustomData]: [],
|
|
[UserScope.Identities]: [],
|
|
[UserScope.Sessions]: []
|
|
});
|
|
var userinfoClaims = Object.freeze({
|
|
[UserScope.Profile]: [],
|
|
[UserScope.Email]: [],
|
|
[UserScope.Phone]: [],
|
|
[UserScope.Address]: [],
|
|
[UserScope.Roles]: [],
|
|
[UserScope.Organizations]: [],
|
|
[UserScope.OrganizationRoles]: [],
|
|
[UserScope.CustomData]: ["custom_data"],
|
|
[UserScope.Identities]: ["identities"],
|
|
[UserScope.Sessions]: []
|
|
});
|
|
var userClaims = Object.freeze(
|
|
// Hard to infer type directly, use `as` for a workaround.
|
|
// eslint-disable-next-line no-restricted-syntax
|
|
Object.fromEntries(Object.values(UserScope).map((current) => [
|
|
current,
|
|
[...idTokenClaims[current], ...userinfoClaims[current]]
|
|
]))
|
|
);
|
|
|
|
// node_modules/@logto/js/lib/consts/index.js
|
|
var ContentType = {
|
|
formUrlEncoded: { "Content-Type": "application/x-www-form-urlencoded" }
|
|
};
|
|
var TokenGrantType;
|
|
(function(TokenGrantType2) {
|
|
TokenGrantType2["AuthorizationCode"] = "authorization_code";
|
|
TokenGrantType2["RefreshToken"] = "refresh_token";
|
|
})(TokenGrantType || (TokenGrantType = {}));
|
|
var QueryKey;
|
|
(function(QueryKey2) {
|
|
QueryKey2["ClientId"] = "client_id";
|
|
QueryKey2["Code"] = "code";
|
|
QueryKey2["CodeChallenge"] = "code_challenge";
|
|
QueryKey2["CodeChallengeMethod"] = "code_challenge_method";
|
|
QueryKey2["CodeVerifier"] = "code_verifier";
|
|
QueryKey2["Error"] = "error";
|
|
QueryKey2["ErrorDescription"] = "error_description";
|
|
QueryKey2["GrantType"] = "grant_type";
|
|
QueryKey2["IdToken"] = "id_token";
|
|
QueryKey2["IdTokenHint"] = "id_token_hint";
|
|
QueryKey2["LoginHint"] = "login_hint";
|
|
QueryKey2["PostLogoutRedirectUri"] = "post_logout_redirect_uri";
|
|
QueryKey2["Prompt"] = "prompt";
|
|
QueryKey2["RedirectUri"] = "redirect_uri";
|
|
QueryKey2["RefreshToken"] = "refresh_token";
|
|
QueryKey2["Resource"] = "resource";
|
|
QueryKey2["ResponseType"] = "response_type";
|
|
QueryKey2["Scope"] = "scope";
|
|
QueryKey2["State"] = "state";
|
|
QueryKey2["Token"] = "token";
|
|
QueryKey2["InteractionMode"] = "interaction_mode";
|
|
QueryKey2["OrganizationId"] = "organization_id";
|
|
QueryKey2["FirstScreen"] = "first_screen";
|
|
QueryKey2["Identifier"] = "identifier";
|
|
QueryKey2["DirectSignIn"] = "direct_sign_in";
|
|
QueryKey2["OneTimeToken"] = "one_time_token";
|
|
})(QueryKey || (QueryKey = {}));
|
|
var Prompt;
|
|
(function(Prompt2) {
|
|
Prompt2["None"] = "none";
|
|
Prompt2["Consent"] = "consent";
|
|
Prompt2["Login"] = "login";
|
|
})(Prompt || (Prompt = {}));
|
|
|
|
// node_modules/@logto/js/lib/core/fetch-token.js
|
|
var fetchTokenByAuthorizationCode = async ({ clientId, tokenEndpoint, redirectUri, codeVerifier, code, resource }, requester) => {
|
|
const parameters = new URLSearchParams();
|
|
parameters.append(QueryKey.ClientId, clientId);
|
|
parameters.append(QueryKey.Code, code);
|
|
parameters.append(QueryKey.CodeVerifier, codeVerifier);
|
|
parameters.append(QueryKey.RedirectUri, redirectUri);
|
|
parameters.append(QueryKey.GrantType, TokenGrantType.AuthorizationCode);
|
|
if (resource) {
|
|
parameters.append(QueryKey.Resource, resource);
|
|
}
|
|
const snakeCaseCodeTokenResponse = await requester(tokenEndpoint, {
|
|
method: "POST",
|
|
headers: ContentType.formUrlEncoded,
|
|
body: parameters.toString()
|
|
});
|
|
return camelcaseKeys(snakeCaseCodeTokenResponse);
|
|
};
|
|
var fetchTokenByRefreshToken = async (params, requester) => {
|
|
const { clientId, tokenEndpoint, refreshToken, resource, organizationId, scopes } = params;
|
|
const parameters = new URLSearchParams();
|
|
parameters.append(QueryKey.ClientId, clientId);
|
|
parameters.append(QueryKey.RefreshToken, refreshToken);
|
|
parameters.append(QueryKey.GrantType, TokenGrantType.RefreshToken);
|
|
if (resource) {
|
|
parameters.append(QueryKey.Resource, resource);
|
|
}
|
|
if (organizationId) {
|
|
parameters.append(QueryKey.OrganizationId, organizationId);
|
|
}
|
|
if (scopes?.length) {
|
|
parameters.append(QueryKey.Scope, scopes.join(" "));
|
|
}
|
|
const snakeCaseRefreshTokenTokenResponse = await requester(tokenEndpoint, {
|
|
method: "POST",
|
|
headers: ContentType.formUrlEncoded,
|
|
body: parameters.toString()
|
|
});
|
|
return camelcaseKeys(snakeCaseRefreshTokenTokenResponse);
|
|
};
|
|
|
|
// node_modules/@logto/js/lib/core/oidc-config.js
|
|
var discoveryPath = "/oidc/.well-known/openid-configuration";
|
|
var fetchOidcConfig = async (endpoint, requester) => camelcaseKeys(await requester(endpoint));
|
|
|
|
// node_modules/@logto/js/lib/core/revoke.js
|
|
var revoke = async (revocationEndpoint, clientId, token, requester) => requester(revocationEndpoint, {
|
|
method: "POST",
|
|
headers: ContentType.formUrlEncoded,
|
|
body: new URLSearchParams({
|
|
[QueryKey.ClientId]: clientId,
|
|
[QueryKey.Token]: token
|
|
}).toString()
|
|
});
|
|
|
|
// node_modules/@logto/js/lib/utils/scopes.js
|
|
var withReservedScopes = (originalScopes) => {
|
|
const reservedScopes = Object.values(ReservedScope);
|
|
const uniqueScopes = /* @__PURE__ */ new Set([...reservedScopes, UserScope.Profile, ...originalScopes ?? []]);
|
|
return Array.from(uniqueScopes).join(" ");
|
|
};
|
|
|
|
// node_modules/@logto/js/lib/core/sign-in.js
|
|
var codeChallengeMethod = "S256";
|
|
var responseType = "code";
|
|
var buildPrompt = (prompt) => {
|
|
if (Array.isArray(prompt)) {
|
|
return prompt.join(" ");
|
|
}
|
|
return prompt ?? Prompt.Consent;
|
|
};
|
|
var generateSignInUri = ({ authorizationEndpoint, clientId, redirectUri, codeChallenge, state, scopes, resources, prompt, firstScreen, identifiers: identifier, interactionMode, loginHint, directSignIn, oneTimeToken, extraParams, includeReservedScopes = true }) => {
|
|
const urlSearchParameters = new URLSearchParams({
|
|
[QueryKey.ClientId]: clientId,
|
|
[QueryKey.RedirectUri]: redirectUri,
|
|
[QueryKey.CodeChallenge]: codeChallenge,
|
|
[QueryKey.CodeChallengeMethod]: codeChallengeMethod,
|
|
[QueryKey.State]: state,
|
|
[QueryKey.ResponseType]: responseType,
|
|
[QueryKey.Prompt]: buildPrompt(prompt)
|
|
});
|
|
const computedScopes = includeReservedScopes ? withReservedScopes(scopes) : scopes?.join(" ");
|
|
if (computedScopes) {
|
|
urlSearchParameters.append(QueryKey.Scope, computedScopes);
|
|
}
|
|
if (loginHint) {
|
|
urlSearchParameters.append(QueryKey.LoginHint, loginHint);
|
|
}
|
|
if (directSignIn) {
|
|
urlSearchParameters.append(QueryKey.DirectSignIn, `${directSignIn.method}:${directSignIn.target}`);
|
|
}
|
|
for (const resource of resources ?? []) {
|
|
urlSearchParameters.append(QueryKey.Resource, resource);
|
|
}
|
|
if (firstScreen) {
|
|
urlSearchParameters.append(QueryKey.FirstScreen, firstScreen);
|
|
} else if (interactionMode) {
|
|
urlSearchParameters.append(QueryKey.InteractionMode, interactionMode);
|
|
}
|
|
if (identifier && identifier.length > 0) {
|
|
urlSearchParameters.append(QueryKey.Identifier, identifier.join(" "));
|
|
}
|
|
if (oneTimeToken) {
|
|
urlSearchParameters.append(QueryKey.OneTimeToken, oneTimeToken);
|
|
}
|
|
if (extraParams) {
|
|
for (const [key, value] of Object.entries(extraParams)) {
|
|
urlSearchParameters.append(key, value);
|
|
}
|
|
}
|
|
return `${authorizationEndpoint}?${urlSearchParameters.toString()}`;
|
|
};
|
|
|
|
// node_modules/@logto/js/lib/core/sign-out.js
|
|
var generateSignOutUri = ({ endSessionEndpoint, clientId, postLogoutRedirectUri }) => {
|
|
const urlSearchParameters = new URLSearchParams({ [QueryKey.ClientId]: clientId });
|
|
if (postLogoutRedirectUri) {
|
|
urlSearchParameters.append(QueryKey.PostLogoutRedirectUri, postLogoutRedirectUri);
|
|
}
|
|
return `${endSessionEndpoint}?${urlSearchParameters.toString()}`;
|
|
};
|
|
|
|
// node_modules/@logto/js/lib/core/user-info.js
|
|
var fetchUserInfo = async (userInfoEndpoint, accessToken, requester) => requester(userInfoEndpoint, {
|
|
headers: { Authorization: `Bearer ${accessToken}` }
|
|
});
|
|
|
|
// node_modules/@silverhand/essentials/lib/utilities/array.js
|
|
var deduplicate = (array) => [...new Set(array)];
|
|
|
|
// node_modules/@silverhand/essentials/lib/utilities/assertions.js
|
|
var notFalsy = (value) => Boolean(value);
|
|
|
|
// node_modules/@silverhand/essentials/lib/utilities/conditional.js
|
|
var conditional = (exp) => notFalsy(exp) ? exp : void 0;
|
|
var conditionalString = (exp) => notFalsy(exp) ? String(exp) : "";
|
|
|
|
// node_modules/@silverhand/essentials/lib/utilities/function.js
|
|
var isPromise = (value) => value !== null && (typeof value === "object" || typeof value === "function") && "then" in value && typeof value.then === "function";
|
|
var trySafe = (exec, onError) => {
|
|
try {
|
|
const unwrapped = typeof exec === "function" ? exec() : exec;
|
|
return isPromise(unwrapped) ? (
|
|
// eslint-disable-next-line promise/prefer-await-to-then
|
|
unwrapped.catch((error) => {
|
|
onError?.(error);
|
|
})
|
|
) : unwrapped;
|
|
} catch (error) {
|
|
onError?.(error);
|
|
}
|
|
};
|
|
|
|
// node_modules/@silverhand/essentials/lib/utilities/string.js
|
|
var replaceNonUrlSafeCharacters = (base64String) => base64String.replaceAll("+", "-").replaceAll("/", "_").replaceAll(/=+$/g, "");
|
|
var restoreNonUrlSafeCharacters = (base64String) => base64String.replaceAll("-", "+").replaceAll("_", "/");
|
|
var urlSafeBase64 = {
|
|
isSafe: (input) => /^[\w-]*$/.test(input),
|
|
encode: (rawString) => {
|
|
const encodedString = btoa(unescape(encodeURIComponent(rawString)));
|
|
return replaceNonUrlSafeCharacters(encodedString);
|
|
},
|
|
decode: (encodedString) => {
|
|
const nonUrlSafeEncodedString = restoreNonUrlSafeCharacters(encodedString);
|
|
return decodeURIComponent(escape(atob(nonUrlSafeEncodedString)));
|
|
},
|
|
replaceNonUrlSafeCharacters,
|
|
restoreNonUrlSafeCharacters
|
|
};
|
|
|
|
// node_modules/@silverhand/essentials/lib/utilities/url.js
|
|
var joinPath = (...segments) => {
|
|
const result = [];
|
|
for (const segment of segments.join("/").split("/")) {
|
|
if (!segment || segment === ".") {
|
|
continue;
|
|
}
|
|
if ([...segment].every((char) => char === ".")) {
|
|
result.pop();
|
|
continue;
|
|
}
|
|
result.push(segment);
|
|
}
|
|
return "/" + result.join("/");
|
|
};
|
|
var appendPath = (baseUrl, ...pathnames) => new URL(joinPath(baseUrl.pathname, ...pathnames), baseUrl);
|
|
|
|
// node_modules/@logto/js/lib/utils/arbitrary-object.js
|
|
var isArbitraryObject = (data) => typeof data === "object" && data !== null;
|
|
|
|
// node_modules/@logto/js/lib/utils/errors.js
|
|
var logtoErrorCodes = Object.freeze({
|
|
"id_token.invalid_iat": "Invalid issued at time in the ID token",
|
|
"id_token.invalid_token": "Invalid ID token",
|
|
"callback_uri_verification.redirect_uri_mismatched": "The callback URI mismatches the redirect URI.",
|
|
"callback_uri_verification.error_found": "Error found in the callback URI",
|
|
"callback_uri_verification.missing_state": "Missing state in the callback URI",
|
|
"callback_uri_verification.state_mismatched": "State mismatched in the callback URI",
|
|
"callback_uri_verification.missing_code": "Missing code in the callback URI",
|
|
crypto_subtle_unavailable: "Crypto.subtle is unavailable in insecure contexts (non-HTTPS).",
|
|
unexpected_response_error: "Unexpected response error from the server."
|
|
});
|
|
var LogtoError = class extends Error {
|
|
constructor(code, data) {
|
|
super(logtoErrorCodes[code]);
|
|
this.code = code;
|
|
this.data = data;
|
|
this.name = "LogtoError";
|
|
}
|
|
};
|
|
var isLogtoRequestErrorJson = (data) => {
|
|
if (!isArbitraryObject(data)) {
|
|
return false;
|
|
}
|
|
return typeof data.code === "string" && typeof data.message === "string";
|
|
};
|
|
var LogtoRequestError = class extends Error {
|
|
constructor(code, message2, cause) {
|
|
super(message2);
|
|
this.code = code;
|
|
this.cause = cause;
|
|
this.name = "LogtoRequestError";
|
|
}
|
|
};
|
|
var OidcError = class {
|
|
constructor(error, errorDescription) {
|
|
this.error = error;
|
|
this.errorDescription = errorDescription;
|
|
this.name = "OidcError";
|
|
}
|
|
};
|
|
|
|
// node_modules/@logto/js/lib/utils/callback-uri.js
|
|
var parseUriParameters = (uri) => {
|
|
const [, queryString = ""] = uri.split("?");
|
|
return new URLSearchParams(queryString);
|
|
};
|
|
var verifyAndParseCodeFromCallbackUri = (callbackUri, redirectUri, state) => {
|
|
if (!callbackUri.startsWith(redirectUri)) {
|
|
throw new LogtoError("callback_uri_verification.redirect_uri_mismatched");
|
|
}
|
|
const uriParameters = parseUriParameters(callbackUri);
|
|
const error = conditional(uriParameters.get(QueryKey.Error));
|
|
const errorDescription = conditional(uriParameters.get(QueryKey.ErrorDescription));
|
|
if (error) {
|
|
throw new LogtoError("callback_uri_verification.error_found", new OidcError(error, errorDescription));
|
|
}
|
|
const stateFromCallbackUri = uriParameters.get(QueryKey.State);
|
|
if (!stateFromCallbackUri) {
|
|
throw new LogtoError("callback_uri_verification.missing_state");
|
|
}
|
|
if (stateFromCallbackUri !== state) {
|
|
throw new LogtoError("callback_uri_verification.state_mismatched");
|
|
}
|
|
const code = uriParameters.get(QueryKey.Code);
|
|
if (!code) {
|
|
throw new LogtoError("callback_uri_verification.missing_code");
|
|
}
|
|
return code;
|
|
};
|
|
|
|
// node_modules/@logto/js/lib/utils/id-token.js
|
|
function assertIdTokenClaims(data) {
|
|
if (!isArbitraryObject(data)) {
|
|
throw new TypeError("IdToken is expected to be an object");
|
|
}
|
|
for (const key of ["iss", "sub", "aud"]) {
|
|
if (typeof data[key] !== "string") {
|
|
throw new TypeError(`At path: IdToken.${key}: expected a string`);
|
|
}
|
|
}
|
|
for (const key of ["exp", "iat"]) {
|
|
if (typeof data[key] !== "number") {
|
|
throw new TypeError(`At path: IdToken.${key}: expected a number`);
|
|
}
|
|
}
|
|
for (const key of ["at_hash", "name", "username", "picture", "email", "phone_number"]) {
|
|
if (data[key] === void 0) {
|
|
continue;
|
|
}
|
|
if (typeof data[key] !== "string" && data[key] !== null) {
|
|
throw new TypeError(`At path: IdToken.${key}: expected null or a string`);
|
|
}
|
|
}
|
|
for (const key of ["email_verified", "phone_number_verified"]) {
|
|
if (data[key] === void 0) {
|
|
continue;
|
|
}
|
|
if (typeof data[key] !== "boolean") {
|
|
throw new TypeError(`At path: IdToken.${key}: expected a boolean`);
|
|
}
|
|
}
|
|
}
|
|
var decodeIdToken = (token) => {
|
|
const { 1: encodedPayload } = token.split(".");
|
|
if (!encodedPayload) {
|
|
throw new LogtoError("id_token.invalid_token");
|
|
}
|
|
const json = urlSafeBase64.decode(encodedPayload);
|
|
const idTokenClaims2 = JSON.parse(json);
|
|
assertIdTokenClaims(idTokenClaims2);
|
|
return idTokenClaims2;
|
|
};
|
|
|
|
// node_modules/@logto/js/lib/utils/access-token.js
|
|
function assertAccessTokenClaims(data) {
|
|
if (!isArbitraryObject(data)) {
|
|
throw new TypeError("AccessToken is expected to be an object");
|
|
}
|
|
for (const key of ["jti", "iss", "sub", "aud", "client_id", "scope"]) {
|
|
if (data[key] === void 0) {
|
|
continue;
|
|
}
|
|
if (typeof data[key] !== "string" && data[key] !== null) {
|
|
throw new TypeError(`At path: AccessToken.${key}: expected null or a string`);
|
|
}
|
|
}
|
|
for (const key of ["exp", "iat"]) {
|
|
if (data[key] === void 0) {
|
|
continue;
|
|
}
|
|
if (typeof data[key] !== "number" && data[key] !== null) {
|
|
throw new TypeError(`At path: AccessToken.${key}: expected null or a number`);
|
|
}
|
|
}
|
|
}
|
|
var decodeAccessToken = (accessToken) => {
|
|
const { 1: encodedPayload } = accessToken.split(".");
|
|
if (!encodedPayload) {
|
|
return {};
|
|
}
|
|
const json = urlSafeBase64.decode(encodedPayload);
|
|
const accessTokenClaims = JSON.parse(json);
|
|
assertAccessTokenClaims(accessTokenClaims);
|
|
return accessTokenClaims;
|
|
};
|
|
|
|
// node_modules/jose/dist/browser/runtime/webcrypto.js
|
|
var webcrypto_default = crypto;
|
|
var isCryptoKey = (key) => key instanceof CryptoKey;
|
|
|
|
// node_modules/jose/dist/browser/lib/buffer_utils.js
|
|
var encoder = new TextEncoder();
|
|
var decoder = new TextDecoder();
|
|
var MAX_INT32 = 2 ** 32;
|
|
function concat(...buffers) {
|
|
const size = buffers.reduce((acc, { length }) => acc + length, 0);
|
|
const buf = new Uint8Array(size);
|
|
let i = 0;
|
|
for (const buffer of buffers) {
|
|
buf.set(buffer, i);
|
|
i += buffer.length;
|
|
}
|
|
return buf;
|
|
}
|
|
|
|
// node_modules/jose/dist/browser/runtime/base64url.js
|
|
var decodeBase64 = (encoded) => {
|
|
const binary = atob(encoded);
|
|
const bytes = new Uint8Array(binary.length);
|
|
for (let i = 0; i < binary.length; i++) {
|
|
bytes[i] = binary.charCodeAt(i);
|
|
}
|
|
return bytes;
|
|
};
|
|
var decode = (input) => {
|
|
let encoded = input;
|
|
if (encoded instanceof Uint8Array) {
|
|
encoded = decoder.decode(encoded);
|
|
}
|
|
encoded = encoded.replace(/-/g, "+").replace(/_/g, "/").replace(/\s/g, "");
|
|
try {
|
|
return decodeBase64(encoded);
|
|
} catch {
|
|
throw new TypeError("The input to be decoded is not correctly encoded.");
|
|
}
|
|
};
|
|
|
|
// node_modules/jose/dist/browser/util/errors.js
|
|
var JOSEError = class extends Error {
|
|
constructor(message2, options) {
|
|
super(message2, options);
|
|
this.code = "ERR_JOSE_GENERIC";
|
|
this.name = this.constructor.name;
|
|
Error.captureStackTrace?.(this, this.constructor);
|
|
}
|
|
};
|
|
JOSEError.code = "ERR_JOSE_GENERIC";
|
|
var JWTClaimValidationFailed = class extends JOSEError {
|
|
constructor(message2, payload, claim = "unspecified", reason = "unspecified") {
|
|
super(message2, { cause: { claim, reason, payload } });
|
|
this.code = "ERR_JWT_CLAIM_VALIDATION_FAILED";
|
|
this.claim = claim;
|
|
this.reason = reason;
|
|
this.payload = payload;
|
|
}
|
|
};
|
|
JWTClaimValidationFailed.code = "ERR_JWT_CLAIM_VALIDATION_FAILED";
|
|
var JWTExpired = class extends JOSEError {
|
|
constructor(message2, payload, claim = "unspecified", reason = "unspecified") {
|
|
super(message2, { cause: { claim, reason, payload } });
|
|
this.code = "ERR_JWT_EXPIRED";
|
|
this.claim = claim;
|
|
this.reason = reason;
|
|
this.payload = payload;
|
|
}
|
|
};
|
|
JWTExpired.code = "ERR_JWT_EXPIRED";
|
|
var JOSEAlgNotAllowed = class extends JOSEError {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.code = "ERR_JOSE_ALG_NOT_ALLOWED";
|
|
}
|
|
};
|
|
JOSEAlgNotAllowed.code = "ERR_JOSE_ALG_NOT_ALLOWED";
|
|
var JOSENotSupported = class extends JOSEError {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.code = "ERR_JOSE_NOT_SUPPORTED";
|
|
}
|
|
};
|
|
JOSENotSupported.code = "ERR_JOSE_NOT_SUPPORTED";
|
|
var JWEDecryptionFailed = class extends JOSEError {
|
|
constructor(message2 = "decryption operation failed", options) {
|
|
super(message2, options);
|
|
this.code = "ERR_JWE_DECRYPTION_FAILED";
|
|
}
|
|
};
|
|
JWEDecryptionFailed.code = "ERR_JWE_DECRYPTION_FAILED";
|
|
var JWEInvalid = class extends JOSEError {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.code = "ERR_JWE_INVALID";
|
|
}
|
|
};
|
|
JWEInvalid.code = "ERR_JWE_INVALID";
|
|
var JWSInvalid = class extends JOSEError {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.code = "ERR_JWS_INVALID";
|
|
}
|
|
};
|
|
JWSInvalid.code = "ERR_JWS_INVALID";
|
|
var JWTInvalid = class extends JOSEError {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.code = "ERR_JWT_INVALID";
|
|
}
|
|
};
|
|
JWTInvalid.code = "ERR_JWT_INVALID";
|
|
var JWKInvalid = class extends JOSEError {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.code = "ERR_JWK_INVALID";
|
|
}
|
|
};
|
|
JWKInvalid.code = "ERR_JWK_INVALID";
|
|
var JWKSInvalid = class extends JOSEError {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.code = "ERR_JWKS_INVALID";
|
|
}
|
|
};
|
|
JWKSInvalid.code = "ERR_JWKS_INVALID";
|
|
var JWKSNoMatchingKey = class extends JOSEError {
|
|
constructor(message2 = "no applicable key found in the JSON Web Key Set", options) {
|
|
super(message2, options);
|
|
this.code = "ERR_JWKS_NO_MATCHING_KEY";
|
|
}
|
|
};
|
|
JWKSNoMatchingKey.code = "ERR_JWKS_NO_MATCHING_KEY";
|
|
var JWKSMultipleMatchingKeys = class extends JOSEError {
|
|
constructor(message2 = "multiple matching keys found in the JSON Web Key Set", options) {
|
|
super(message2, options);
|
|
this.code = "ERR_JWKS_MULTIPLE_MATCHING_KEYS";
|
|
}
|
|
};
|
|
JWKSMultipleMatchingKeys.code = "ERR_JWKS_MULTIPLE_MATCHING_KEYS";
|
|
var JWKSTimeout = class extends JOSEError {
|
|
constructor(message2 = "request timed out", options) {
|
|
super(message2, options);
|
|
this.code = "ERR_JWKS_TIMEOUT";
|
|
}
|
|
};
|
|
JWKSTimeout.code = "ERR_JWKS_TIMEOUT";
|
|
var JWSSignatureVerificationFailed = class extends JOSEError {
|
|
constructor(message2 = "signature verification failed", options) {
|
|
super(message2, options);
|
|
this.code = "ERR_JWS_SIGNATURE_VERIFICATION_FAILED";
|
|
}
|
|
};
|
|
JWSSignatureVerificationFailed.code = "ERR_JWS_SIGNATURE_VERIFICATION_FAILED";
|
|
|
|
// node_modules/jose/dist/browser/lib/crypto_key.js
|
|
function unusable(name, prop = "algorithm.name") {
|
|
return new TypeError(`CryptoKey does not support this operation, its ${prop} must be ${name}`);
|
|
}
|
|
function isAlgorithm(algorithm, name) {
|
|
return algorithm.name === name;
|
|
}
|
|
function getHashLength(hash) {
|
|
return parseInt(hash.name.slice(4), 10);
|
|
}
|
|
function getNamedCurve(alg) {
|
|
switch (alg) {
|
|
case "ES256":
|
|
return "P-256";
|
|
case "ES384":
|
|
return "P-384";
|
|
case "ES512":
|
|
return "P-521";
|
|
default:
|
|
throw new Error("unreachable");
|
|
}
|
|
}
|
|
function checkUsage(key, usages) {
|
|
if (usages.length && !usages.some((expected) => key.usages.includes(expected))) {
|
|
let msg = "CryptoKey does not support this operation, its usages must include ";
|
|
if (usages.length > 2) {
|
|
const last = usages.pop();
|
|
msg += `one of ${usages.join(", ")}, or ${last}.`;
|
|
} else if (usages.length === 2) {
|
|
msg += `one of ${usages[0]} or ${usages[1]}.`;
|
|
} else {
|
|
msg += `${usages[0]}.`;
|
|
}
|
|
throw new TypeError(msg);
|
|
}
|
|
}
|
|
function checkSigCryptoKey(key, alg, ...usages) {
|
|
switch (alg) {
|
|
case "HS256":
|
|
case "HS384":
|
|
case "HS512": {
|
|
if (!isAlgorithm(key.algorithm, "HMAC"))
|
|
throw unusable("HMAC");
|
|
const expected = parseInt(alg.slice(2), 10);
|
|
const actual = getHashLength(key.algorithm.hash);
|
|
if (actual !== expected)
|
|
throw unusable(`SHA-${expected}`, "algorithm.hash");
|
|
break;
|
|
}
|
|
case "RS256":
|
|
case "RS384":
|
|
case "RS512": {
|
|
if (!isAlgorithm(key.algorithm, "RSASSA-PKCS1-v1_5"))
|
|
throw unusable("RSASSA-PKCS1-v1_5");
|
|
const expected = parseInt(alg.slice(2), 10);
|
|
const actual = getHashLength(key.algorithm.hash);
|
|
if (actual !== expected)
|
|
throw unusable(`SHA-${expected}`, "algorithm.hash");
|
|
break;
|
|
}
|
|
case "PS256":
|
|
case "PS384":
|
|
case "PS512": {
|
|
if (!isAlgorithm(key.algorithm, "RSA-PSS"))
|
|
throw unusable("RSA-PSS");
|
|
const expected = parseInt(alg.slice(2), 10);
|
|
const actual = getHashLength(key.algorithm.hash);
|
|
if (actual !== expected)
|
|
throw unusable(`SHA-${expected}`, "algorithm.hash");
|
|
break;
|
|
}
|
|
case "EdDSA": {
|
|
if (key.algorithm.name !== "Ed25519" && key.algorithm.name !== "Ed448") {
|
|
throw unusable("Ed25519 or Ed448");
|
|
}
|
|
break;
|
|
}
|
|
case "Ed25519": {
|
|
if (!isAlgorithm(key.algorithm, "Ed25519"))
|
|
throw unusable("Ed25519");
|
|
break;
|
|
}
|
|
case "ES256":
|
|
case "ES384":
|
|
case "ES512": {
|
|
if (!isAlgorithm(key.algorithm, "ECDSA"))
|
|
throw unusable("ECDSA");
|
|
const expected = getNamedCurve(alg);
|
|
const actual = key.algorithm.namedCurve;
|
|
if (actual !== expected)
|
|
throw unusable(expected, "algorithm.namedCurve");
|
|
break;
|
|
}
|
|
default:
|
|
throw new TypeError("CryptoKey does not support this operation");
|
|
}
|
|
checkUsage(key, usages);
|
|
}
|
|
|
|
// node_modules/jose/dist/browser/lib/invalid_key_input.js
|
|
function message(msg, actual, ...types2) {
|
|
types2 = types2.filter(Boolean);
|
|
if (types2.length > 2) {
|
|
const last = types2.pop();
|
|
msg += `one of type ${types2.join(", ")}, or ${last}.`;
|
|
} else if (types2.length === 2) {
|
|
msg += `one of type ${types2[0]} or ${types2[1]}.`;
|
|
} else {
|
|
msg += `of type ${types2[0]}.`;
|
|
}
|
|
if (actual == null) {
|
|
msg += ` Received ${actual}`;
|
|
} else if (typeof actual === "function" && actual.name) {
|
|
msg += ` Received function ${actual.name}`;
|
|
} else if (typeof actual === "object" && actual != null) {
|
|
if (actual.constructor?.name) {
|
|
msg += ` Received an instance of ${actual.constructor.name}`;
|
|
}
|
|
}
|
|
return msg;
|
|
}
|
|
var invalid_key_input_default = (actual, ...types2) => {
|
|
return message("Key must be ", actual, ...types2);
|
|
};
|
|
function withAlg(alg, actual, ...types2) {
|
|
return message(`Key for the ${alg} algorithm must be `, actual, ...types2);
|
|
}
|
|
|
|
// node_modules/jose/dist/browser/runtime/is_key_like.js
|
|
var is_key_like_default = (key) => {
|
|
if (isCryptoKey(key)) {
|
|
return true;
|
|
}
|
|
return key?.[Symbol.toStringTag] === "KeyObject";
|
|
};
|
|
var types = ["CryptoKey"];
|
|
|
|
// node_modules/jose/dist/browser/lib/is_disjoint.js
|
|
var isDisjoint = (...headers) => {
|
|
const sources = headers.filter(Boolean);
|
|
if (sources.length === 0 || sources.length === 1) {
|
|
return true;
|
|
}
|
|
let acc;
|
|
for (const header of sources) {
|
|
const parameters = Object.keys(header);
|
|
if (!acc || acc.size === 0) {
|
|
acc = new Set(parameters);
|
|
continue;
|
|
}
|
|
for (const parameter of parameters) {
|
|
if (acc.has(parameter)) {
|
|
return false;
|
|
}
|
|
acc.add(parameter);
|
|
}
|
|
}
|
|
return true;
|
|
};
|
|
var is_disjoint_default = isDisjoint;
|
|
|
|
// node_modules/jose/dist/browser/lib/is_object.js
|
|
function isObjectLike(value) {
|
|
return typeof value === "object" && value !== null;
|
|
}
|
|
function isObject4(input) {
|
|
if (!isObjectLike(input) || Object.prototype.toString.call(input) !== "[object Object]") {
|
|
return false;
|
|
}
|
|
if (Object.getPrototypeOf(input) === null) {
|
|
return true;
|
|
}
|
|
let proto = input;
|
|
while (Object.getPrototypeOf(proto) !== null) {
|
|
proto = Object.getPrototypeOf(proto);
|
|
}
|
|
return Object.getPrototypeOf(input) === proto;
|
|
}
|
|
|
|
// node_modules/jose/dist/browser/runtime/check_key_length.js
|
|
var check_key_length_default = (alg, key) => {
|
|
if (alg.startsWith("RS") || alg.startsWith("PS")) {
|
|
const { modulusLength } = key.algorithm;
|
|
if (typeof modulusLength !== "number" || modulusLength < 2048) {
|
|
throw new TypeError(`${alg} requires key modulusLength to be 2048 bits or larger`);
|
|
}
|
|
}
|
|
};
|
|
|
|
// node_modules/jose/dist/browser/lib/is_jwk.js
|
|
function isJWK(key) {
|
|
return isObject4(key) && typeof key.kty === "string";
|
|
}
|
|
function isPrivateJWK(key) {
|
|
return key.kty !== "oct" && typeof key.d === "string";
|
|
}
|
|
function isPublicJWK(key) {
|
|
return key.kty !== "oct" && typeof key.d === "undefined";
|
|
}
|
|
function isSecretJWK(key) {
|
|
return isJWK(key) && key.kty === "oct" && typeof key.k === "string";
|
|
}
|
|
|
|
// node_modules/jose/dist/browser/runtime/jwk_to_key.js
|
|
function subtleMapping(jwk) {
|
|
let algorithm;
|
|
let keyUsages;
|
|
switch (jwk.kty) {
|
|
case "RSA": {
|
|
switch (jwk.alg) {
|
|
case "PS256":
|
|
case "PS384":
|
|
case "PS512":
|
|
algorithm = { name: "RSA-PSS", hash: `SHA-${jwk.alg.slice(-3)}` };
|
|
keyUsages = jwk.d ? ["sign"] : ["verify"];
|
|
break;
|
|
case "RS256":
|
|
case "RS384":
|
|
case "RS512":
|
|
algorithm = { name: "RSASSA-PKCS1-v1_5", hash: `SHA-${jwk.alg.slice(-3)}` };
|
|
keyUsages = jwk.d ? ["sign"] : ["verify"];
|
|
break;
|
|
case "RSA-OAEP":
|
|
case "RSA-OAEP-256":
|
|
case "RSA-OAEP-384":
|
|
case "RSA-OAEP-512":
|
|
algorithm = {
|
|
name: "RSA-OAEP",
|
|
hash: `SHA-${parseInt(jwk.alg.slice(-3), 10) || 1}`
|
|
};
|
|
keyUsages = jwk.d ? ["decrypt", "unwrapKey"] : ["encrypt", "wrapKey"];
|
|
break;
|
|
default:
|
|
throw new JOSENotSupported('Invalid or unsupported JWK "alg" (Algorithm) Parameter value');
|
|
}
|
|
break;
|
|
}
|
|
case "EC": {
|
|
switch (jwk.alg) {
|
|
case "ES256":
|
|
algorithm = { name: "ECDSA", namedCurve: "P-256" };
|
|
keyUsages = jwk.d ? ["sign"] : ["verify"];
|
|
break;
|
|
case "ES384":
|
|
algorithm = { name: "ECDSA", namedCurve: "P-384" };
|
|
keyUsages = jwk.d ? ["sign"] : ["verify"];
|
|
break;
|
|
case "ES512":
|
|
algorithm = { name: "ECDSA", namedCurve: "P-521" };
|
|
keyUsages = jwk.d ? ["sign"] : ["verify"];
|
|
break;
|
|
case "ECDH-ES":
|
|
case "ECDH-ES+A128KW":
|
|
case "ECDH-ES+A192KW":
|
|
case "ECDH-ES+A256KW":
|
|
algorithm = { name: "ECDH", namedCurve: jwk.crv };
|
|
keyUsages = jwk.d ? ["deriveBits"] : [];
|
|
break;
|
|
default:
|
|
throw new JOSENotSupported('Invalid or unsupported JWK "alg" (Algorithm) Parameter value');
|
|
}
|
|
break;
|
|
}
|
|
case "OKP": {
|
|
switch (jwk.alg) {
|
|
case "Ed25519":
|
|
algorithm = { name: "Ed25519" };
|
|
keyUsages = jwk.d ? ["sign"] : ["verify"];
|
|
break;
|
|
case "EdDSA":
|
|
algorithm = { name: jwk.crv };
|
|
keyUsages = jwk.d ? ["sign"] : ["verify"];
|
|
break;
|
|
case "ECDH-ES":
|
|
case "ECDH-ES+A128KW":
|
|
case "ECDH-ES+A192KW":
|
|
case "ECDH-ES+A256KW":
|
|
algorithm = { name: jwk.crv };
|
|
keyUsages = jwk.d ? ["deriveBits"] : [];
|
|
break;
|
|
default:
|
|
throw new JOSENotSupported('Invalid or unsupported JWK "alg" (Algorithm) Parameter value');
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
throw new JOSENotSupported('Invalid or unsupported JWK "kty" (Key Type) Parameter value');
|
|
}
|
|
return { algorithm, keyUsages };
|
|
}
|
|
var parse = async (jwk) => {
|
|
if (!jwk.alg) {
|
|
throw new TypeError('"alg" argument is required when "jwk.alg" is not present');
|
|
}
|
|
const { algorithm, keyUsages } = subtleMapping(jwk);
|
|
const rest = [
|
|
algorithm,
|
|
jwk.ext ?? false,
|
|
jwk.key_ops ?? keyUsages
|
|
];
|
|
const keyData = { ...jwk };
|
|
delete keyData.alg;
|
|
delete keyData.use;
|
|
return webcrypto_default.subtle.importKey("jwk", keyData, ...rest);
|
|
};
|
|
var jwk_to_key_default = parse;
|
|
|
|
// node_modules/jose/dist/browser/runtime/normalize_key.js
|
|
var exportKeyValue = (k) => decode(k);
|
|
var privCache;
|
|
var pubCache;
|
|
var isKeyObject = (key) => {
|
|
return key?.[Symbol.toStringTag] === "KeyObject";
|
|
};
|
|
var importAndCache = async (cache2, key, jwk, alg, freeze = false) => {
|
|
let cached = cache2.get(key);
|
|
if (cached?.[alg]) {
|
|
return cached[alg];
|
|
}
|
|
const cryptoKey = await jwk_to_key_default({ ...jwk, alg });
|
|
if (freeze)
|
|
Object.freeze(key);
|
|
if (!cached) {
|
|
cache2.set(key, { [alg]: cryptoKey });
|
|
} else {
|
|
cached[alg] = cryptoKey;
|
|
}
|
|
return cryptoKey;
|
|
};
|
|
var normalizePublicKey = (key, alg) => {
|
|
if (isKeyObject(key)) {
|
|
let jwk = key.export({ format: "jwk" });
|
|
delete jwk.d;
|
|
delete jwk.dp;
|
|
delete jwk.dq;
|
|
delete jwk.p;
|
|
delete jwk.q;
|
|
delete jwk.qi;
|
|
if (jwk.k) {
|
|
return exportKeyValue(jwk.k);
|
|
}
|
|
pubCache || (pubCache = /* @__PURE__ */ new WeakMap());
|
|
return importAndCache(pubCache, key, jwk, alg);
|
|
}
|
|
if (isJWK(key)) {
|
|
if (key.k)
|
|
return decode(key.k);
|
|
pubCache || (pubCache = /* @__PURE__ */ new WeakMap());
|
|
const cryptoKey = importAndCache(pubCache, key, key, alg, true);
|
|
return cryptoKey;
|
|
}
|
|
return key;
|
|
};
|
|
var normalizePrivateKey = (key, alg) => {
|
|
if (isKeyObject(key)) {
|
|
let jwk = key.export({ format: "jwk" });
|
|
if (jwk.k) {
|
|
return exportKeyValue(jwk.k);
|
|
}
|
|
privCache || (privCache = /* @__PURE__ */ new WeakMap());
|
|
return importAndCache(privCache, key, jwk, alg);
|
|
}
|
|
if (isJWK(key)) {
|
|
if (key.k)
|
|
return decode(key.k);
|
|
privCache || (privCache = /* @__PURE__ */ new WeakMap());
|
|
const cryptoKey = importAndCache(privCache, key, key, alg, true);
|
|
return cryptoKey;
|
|
}
|
|
return key;
|
|
};
|
|
var normalize_key_default = { normalizePublicKey, normalizePrivateKey };
|
|
|
|
// node_modules/jose/dist/browser/key/import.js
|
|
async function importJWK(jwk, alg) {
|
|
if (!isObject4(jwk)) {
|
|
throw new TypeError("JWK must be an object");
|
|
}
|
|
alg || (alg = jwk.alg);
|
|
switch (jwk.kty) {
|
|
case "oct":
|
|
if (typeof jwk.k !== "string" || !jwk.k) {
|
|
throw new TypeError('missing "k" (Key Value) Parameter value');
|
|
}
|
|
return decode(jwk.k);
|
|
case "RSA":
|
|
if ("oth" in jwk && jwk.oth !== void 0) {
|
|
throw new JOSENotSupported('RSA JWK "oth" (Other Primes Info) Parameter value is not supported');
|
|
}
|
|
case "EC":
|
|
case "OKP":
|
|
return jwk_to_key_default({ ...jwk, alg });
|
|
default:
|
|
throw new JOSENotSupported('Unsupported "kty" (Key Type) Parameter value');
|
|
}
|
|
}
|
|
|
|
// node_modules/jose/dist/browser/lib/check_key_type.js
|
|
var tag = (key) => key?.[Symbol.toStringTag];
|
|
var jwkMatchesOp = (alg, key, usage) => {
|
|
if (key.use !== void 0 && key.use !== "sig") {
|
|
throw new TypeError("Invalid key for this operation, when present its use must be sig");
|
|
}
|
|
if (key.key_ops !== void 0 && key.key_ops.includes?.(usage) !== true) {
|
|
throw new TypeError(`Invalid key for this operation, when present its key_ops must include ${usage}`);
|
|
}
|
|
if (key.alg !== void 0 && key.alg !== alg) {
|
|
throw new TypeError(`Invalid key for this operation, when present its alg must be ${alg}`);
|
|
}
|
|
return true;
|
|
};
|
|
var symmetricTypeCheck = (alg, key, usage, allowJwk) => {
|
|
if (key instanceof Uint8Array)
|
|
return;
|
|
if (allowJwk && isJWK(key)) {
|
|
if (isSecretJWK(key) && jwkMatchesOp(alg, key, usage))
|
|
return;
|
|
throw new TypeError(`JSON Web Key for symmetric algorithms must have JWK "kty" (Key Type) equal to "oct" and the JWK "k" (Key Value) present`);
|
|
}
|
|
if (!is_key_like_default(key)) {
|
|
throw new TypeError(withAlg(alg, key, ...types, "Uint8Array", allowJwk ? "JSON Web Key" : null));
|
|
}
|
|
if (key.type !== "secret") {
|
|
throw new TypeError(`${tag(key)} instances for symmetric algorithms must be of type "secret"`);
|
|
}
|
|
};
|
|
var asymmetricTypeCheck = (alg, key, usage, allowJwk) => {
|
|
if (allowJwk && isJWK(key)) {
|
|
switch (usage) {
|
|
case "sign":
|
|
if (isPrivateJWK(key) && jwkMatchesOp(alg, key, usage))
|
|
return;
|
|
throw new TypeError(`JSON Web Key for this operation be a private JWK`);
|
|
case "verify":
|
|
if (isPublicJWK(key) && jwkMatchesOp(alg, key, usage))
|
|
return;
|
|
throw new TypeError(`JSON Web Key for this operation be a public JWK`);
|
|
}
|
|
}
|
|
if (!is_key_like_default(key)) {
|
|
throw new TypeError(withAlg(alg, key, ...types, allowJwk ? "JSON Web Key" : null));
|
|
}
|
|
if (key.type === "secret") {
|
|
throw new TypeError(`${tag(key)} instances for asymmetric algorithms must not be of type "secret"`);
|
|
}
|
|
if (usage === "sign" && key.type === "public") {
|
|
throw new TypeError(`${tag(key)} instances for asymmetric algorithm signing must be of type "private"`);
|
|
}
|
|
if (usage === "decrypt" && key.type === "public") {
|
|
throw new TypeError(`${tag(key)} instances for asymmetric algorithm decryption must be of type "private"`);
|
|
}
|
|
if (key.algorithm && usage === "verify" && key.type === "private") {
|
|
throw new TypeError(`${tag(key)} instances for asymmetric algorithm verifying must be of type "public"`);
|
|
}
|
|
if (key.algorithm && usage === "encrypt" && key.type === "private") {
|
|
throw new TypeError(`${tag(key)} instances for asymmetric algorithm encryption must be of type "public"`);
|
|
}
|
|
};
|
|
function checkKeyType(allowJwk, alg, key, usage) {
|
|
const symmetric = alg.startsWith("HS") || alg === "dir" || alg.startsWith("PBES2") || /^A\d{3}(?:GCM)?KW$/.test(alg);
|
|
if (symmetric) {
|
|
symmetricTypeCheck(alg, key, usage, allowJwk);
|
|
} else {
|
|
asymmetricTypeCheck(alg, key, usage, allowJwk);
|
|
}
|
|
}
|
|
var check_key_type_default = checkKeyType.bind(void 0, false);
|
|
var checkKeyTypeWithJwk = checkKeyType.bind(void 0, true);
|
|
|
|
// node_modules/jose/dist/browser/lib/validate_crit.js
|
|
function validateCrit(Err, recognizedDefault, recognizedOption, protectedHeader, joseHeader) {
|
|
if (joseHeader.crit !== void 0 && protectedHeader?.crit === void 0) {
|
|
throw new Err('"crit" (Critical) Header Parameter MUST be integrity protected');
|
|
}
|
|
if (!protectedHeader || protectedHeader.crit === void 0) {
|
|
return /* @__PURE__ */ new Set();
|
|
}
|
|
if (!Array.isArray(protectedHeader.crit) || protectedHeader.crit.length === 0 || protectedHeader.crit.some((input) => typeof input !== "string" || input.length === 0)) {
|
|
throw new Err('"crit" (Critical) Header Parameter MUST be an array of non-empty strings when present');
|
|
}
|
|
let recognized;
|
|
if (recognizedOption !== void 0) {
|
|
recognized = new Map([...Object.entries(recognizedOption), ...recognizedDefault.entries()]);
|
|
} else {
|
|
recognized = recognizedDefault;
|
|
}
|
|
for (const parameter of protectedHeader.crit) {
|
|
if (!recognized.has(parameter)) {
|
|
throw new JOSENotSupported(`Extension Header Parameter "${parameter}" is not recognized`);
|
|
}
|
|
if (joseHeader[parameter] === void 0) {
|
|
throw new Err(`Extension Header Parameter "${parameter}" is missing`);
|
|
}
|
|
if (recognized.get(parameter) && protectedHeader[parameter] === void 0) {
|
|
throw new Err(`Extension Header Parameter "${parameter}" MUST be integrity protected`);
|
|
}
|
|
}
|
|
return new Set(protectedHeader.crit);
|
|
}
|
|
var validate_crit_default = validateCrit;
|
|
|
|
// node_modules/jose/dist/browser/lib/validate_algorithms.js
|
|
var validateAlgorithms = (option, algorithms) => {
|
|
if (algorithms !== void 0 && (!Array.isArray(algorithms) || algorithms.some((s) => typeof s !== "string"))) {
|
|
throw new TypeError(`"${option}" option must be an array of strings`);
|
|
}
|
|
if (!algorithms) {
|
|
return void 0;
|
|
}
|
|
return new Set(algorithms);
|
|
};
|
|
var validate_algorithms_default = validateAlgorithms;
|
|
|
|
// node_modules/jose/dist/browser/runtime/subtle_dsa.js
|
|
function subtleDsa(alg, algorithm) {
|
|
const hash = `SHA-${alg.slice(-3)}`;
|
|
switch (alg) {
|
|
case "HS256":
|
|
case "HS384":
|
|
case "HS512":
|
|
return { hash, name: "HMAC" };
|
|
case "PS256":
|
|
case "PS384":
|
|
case "PS512":
|
|
return { hash, name: "RSA-PSS", saltLength: alg.slice(-3) >> 3 };
|
|
case "RS256":
|
|
case "RS384":
|
|
case "RS512":
|
|
return { hash, name: "RSASSA-PKCS1-v1_5" };
|
|
case "ES256":
|
|
case "ES384":
|
|
case "ES512":
|
|
return { hash, name: "ECDSA", namedCurve: algorithm.namedCurve };
|
|
case "Ed25519":
|
|
return { name: "Ed25519" };
|
|
case "EdDSA":
|
|
return { name: algorithm.name };
|
|
default:
|
|
throw new JOSENotSupported(`alg ${alg} is not supported either by JOSE or your javascript runtime`);
|
|
}
|
|
}
|
|
|
|
// node_modules/jose/dist/browser/runtime/get_sign_verify_key.js
|
|
async function getCryptoKey(alg, key, usage) {
|
|
if (usage === "sign") {
|
|
key = await normalize_key_default.normalizePrivateKey(key, alg);
|
|
}
|
|
if (usage === "verify") {
|
|
key = await normalize_key_default.normalizePublicKey(key, alg);
|
|
}
|
|
if (isCryptoKey(key)) {
|
|
checkSigCryptoKey(key, alg, usage);
|
|
return key;
|
|
}
|
|
if (key instanceof Uint8Array) {
|
|
if (!alg.startsWith("HS")) {
|
|
throw new TypeError(invalid_key_input_default(key, ...types));
|
|
}
|
|
return webcrypto_default.subtle.importKey("raw", key, { hash: `SHA-${alg.slice(-3)}`, name: "HMAC" }, false, [usage]);
|
|
}
|
|
throw new TypeError(invalid_key_input_default(key, ...types, "Uint8Array", "JSON Web Key"));
|
|
}
|
|
|
|
// node_modules/jose/dist/browser/runtime/verify.js
|
|
var verify = async (alg, key, signature, data) => {
|
|
const cryptoKey = await getCryptoKey(alg, key, "verify");
|
|
check_key_length_default(alg, cryptoKey);
|
|
const algorithm = subtleDsa(alg, cryptoKey.algorithm);
|
|
try {
|
|
return await webcrypto_default.subtle.verify(algorithm, cryptoKey, signature, data);
|
|
} catch {
|
|
return false;
|
|
}
|
|
};
|
|
var verify_default = verify;
|
|
|
|
// node_modules/jose/dist/browser/jws/flattened/verify.js
|
|
async function flattenedVerify(jws, key, options) {
|
|
if (!isObject4(jws)) {
|
|
throw new JWSInvalid("Flattened JWS must be an object");
|
|
}
|
|
if (jws.protected === void 0 && jws.header === void 0) {
|
|
throw new JWSInvalid('Flattened JWS must have either of the "protected" or "header" members');
|
|
}
|
|
if (jws.protected !== void 0 && typeof jws.protected !== "string") {
|
|
throw new JWSInvalid("JWS Protected Header incorrect type");
|
|
}
|
|
if (jws.payload === void 0) {
|
|
throw new JWSInvalid("JWS Payload missing");
|
|
}
|
|
if (typeof jws.signature !== "string") {
|
|
throw new JWSInvalid("JWS Signature missing or incorrect type");
|
|
}
|
|
if (jws.header !== void 0 && !isObject4(jws.header)) {
|
|
throw new JWSInvalid("JWS Unprotected Header incorrect type");
|
|
}
|
|
let parsedProt = {};
|
|
if (jws.protected) {
|
|
try {
|
|
const protectedHeader = decode(jws.protected);
|
|
parsedProt = JSON.parse(decoder.decode(protectedHeader));
|
|
} catch {
|
|
throw new JWSInvalid("JWS Protected Header is invalid");
|
|
}
|
|
}
|
|
if (!is_disjoint_default(parsedProt, jws.header)) {
|
|
throw new JWSInvalid("JWS Protected and JWS Unprotected Header Parameter names must be disjoint");
|
|
}
|
|
const joseHeader = {
|
|
...parsedProt,
|
|
...jws.header
|
|
};
|
|
const extensions = validate_crit_default(JWSInvalid, /* @__PURE__ */ new Map([["b64", true]]), options?.crit, parsedProt, joseHeader);
|
|
let b64 = true;
|
|
if (extensions.has("b64")) {
|
|
b64 = parsedProt.b64;
|
|
if (typeof b64 !== "boolean") {
|
|
throw new JWSInvalid('The "b64" (base64url-encode payload) Header Parameter must be a boolean');
|
|
}
|
|
}
|
|
const { alg } = joseHeader;
|
|
if (typeof alg !== "string" || !alg) {
|
|
throw new JWSInvalid('JWS "alg" (Algorithm) Header Parameter missing or invalid');
|
|
}
|
|
const algorithms = options && validate_algorithms_default("algorithms", options.algorithms);
|
|
if (algorithms && !algorithms.has(alg)) {
|
|
throw new JOSEAlgNotAllowed('"alg" (Algorithm) Header Parameter value not allowed');
|
|
}
|
|
if (b64) {
|
|
if (typeof jws.payload !== "string") {
|
|
throw new JWSInvalid("JWS Payload must be a string");
|
|
}
|
|
} else if (typeof jws.payload !== "string" && !(jws.payload instanceof Uint8Array)) {
|
|
throw new JWSInvalid("JWS Payload must be a string or an Uint8Array instance");
|
|
}
|
|
let resolvedKey = false;
|
|
if (typeof key === "function") {
|
|
key = await key(parsedProt, jws);
|
|
resolvedKey = true;
|
|
checkKeyTypeWithJwk(alg, key, "verify");
|
|
if (isJWK(key)) {
|
|
key = await importJWK(key, alg);
|
|
}
|
|
} else {
|
|
checkKeyTypeWithJwk(alg, key, "verify");
|
|
}
|
|
const data = concat(encoder.encode(jws.protected ?? ""), encoder.encode("."), typeof jws.payload === "string" ? encoder.encode(jws.payload) : jws.payload);
|
|
let signature;
|
|
try {
|
|
signature = decode(jws.signature);
|
|
} catch {
|
|
throw new JWSInvalid("Failed to base64url decode the signature");
|
|
}
|
|
const verified = await verify_default(alg, key, signature, data);
|
|
if (!verified) {
|
|
throw new JWSSignatureVerificationFailed();
|
|
}
|
|
let payload;
|
|
if (b64) {
|
|
try {
|
|
payload = decode(jws.payload);
|
|
} catch {
|
|
throw new JWSInvalid("Failed to base64url decode the payload");
|
|
}
|
|
} else if (typeof jws.payload === "string") {
|
|
payload = encoder.encode(jws.payload);
|
|
} else {
|
|
payload = jws.payload;
|
|
}
|
|
const result = { payload };
|
|
if (jws.protected !== void 0) {
|
|
result.protectedHeader = parsedProt;
|
|
}
|
|
if (jws.header !== void 0) {
|
|
result.unprotectedHeader = jws.header;
|
|
}
|
|
if (resolvedKey) {
|
|
return { ...result, key };
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// node_modules/jose/dist/browser/jws/compact/verify.js
|
|
async function compactVerify(jws, key, options) {
|
|
if (jws instanceof Uint8Array) {
|
|
jws = decoder.decode(jws);
|
|
}
|
|
if (typeof jws !== "string") {
|
|
throw new JWSInvalid("Compact JWS must be a string or Uint8Array");
|
|
}
|
|
const { 0: protectedHeader, 1: payload, 2: signature, length } = jws.split(".");
|
|
if (length !== 3) {
|
|
throw new JWSInvalid("Invalid Compact JWS");
|
|
}
|
|
const verified = await flattenedVerify({ payload, protected: protectedHeader, signature }, key, options);
|
|
const result = { payload: verified.payload, protectedHeader: verified.protectedHeader };
|
|
if (typeof key === "function") {
|
|
return { ...result, key: verified.key };
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// node_modules/jose/dist/browser/lib/epoch.js
|
|
var epoch_default = (date) => Math.floor(date.getTime() / 1e3);
|
|
|
|
// node_modules/jose/dist/browser/lib/secs.js
|
|
var minute = 60;
|
|
var hour = minute * 60;
|
|
var day = hour * 24;
|
|
var week = day * 7;
|
|
var year = day * 365.25;
|
|
var REGEX = /^(\+|\-)? ?(\d+|\d+\.\d+) ?(seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|weeks?|w|years?|yrs?|y)(?: (ago|from now))?$/i;
|
|
var secs_default = (str) => {
|
|
const matched = REGEX.exec(str);
|
|
if (!matched || matched[4] && matched[1]) {
|
|
throw new TypeError("Invalid time period format");
|
|
}
|
|
const value = parseFloat(matched[2]);
|
|
const unit = matched[3].toLowerCase();
|
|
let numericDate;
|
|
switch (unit) {
|
|
case "sec":
|
|
case "secs":
|
|
case "second":
|
|
case "seconds":
|
|
case "s":
|
|
numericDate = Math.round(value);
|
|
break;
|
|
case "minute":
|
|
case "minutes":
|
|
case "min":
|
|
case "mins":
|
|
case "m":
|
|
numericDate = Math.round(value * minute);
|
|
break;
|
|
case "hour":
|
|
case "hours":
|
|
case "hr":
|
|
case "hrs":
|
|
case "h":
|
|
numericDate = Math.round(value * hour);
|
|
break;
|
|
case "day":
|
|
case "days":
|
|
case "d":
|
|
numericDate = Math.round(value * day);
|
|
break;
|
|
case "week":
|
|
case "weeks":
|
|
case "w":
|
|
numericDate = Math.round(value * week);
|
|
break;
|
|
default:
|
|
numericDate = Math.round(value * year);
|
|
break;
|
|
}
|
|
if (matched[1] === "-" || matched[4] === "ago") {
|
|
return -numericDate;
|
|
}
|
|
return numericDate;
|
|
};
|
|
|
|
// node_modules/jose/dist/browser/lib/jwt_claims_set.js
|
|
var normalizeTyp = (value) => value.toLowerCase().replace(/^application\//, "");
|
|
var checkAudiencePresence = (audPayload, audOption) => {
|
|
if (typeof audPayload === "string") {
|
|
return audOption.includes(audPayload);
|
|
}
|
|
if (Array.isArray(audPayload)) {
|
|
return audOption.some(Set.prototype.has.bind(new Set(audPayload)));
|
|
}
|
|
return false;
|
|
};
|
|
var jwt_claims_set_default = (protectedHeader, encodedPayload, options = {}) => {
|
|
let payload;
|
|
try {
|
|
payload = JSON.parse(decoder.decode(encodedPayload));
|
|
} catch {
|
|
}
|
|
if (!isObject4(payload)) {
|
|
throw new JWTInvalid("JWT Claims Set must be a top-level JSON object");
|
|
}
|
|
const { typ } = options;
|
|
if (typ && (typeof protectedHeader.typ !== "string" || normalizeTyp(protectedHeader.typ) !== normalizeTyp(typ))) {
|
|
throw new JWTClaimValidationFailed('unexpected "typ" JWT header value', payload, "typ", "check_failed");
|
|
}
|
|
const { requiredClaims = [], issuer, subject, audience, maxTokenAge } = options;
|
|
const presenceCheck = [...requiredClaims];
|
|
if (maxTokenAge !== void 0)
|
|
presenceCheck.push("iat");
|
|
if (audience !== void 0)
|
|
presenceCheck.push("aud");
|
|
if (subject !== void 0)
|
|
presenceCheck.push("sub");
|
|
if (issuer !== void 0)
|
|
presenceCheck.push("iss");
|
|
for (const claim of new Set(presenceCheck.reverse())) {
|
|
if (!(claim in payload)) {
|
|
throw new JWTClaimValidationFailed(`missing required "${claim}" claim`, payload, claim, "missing");
|
|
}
|
|
}
|
|
if (issuer && !(Array.isArray(issuer) ? issuer : [issuer]).includes(payload.iss)) {
|
|
throw new JWTClaimValidationFailed('unexpected "iss" claim value', payload, "iss", "check_failed");
|
|
}
|
|
if (subject && payload.sub !== subject) {
|
|
throw new JWTClaimValidationFailed('unexpected "sub" claim value', payload, "sub", "check_failed");
|
|
}
|
|
if (audience && !checkAudiencePresence(payload.aud, typeof audience === "string" ? [audience] : audience)) {
|
|
throw new JWTClaimValidationFailed('unexpected "aud" claim value', payload, "aud", "check_failed");
|
|
}
|
|
let tolerance;
|
|
switch (typeof options.clockTolerance) {
|
|
case "string":
|
|
tolerance = secs_default(options.clockTolerance);
|
|
break;
|
|
case "number":
|
|
tolerance = options.clockTolerance;
|
|
break;
|
|
case "undefined":
|
|
tolerance = 0;
|
|
break;
|
|
default:
|
|
throw new TypeError("Invalid clockTolerance option type");
|
|
}
|
|
const { currentDate } = options;
|
|
const now = epoch_default(currentDate || /* @__PURE__ */ new Date());
|
|
if ((payload.iat !== void 0 || maxTokenAge) && typeof payload.iat !== "number") {
|
|
throw new JWTClaimValidationFailed('"iat" claim must be a number', payload, "iat", "invalid");
|
|
}
|
|
if (payload.nbf !== void 0) {
|
|
if (typeof payload.nbf !== "number") {
|
|
throw new JWTClaimValidationFailed('"nbf" claim must be a number', payload, "nbf", "invalid");
|
|
}
|
|
if (payload.nbf > now + tolerance) {
|
|
throw new JWTClaimValidationFailed('"nbf" claim timestamp check failed', payload, "nbf", "check_failed");
|
|
}
|
|
}
|
|
if (payload.exp !== void 0) {
|
|
if (typeof payload.exp !== "number") {
|
|
throw new JWTClaimValidationFailed('"exp" claim must be a number', payload, "exp", "invalid");
|
|
}
|
|
if (payload.exp <= now - tolerance) {
|
|
throw new JWTExpired('"exp" claim timestamp check failed', payload, "exp", "check_failed");
|
|
}
|
|
}
|
|
if (maxTokenAge) {
|
|
const age = now - payload.iat;
|
|
const max = typeof maxTokenAge === "number" ? maxTokenAge : secs_default(maxTokenAge);
|
|
if (age - tolerance > max) {
|
|
throw new JWTExpired('"iat" claim timestamp check failed (too far in the past)', payload, "iat", "check_failed");
|
|
}
|
|
if (age < 0 - tolerance) {
|
|
throw new JWTClaimValidationFailed('"iat" claim timestamp check failed (it should be in the past)', payload, "iat", "check_failed");
|
|
}
|
|
}
|
|
return payload;
|
|
};
|
|
|
|
// node_modules/jose/dist/browser/jwt/verify.js
|
|
async function jwtVerify(jwt, key, options) {
|
|
const verified = await compactVerify(jwt, key, options);
|
|
if (verified.protectedHeader.crit?.includes("b64") && verified.protectedHeader.b64 === false) {
|
|
throw new JWTInvalid("JWTs MUST NOT use unencoded payload");
|
|
}
|
|
const payload = jwt_claims_set_default(verified.protectedHeader, verified.payload, options);
|
|
const result = { payload, protectedHeader: verified.protectedHeader };
|
|
if (typeof key === "function") {
|
|
return { ...result, key: verified.key };
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// node_modules/jose/dist/browser/jwks/local.js
|
|
function getKtyFromAlg(alg) {
|
|
switch (typeof alg === "string" && alg.slice(0, 2)) {
|
|
case "RS":
|
|
case "PS":
|
|
return "RSA";
|
|
case "ES":
|
|
return "EC";
|
|
case "Ed":
|
|
return "OKP";
|
|
default:
|
|
throw new JOSENotSupported('Unsupported "alg" value for a JSON Web Key Set');
|
|
}
|
|
}
|
|
function isJWKSLike(jwks) {
|
|
return jwks && typeof jwks === "object" && Array.isArray(jwks.keys) && jwks.keys.every(isJWKLike);
|
|
}
|
|
function isJWKLike(key) {
|
|
return isObject4(key);
|
|
}
|
|
function clone(obj) {
|
|
if (typeof structuredClone === "function") {
|
|
return structuredClone(obj);
|
|
}
|
|
return JSON.parse(JSON.stringify(obj));
|
|
}
|
|
var LocalJWKSet = class {
|
|
constructor(jwks) {
|
|
this._cached = /* @__PURE__ */ new WeakMap();
|
|
if (!isJWKSLike(jwks)) {
|
|
throw new JWKSInvalid("JSON Web Key Set malformed");
|
|
}
|
|
this._jwks = clone(jwks);
|
|
}
|
|
async getKey(protectedHeader, token) {
|
|
const { alg, kid } = { ...protectedHeader, ...token?.header };
|
|
const kty = getKtyFromAlg(alg);
|
|
const candidates = this._jwks.keys.filter((jwk2) => {
|
|
let candidate = kty === jwk2.kty;
|
|
if (candidate && typeof kid === "string") {
|
|
candidate = kid === jwk2.kid;
|
|
}
|
|
if (candidate && typeof jwk2.alg === "string") {
|
|
candidate = alg === jwk2.alg;
|
|
}
|
|
if (candidate && typeof jwk2.use === "string") {
|
|
candidate = jwk2.use === "sig";
|
|
}
|
|
if (candidate && Array.isArray(jwk2.key_ops)) {
|
|
candidate = jwk2.key_ops.includes("verify");
|
|
}
|
|
if (candidate) {
|
|
switch (alg) {
|
|
case "ES256":
|
|
candidate = jwk2.crv === "P-256";
|
|
break;
|
|
case "ES256K":
|
|
candidate = jwk2.crv === "secp256k1";
|
|
break;
|
|
case "ES384":
|
|
candidate = jwk2.crv === "P-384";
|
|
break;
|
|
case "ES512":
|
|
candidate = jwk2.crv === "P-521";
|
|
break;
|
|
case "Ed25519":
|
|
candidate = jwk2.crv === "Ed25519";
|
|
break;
|
|
case "EdDSA":
|
|
candidate = jwk2.crv === "Ed25519" || jwk2.crv === "Ed448";
|
|
break;
|
|
}
|
|
}
|
|
return candidate;
|
|
});
|
|
const { 0: jwk, length } = candidates;
|
|
if (length === 0) {
|
|
throw new JWKSNoMatchingKey();
|
|
}
|
|
if (length !== 1) {
|
|
const error = new JWKSMultipleMatchingKeys();
|
|
const { _cached } = this;
|
|
error[Symbol.asyncIterator] = async function* () {
|
|
for (const jwk2 of candidates) {
|
|
try {
|
|
yield await importWithAlgCache(_cached, jwk2, alg);
|
|
} catch {
|
|
}
|
|
}
|
|
};
|
|
throw error;
|
|
}
|
|
return importWithAlgCache(this._cached, jwk, alg);
|
|
}
|
|
};
|
|
async function importWithAlgCache(cache2, jwk, alg) {
|
|
const cached = cache2.get(jwk) || cache2.set(jwk, {}).get(jwk);
|
|
if (cached[alg] === void 0) {
|
|
const key = await importJWK({ ...jwk, ext: true }, alg);
|
|
if (key instanceof Uint8Array || key.type !== "public") {
|
|
throw new JWKSInvalid("JSON Web Key Set members must be public keys");
|
|
}
|
|
cached[alg] = key;
|
|
}
|
|
return cached[alg];
|
|
}
|
|
function createLocalJWKSet(jwks) {
|
|
const set = new LocalJWKSet(jwks);
|
|
const localJWKSet = async (protectedHeader, token) => set.getKey(protectedHeader, token);
|
|
Object.defineProperties(localJWKSet, {
|
|
jwks: {
|
|
value: () => clone(set._jwks),
|
|
enumerable: true,
|
|
configurable: false,
|
|
writable: false
|
|
}
|
|
});
|
|
return localJWKSet;
|
|
}
|
|
|
|
// node_modules/jose/dist/browser/runtime/fetch_jwks.js
|
|
var fetchJwks = async (url, timeout, options) => {
|
|
let controller;
|
|
let id;
|
|
let timedOut = false;
|
|
if (typeof AbortController === "function") {
|
|
controller = new AbortController();
|
|
id = setTimeout(() => {
|
|
timedOut = true;
|
|
controller.abort();
|
|
}, timeout);
|
|
}
|
|
const response = await fetch(url.href, {
|
|
signal: controller ? controller.signal : void 0,
|
|
redirect: "manual",
|
|
headers: options.headers
|
|
}).catch((err) => {
|
|
if (timedOut)
|
|
throw new JWKSTimeout();
|
|
throw err;
|
|
});
|
|
if (id !== void 0)
|
|
clearTimeout(id);
|
|
if (response.status !== 200) {
|
|
throw new JOSEError("Expected 200 OK from the JSON Web Key Set HTTP response");
|
|
}
|
|
try {
|
|
return await response.json();
|
|
} catch {
|
|
throw new JOSEError("Failed to parse the JSON Web Key Set HTTP response as JSON");
|
|
}
|
|
};
|
|
var fetch_jwks_default = fetchJwks;
|
|
|
|
// node_modules/jose/dist/browser/jwks/remote.js
|
|
function isCloudflareWorkers() {
|
|
return typeof WebSocketPair !== "undefined" || typeof navigator !== "undefined" && navigator.userAgent === "Cloudflare-Workers" || typeof EdgeRuntime !== "undefined" && EdgeRuntime === "vercel";
|
|
}
|
|
var USER_AGENT;
|
|
if (typeof navigator === "undefined" || !navigator.userAgent?.startsWith?.("Mozilla/5.0 ")) {
|
|
const NAME = "jose";
|
|
const VERSION = "v5.10.0";
|
|
USER_AGENT = `${NAME}/${VERSION}`;
|
|
}
|
|
var jwksCache = /* @__PURE__ */ Symbol();
|
|
function isFreshJwksCache(input, cacheMaxAge) {
|
|
if (typeof input !== "object" || input === null) {
|
|
return false;
|
|
}
|
|
if (!("uat" in input) || typeof input.uat !== "number" || Date.now() - input.uat >= cacheMaxAge) {
|
|
return false;
|
|
}
|
|
if (!("jwks" in input) || !isObject4(input.jwks) || !Array.isArray(input.jwks.keys) || !Array.prototype.every.call(input.jwks.keys, isObject4)) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
var RemoteJWKSet = class {
|
|
constructor(url, options) {
|
|
if (!(url instanceof URL)) {
|
|
throw new TypeError("url must be an instance of URL");
|
|
}
|
|
this._url = new URL(url.href);
|
|
this._options = { agent: options?.agent, headers: options?.headers };
|
|
this._timeoutDuration = typeof options?.timeoutDuration === "number" ? options?.timeoutDuration : 5e3;
|
|
this._cooldownDuration = typeof options?.cooldownDuration === "number" ? options?.cooldownDuration : 3e4;
|
|
this._cacheMaxAge = typeof options?.cacheMaxAge === "number" ? options?.cacheMaxAge : 6e5;
|
|
if (options?.[jwksCache] !== void 0) {
|
|
this._cache = options?.[jwksCache];
|
|
if (isFreshJwksCache(options?.[jwksCache], this._cacheMaxAge)) {
|
|
this._jwksTimestamp = this._cache.uat;
|
|
this._local = createLocalJWKSet(this._cache.jwks);
|
|
}
|
|
}
|
|
}
|
|
coolingDown() {
|
|
return typeof this._jwksTimestamp === "number" ? Date.now() < this._jwksTimestamp + this._cooldownDuration : false;
|
|
}
|
|
fresh() {
|
|
return typeof this._jwksTimestamp === "number" ? Date.now() < this._jwksTimestamp + this._cacheMaxAge : false;
|
|
}
|
|
async getKey(protectedHeader, token) {
|
|
if (!this._local || !this.fresh()) {
|
|
await this.reload();
|
|
}
|
|
try {
|
|
return await this._local(protectedHeader, token);
|
|
} catch (err) {
|
|
if (err instanceof JWKSNoMatchingKey) {
|
|
if (this.coolingDown() === false) {
|
|
await this.reload();
|
|
return this._local(protectedHeader, token);
|
|
}
|
|
}
|
|
throw err;
|
|
}
|
|
}
|
|
async reload() {
|
|
if (this._pendingFetch && isCloudflareWorkers()) {
|
|
this._pendingFetch = void 0;
|
|
}
|
|
const headers = new Headers(this._options.headers);
|
|
if (USER_AGENT && !headers.has("User-Agent")) {
|
|
headers.set("User-Agent", USER_AGENT);
|
|
this._options.headers = Object.fromEntries(headers.entries());
|
|
}
|
|
this._pendingFetch || (this._pendingFetch = fetch_jwks_default(this._url, this._timeoutDuration, this._options).then((json) => {
|
|
this._local = createLocalJWKSet(json);
|
|
if (this._cache) {
|
|
this._cache.uat = Date.now();
|
|
this._cache.jwks = json;
|
|
}
|
|
this._jwksTimestamp = Date.now();
|
|
this._pendingFetch = void 0;
|
|
}).catch((err) => {
|
|
this._pendingFetch = void 0;
|
|
throw err;
|
|
}));
|
|
await this._pendingFetch;
|
|
}
|
|
};
|
|
function createRemoteJWKSet(url, options) {
|
|
const set = new RemoteJWKSet(url, options);
|
|
const remoteJWKSet = async (protectedHeader, token) => set.getKey(protectedHeader, token);
|
|
Object.defineProperties(remoteJWKSet, {
|
|
coolingDown: {
|
|
get: () => set.coolingDown(),
|
|
enumerable: true,
|
|
configurable: false
|
|
},
|
|
fresh: {
|
|
get: () => set.fresh(),
|
|
enumerable: true,
|
|
configurable: false
|
|
},
|
|
reload: {
|
|
value: () => set.reload(),
|
|
enumerable: true,
|
|
configurable: false,
|
|
writable: false
|
|
},
|
|
reloading: {
|
|
get: () => !!set._pendingFetch,
|
|
enumerable: true,
|
|
configurable: false
|
|
},
|
|
jwks: {
|
|
value: () => set._local?.jwks(),
|
|
enumerable: true,
|
|
configurable: false,
|
|
writable: false
|
|
}
|
|
});
|
|
return remoteJWKSet;
|
|
}
|
|
|
|
// node_modules/@logto/client/lib/adapter/defaults.js
|
|
var defaultClockTolerance = 300;
|
|
var verifyIdToken = async (idToken, clientId, issuer, jwks, clockTolerance = defaultClockTolerance) => {
|
|
const result = await jwtVerify(idToken, jwks, { audience: clientId, issuer, clockTolerance });
|
|
if (Math.abs((result.payload.iat ?? 0) - Date.now() / 1e3) > clockTolerance) {
|
|
throw new LogtoError("id_token.invalid_iat");
|
|
}
|
|
};
|
|
var DefaultJwtVerifier = class {
|
|
constructor(client, clockTolerance = defaultClockTolerance) {
|
|
this.client = client;
|
|
this.clockTolerance = clockTolerance;
|
|
}
|
|
async verifyIdToken(idToken) {
|
|
const { appId } = this.client.logtoConfig;
|
|
const { issuer, jwksUri } = await this.client.getOidcConfig();
|
|
this.getJwtVerifyGetKey ||= createRemoteJWKSet(new URL(jwksUri));
|
|
await verifyIdToken(idToken, appId, issuer, this.getJwtVerifyGetKey, this.clockTolerance);
|
|
}
|
|
};
|
|
|
|
// node_modules/@logto/client/lib/adapter/types.js
|
|
var PersistKey;
|
|
(function(PersistKey2) {
|
|
PersistKey2["IdToken"] = "idToken";
|
|
PersistKey2["RefreshToken"] = "refreshToken";
|
|
PersistKey2["AccessToken"] = "accessToken";
|
|
PersistKey2["SignInSession"] = "signInSession";
|
|
})(PersistKey || (PersistKey = {}));
|
|
var CacheKey;
|
|
(function(CacheKey2) {
|
|
CacheKey2["OpenidConfig"] = "openidConfiguration";
|
|
CacheKey2["Jwks"] = "jwks";
|
|
})(CacheKey || (CacheKey = {}));
|
|
|
|
// node_modules/@logto/client/lib/adapter/index.js
|
|
var ClientAdapterInstance = class {
|
|
/* END OF IMPLEMENTATION */
|
|
constructor(adapter) {
|
|
Object.assign(this, adapter);
|
|
}
|
|
async setStorageItem(key, value) {
|
|
if (!value) {
|
|
await this.storage.removeItem(key);
|
|
return;
|
|
}
|
|
await this.storage.setItem(key, value);
|
|
}
|
|
/**
|
|
* Try to get the string value from the cache and parse as JSON.
|
|
* Return the parsed value if it is an object, return `undefined` otherwise.
|
|
*
|
|
* @param key The cache key to get value from.
|
|
*/
|
|
async getCachedObject(key) {
|
|
const cached = await trySafe(async () => {
|
|
const data = await this.unstable_cache?.getItem(key);
|
|
return conditional(data && JSON.parse(data));
|
|
});
|
|
if (cached && typeof cached === "object") {
|
|
return cached;
|
|
}
|
|
}
|
|
/**
|
|
* Try to get the value from the cache first, if it doesn't exist in cache,
|
|
* run the getter function and store the result into cache.
|
|
*
|
|
* @param key The cache key to get value from.
|
|
*/
|
|
async getWithCache(key, getter) {
|
|
const cached = await this.getCachedObject(key);
|
|
if (cached) {
|
|
return cached;
|
|
}
|
|
const result = await getter();
|
|
await this.unstable_cache?.setItem(key, JSON.stringify(result));
|
|
return result;
|
|
}
|
|
};
|
|
|
|
// node_modules/@logto/client/lib/errors.js
|
|
var logtoClientErrorCodes = Object.freeze({
|
|
"sign_in_session.invalid": "Invalid sign-in session.",
|
|
"sign_in_session.not_found": "Sign-in session not found.",
|
|
not_authenticated: "Not authenticated.",
|
|
fetch_user_info_failed: "Unable to fetch user info. The access token may be invalid.",
|
|
user_cancelled: "The user cancelled the action.",
|
|
missing_scope_organizations: `The \`${UserScope.Organizations}\` scope is required`
|
|
});
|
|
var LogtoClientError = class extends Error {
|
|
constructor(code, data) {
|
|
super(logtoClientErrorCodes[code]);
|
|
this.name = "LogtoClientError";
|
|
this.code = code;
|
|
this.data = data;
|
|
}
|
|
};
|
|
|
|
// node_modules/@logto/client/lib/types/index.js
|
|
var normalizeLogtoConfig = (config) => {
|
|
const { prompt = Prompt.Consent, scopes = [], resources, ...rest } = config;
|
|
const includeReservedScopes = config.includeReservedScopes ?? true;
|
|
return {
|
|
...rest,
|
|
prompt,
|
|
scopes: includeReservedScopes ? withReservedScopes(scopes).split(" ") : scopes,
|
|
resources: scopes.includes(UserScope.Organizations) ? deduplicate([...resources ?? [], ReservedResource.Organization]) : resources,
|
|
includeReservedScopes
|
|
};
|
|
};
|
|
var isLogtoSignInSessionItem = (data) => {
|
|
if (!isArbitraryObject(data)) {
|
|
return false;
|
|
}
|
|
return ["redirectUri", "codeVerifier", "state"].every((key) => typeof data[key] === "string");
|
|
};
|
|
var isLogtoAccessTokenMap = (data) => {
|
|
if (!isArbitraryObject(data)) {
|
|
return false;
|
|
}
|
|
return Object.values(data).every((value) => {
|
|
if (!isArbitraryObject(value)) {
|
|
return false;
|
|
}
|
|
return typeof value.token === "string" && typeof value.scope === "string" && typeof value.expiresAt === "number";
|
|
});
|
|
};
|
|
|
|
// node_modules/@logto/client/lib/utils/index.js
|
|
var buildAccessTokenKey = (resource = "", organizationId, scopes = []) => `${scopes.slice().sort().join(" ")}@${resource}${conditionalString(organizationId && `#${organizationId}`)}`;
|
|
var getDiscoveryEndpoint = (endpoint) => appendPath(new URL(endpoint), discoveryPath).toString();
|
|
|
|
// node_modules/@logto/client/lib/utils/memoize.js
|
|
function memoize(run) {
|
|
const promiseCache = /* @__PURE__ */ new Map();
|
|
const memoized = async function(...args) {
|
|
const promiseKey = JSON.stringify(args);
|
|
const cachedPromise = promiseCache.get(promiseKey);
|
|
if (cachedPromise) {
|
|
return cachedPromise;
|
|
}
|
|
const promise = (async () => {
|
|
try {
|
|
return await run.apply(this, args);
|
|
} finally {
|
|
promiseCache.delete(promiseKey);
|
|
}
|
|
})();
|
|
promiseCache.set(promiseKey, promise);
|
|
return promise;
|
|
};
|
|
return memoized;
|
|
}
|
|
|
|
// node_modules/@logto/client/lib/utils/once.js
|
|
function once2(function_) {
|
|
let called = false;
|
|
let result;
|
|
return function(...args) {
|
|
if (!called) {
|
|
called = true;
|
|
result = function_.apply(this, args);
|
|
}
|
|
return result;
|
|
};
|
|
}
|
|
|
|
// node_modules/@logto/client/lib/client.js
|
|
var StandardLogtoClient = class {
|
|
get jwtVerifier() {
|
|
return this.jwtVerifierInstance;
|
|
}
|
|
constructor(logtoConfig, adapter, buildJwtVerifier) {
|
|
this.getOidcConfig = once2(this.#getOidcConfig);
|
|
this.getAccessToken = memoize(this.#getAccessToken);
|
|
this.getOrganizationToken = memoize(this.#getOrganizationToken);
|
|
this.clearAccessToken = memoize(this.#clearAccessToken);
|
|
this.clearAllTokens = memoize(this.#clearAllTokens);
|
|
this.handleSignInCallback = memoize(this.#handleSignInCallback);
|
|
this.accessTokenMap = /* @__PURE__ */ new Map();
|
|
this.logtoConfig = normalizeLogtoConfig(logtoConfig);
|
|
this.adapter = new ClientAdapterInstance(adapter);
|
|
this.jwtVerifierInstance = buildJwtVerifier(this);
|
|
void this.loadAccessTokenMap();
|
|
}
|
|
/**
|
|
* Set the JWT verifier for the client.
|
|
* @param buildJwtVerifier The JWT verifier instance or a function that returns the JWT verifier instance.
|
|
*/
|
|
setJwtVerifier(buildJwtVerifier) {
|
|
this.jwtVerifierInstance = typeof buildJwtVerifier === "function" ? buildJwtVerifier(this) : buildJwtVerifier;
|
|
}
|
|
/**
|
|
* Check if the user is authenticated by checking if the ID token exists.
|
|
*/
|
|
async isAuthenticated() {
|
|
return Boolean(await this.getIdToken());
|
|
}
|
|
/**
|
|
* Get the Refresh Token from the storage.
|
|
*/
|
|
async getRefreshToken() {
|
|
return this.adapter.storage.getItem("refreshToken");
|
|
}
|
|
/**
|
|
* Get the ID Token from the storage. If you want to get the ID Token claims,
|
|
* use {@link getIdTokenClaims} instead.
|
|
*/
|
|
async getIdToken() {
|
|
return this.adapter.storage.getItem("idToken");
|
|
}
|
|
/**
|
|
* Get the ID Token claims.
|
|
*/
|
|
async getIdTokenClaims() {
|
|
const idToken = await this.getIdToken();
|
|
if (!idToken) {
|
|
throw new LogtoClientError("not_authenticated", "ID token not found");
|
|
}
|
|
return decodeIdToken(idToken);
|
|
}
|
|
/**
|
|
* Get the access token claims for the specified resource.
|
|
*
|
|
* @param resource The resource that the access token is granted for. If not
|
|
* specified, the access token will be used for OpenID Connect or the default
|
|
* resource, as specified in the Logto Console.
|
|
*/
|
|
async getAccessTokenClaims(resource) {
|
|
const accessToken = await this.getAccessToken(resource);
|
|
return decodeAccessToken(accessToken);
|
|
}
|
|
/**
|
|
* Get the organization token claims for the specified organization.
|
|
*
|
|
* @param organizationId The ID of the organization that the access token is granted for.
|
|
*/
|
|
async getOrganizationTokenClaims(organizationId) {
|
|
const accessToken = await this.getOrganizationToken(organizationId);
|
|
return decodeAccessToken(accessToken);
|
|
}
|
|
/**
|
|
* Get the user information from the Userinfo Endpoint.
|
|
*
|
|
* Note the Userinfo Endpoint will return more claims than the ID Token. See
|
|
* {@link https://docs.logto.io/docs/recipes/integrate-logto/vanilla-js/#fetch-user-information | Fetch user information}
|
|
* for more information.
|
|
*
|
|
* @returns The user information.
|
|
* @throws LogtoClientError if the user is not authenticated.
|
|
*/
|
|
async fetchUserInfo() {
|
|
const { userinfoEndpoint } = await this.getOidcConfig();
|
|
const accessToken = await this.getAccessToken();
|
|
if (!accessToken) {
|
|
throw new LogtoClientError("fetch_user_info_failed");
|
|
}
|
|
return fetchUserInfo(userinfoEndpoint, accessToken, this.adapter.requester);
|
|
}
|
|
async signIn(options, mode, hint) {
|
|
const { redirectUri: redirectUriUrl, postRedirectUri: postRedirectUriUrl, firstScreen, identifiers, interactionMode, loginHint, directSignIn, extraParams, prompt, clearTokens } = typeof options === "string" || options instanceof URL ? {
|
|
redirectUri: options,
|
|
postRedirectUri: void 0,
|
|
firstScreen: void 0,
|
|
identifiers: void 0,
|
|
interactionMode: mode,
|
|
loginHint: hint,
|
|
directSignIn: void 0,
|
|
extraParams: void 0,
|
|
prompt: void 0,
|
|
clearTokens: true
|
|
} : options;
|
|
const redirectUri = redirectUriUrl.toString();
|
|
const postRedirectUri = postRedirectUriUrl?.toString();
|
|
const { appId: clientId, prompt: promptViaConfig, resources, scopes, includeReservedScopes } = this.logtoConfig;
|
|
const { authorizationEndpoint } = await this.getOidcConfig();
|
|
const [codeVerifier, state] = await Promise.all([
|
|
this.adapter.generateCodeVerifier(),
|
|
this.adapter.generateState()
|
|
]);
|
|
const codeChallenge = await this.adapter.generateCodeChallenge(codeVerifier);
|
|
const signInUri = generateSignInUri({
|
|
authorizationEndpoint,
|
|
clientId,
|
|
redirectUri: redirectUri.toString(),
|
|
codeChallenge,
|
|
state,
|
|
scopes,
|
|
resources,
|
|
prompt: prompt ?? promptViaConfig,
|
|
firstScreen,
|
|
identifiers,
|
|
interactionMode,
|
|
loginHint,
|
|
directSignIn,
|
|
extraParams,
|
|
includeReservedScopes
|
|
});
|
|
await Promise.all([
|
|
this.setSignInSession({ redirectUri, postRedirectUri, codeVerifier, state }),
|
|
clearTokens === false ? void 0 : this.clearAllTokens()
|
|
]);
|
|
await this.adapter.navigate(signInUri, { redirectUri, for: "sign-in" });
|
|
}
|
|
/**
|
|
* Check if the user is redirected from the sign-in page by checking if the
|
|
* current URL matches the redirect URI in the sign-in session.
|
|
*
|
|
* If there's no sign-in session, it will return `false`.
|
|
*
|
|
* @param url The current URL.
|
|
*/
|
|
async isSignInRedirected(url) {
|
|
const signInSession = await this.getSignInSession();
|
|
if (!signInSession) {
|
|
return false;
|
|
}
|
|
const { redirectUri } = signInSession;
|
|
const { origin, pathname } = new URL(url);
|
|
return `${origin}${pathname}` === redirectUri;
|
|
}
|
|
/**
|
|
* Start the sign-out flow with the specified redirect URI. The URI must be
|
|
* registered in the Logto Console.
|
|
*
|
|
* It will also revoke all the tokens and clean up the storage.
|
|
*
|
|
* The user will be redirected that URI after the sign-out flow is completed.
|
|
* If the `postLogoutRedirectUri` is not specified, the user will be redirected
|
|
* to a default page.
|
|
*/
|
|
async signOut(postLogoutRedirectUri) {
|
|
const { appId: clientId } = this.logtoConfig;
|
|
const { endSessionEndpoint, revocationEndpoint } = await this.getOidcConfig();
|
|
const refreshToken = await this.getRefreshToken();
|
|
if (refreshToken) {
|
|
try {
|
|
await revoke(revocationEndpoint, clientId, refreshToken, this.adapter.requester);
|
|
} catch {
|
|
}
|
|
}
|
|
const url = generateSignOutUri({
|
|
endSessionEndpoint,
|
|
postLogoutRedirectUri,
|
|
clientId
|
|
});
|
|
await this.clearAllTokens();
|
|
await this.adapter.navigate(url, { redirectUri: postLogoutRedirectUri, for: "sign-out" });
|
|
}
|
|
async getSignInSession() {
|
|
const jsonItem = await this.adapter.storage.getItem("signInSession");
|
|
if (!jsonItem) {
|
|
return null;
|
|
}
|
|
const item = JSON.parse(jsonItem);
|
|
if (!isLogtoSignInSessionItem(item)) {
|
|
throw new LogtoClientError("sign_in_session.invalid");
|
|
}
|
|
return item;
|
|
}
|
|
async setSignInSession(value) {
|
|
return this.adapter.setStorageItem(PersistKey.SignInSession, value && JSON.stringify(value));
|
|
}
|
|
async setIdToken(value) {
|
|
return this.adapter.setStorageItem(PersistKey.IdToken, value);
|
|
}
|
|
async setRefreshToken(value) {
|
|
return this.adapter.setStorageItem(PersistKey.RefreshToken, value);
|
|
}
|
|
async getAccessTokenByRefreshToken(resource, organizationId) {
|
|
const currentRefreshToken = await this.getRefreshToken();
|
|
if (!currentRefreshToken) {
|
|
throw new LogtoClientError("not_authenticated", "Refresh token not found");
|
|
}
|
|
const accessTokenKey = buildAccessTokenKey(resource, organizationId);
|
|
const { appId: clientId } = this.logtoConfig;
|
|
const { tokenEndpoint } = await this.getOidcConfig();
|
|
const requestedAt = Math.round(Date.now() / 1e3);
|
|
const { accessToken, refreshToken, idToken, scope, expiresIn } = await fetchTokenByRefreshToken({
|
|
clientId,
|
|
tokenEndpoint,
|
|
refreshToken: currentRefreshToken,
|
|
resource,
|
|
organizationId
|
|
}, this.adapter.requester);
|
|
this.accessTokenMap.set(accessTokenKey, {
|
|
token: accessToken,
|
|
scope,
|
|
/** The `expiresAt` variable provides an approximate estimation of the actual `exp` property
|
|
* in the token claims. It is utilized by the client to determine if the cached access token
|
|
* has expired and when a new access token should be requested.
|
|
*/
|
|
expiresAt: requestedAt + expiresIn
|
|
});
|
|
await this.saveAccessTokenMap();
|
|
if (refreshToken) {
|
|
await this.setRefreshToken(refreshToken);
|
|
}
|
|
if (idToken) {
|
|
await this.jwtVerifier.verifyIdToken(idToken);
|
|
await this.setIdToken(idToken);
|
|
}
|
|
return accessToken;
|
|
}
|
|
async saveAccessTokenMap() {
|
|
const data = {};
|
|
for (const [key, accessToken] of this.accessTokenMap.entries()) {
|
|
data[key] = accessToken;
|
|
}
|
|
await this.adapter.storage.setItem("accessToken", JSON.stringify(data));
|
|
}
|
|
async loadAccessTokenMap() {
|
|
const raw = await this.adapter.storage.getItem("accessToken");
|
|
if (!raw) {
|
|
return;
|
|
}
|
|
try {
|
|
const json = JSON.parse(raw);
|
|
if (!isLogtoAccessTokenMap(json)) {
|
|
return;
|
|
}
|
|
this.accessTokenMap.clear();
|
|
for (const [key, accessToken] of Object.entries(json)) {
|
|
this.accessTokenMap.set(key, accessToken);
|
|
}
|
|
} catch (error) {
|
|
console.warn(error);
|
|
}
|
|
}
|
|
async #getOidcConfig() {
|
|
return this.adapter.getWithCache(CacheKey.OpenidConfig, async () => {
|
|
return fetchOidcConfig(getDiscoveryEndpoint(this.logtoConfig.endpoint), this.adapter.requester);
|
|
});
|
|
}
|
|
async #getAccessToken(resource, organizationId) {
|
|
if (!await this.isAuthenticated()) {
|
|
throw new LogtoClientError("not_authenticated");
|
|
}
|
|
const accessTokenKey = buildAccessTokenKey(resource, organizationId);
|
|
const accessToken = this.accessTokenMap.get(accessTokenKey);
|
|
if (accessToken && accessToken.expiresAt > Date.now() / 1e3) {
|
|
return accessToken.token;
|
|
}
|
|
if (accessToken) {
|
|
this.accessTokenMap.delete(accessTokenKey);
|
|
}
|
|
return this.getAccessTokenByRefreshToken(resource, organizationId);
|
|
}
|
|
async #getOrganizationToken(organizationId) {
|
|
if (!this.logtoConfig.scopes?.includes(UserScope.Organizations)) {
|
|
throw new LogtoClientError("missing_scope_organizations");
|
|
}
|
|
return this.getAccessToken(void 0, organizationId);
|
|
}
|
|
async #clearAccessToken() {
|
|
this.accessTokenMap.clear();
|
|
await this.adapter.storage.removeItem("accessToken");
|
|
}
|
|
async #clearAllTokens() {
|
|
await Promise.all([this.setRefreshToken(null), this.setIdToken(null), this.clearAccessToken()]);
|
|
}
|
|
async #handleSignInCallback(callbackUri) {
|
|
const signInSession = await this.getSignInSession();
|
|
if (!signInSession) {
|
|
throw new LogtoClientError("sign_in_session.not_found");
|
|
}
|
|
const { redirectUri, postRedirectUri, state, codeVerifier } = signInSession;
|
|
const code = verifyAndParseCodeFromCallbackUri(callbackUri, redirectUri, state);
|
|
const accessTokenKey = buildAccessTokenKey();
|
|
const { appId: clientId } = this.logtoConfig;
|
|
const { tokenEndpoint } = await this.getOidcConfig();
|
|
const requestedAt = Math.round(Date.now() / 1e3);
|
|
const { idToken, refreshToken, accessToken, scope, expiresIn } = await fetchTokenByAuthorizationCode({
|
|
clientId,
|
|
tokenEndpoint,
|
|
redirectUri,
|
|
codeVerifier,
|
|
code
|
|
}, this.adapter.requester);
|
|
await this.jwtVerifier.verifyIdToken(idToken);
|
|
await this.setRefreshToken(refreshToken ?? null);
|
|
await this.setIdToken(idToken);
|
|
this.accessTokenMap.set(accessTokenKey, {
|
|
token: accessToken,
|
|
scope,
|
|
/** The `expiresAt` variable provides an approximate estimation of the actual `exp` property
|
|
* in the token claims. It is utilized by the client to determine if the cached access token
|
|
* has expired and when a new access token should be requested.
|
|
*/
|
|
expiresAt: requestedAt + expiresIn
|
|
});
|
|
await this.saveAccessTokenMap();
|
|
await this.setSignInSession(null);
|
|
if (postRedirectUri) {
|
|
await this.adapter.navigate(postRedirectUri, { for: "post-sign-in" });
|
|
}
|
|
}
|
|
};
|
|
|
|
// node_modules/@logto/client/lib/utils/requester.js
|
|
var createRequester = (fetchFunction) => {
|
|
return async (...args) => {
|
|
const response = await fetchFunction(...args);
|
|
if (!response.ok) {
|
|
const cloned = response.clone();
|
|
const responseJson = await response.json();
|
|
console.error(`Logto requester error: [status=${response.status}]`, responseJson);
|
|
if (!isLogtoRequestErrorJson(responseJson)) {
|
|
throw new LogtoError("unexpected_response_error", responseJson);
|
|
}
|
|
const { code, message: message2 } = responseJson;
|
|
throw new LogtoRequestError(code, message2, cloned);
|
|
}
|
|
return response.json();
|
|
};
|
|
};
|
|
|
|
// node_modules/@logto/client/lib/index.js
|
|
var LogtoClient = class extends StandardLogtoClient {
|
|
constructor(logtoConfig, adapter, buildJwtVerifier) {
|
|
super(logtoConfig, adapter, buildJwtVerifier ?? ((client) => new DefaultJwtVerifier(client)));
|
|
}
|
|
};
|
|
|
|
// node_modules/js-base64/base64.mjs
|
|
var _hasBuffer = typeof Buffer === "function";
|
|
var _TD = typeof TextDecoder === "function" ? new TextDecoder() : void 0;
|
|
var _TE = typeof TextEncoder === "function" ? new TextEncoder() : void 0;
|
|
var b64ch = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
|
|
var b64chs = Array.prototype.slice.call(b64ch);
|
|
var b64tab = ((a) => {
|
|
let tab = {};
|
|
a.forEach((c, i) => tab[c] = i);
|
|
return tab;
|
|
})(b64chs);
|
|
var _fromCC = String.fromCharCode.bind(String);
|
|
var _U8Afrom = typeof Uint8Array.from === "function" ? Uint8Array.from.bind(Uint8Array) : (it) => new Uint8Array(Array.prototype.slice.call(it, 0));
|
|
var _mkUriSafe = (src) => src.replace(/=/g, "").replace(/[+\/]/g, (m0) => m0 == "+" ? "-" : "_");
|
|
var btoaPolyfill = (bin) => {
|
|
let u32, c0, c1, c2, asc = "";
|
|
const pad = bin.length % 3;
|
|
for (let i = 0; i < bin.length; ) {
|
|
if ((c0 = bin.charCodeAt(i++)) > 255 || (c1 = bin.charCodeAt(i++)) > 255 || (c2 = bin.charCodeAt(i++)) > 255)
|
|
throw new TypeError("invalid character found");
|
|
u32 = c0 << 16 | c1 << 8 | c2;
|
|
asc += b64chs[u32 >> 18 & 63] + b64chs[u32 >> 12 & 63] + b64chs[u32 >> 6 & 63] + b64chs[u32 & 63];
|
|
}
|
|
return pad ? asc.slice(0, pad - 3) + "===".substring(pad) : asc;
|
|
};
|
|
var _btoa = typeof btoa === "function" ? (bin) => btoa(bin) : _hasBuffer ? (bin) => Buffer.from(bin, "binary").toString("base64") : btoaPolyfill;
|
|
var _fromUint8Array = _hasBuffer ? (u8a) => Buffer.from(u8a).toString("base64") : (u8a) => {
|
|
const maxargs = 4096;
|
|
let strs = [];
|
|
for (let i = 0, l = u8a.length; i < l; i += maxargs) {
|
|
strs.push(_fromCC.apply(null, u8a.subarray(i, i + maxargs)));
|
|
}
|
|
return _btoa(strs.join(""));
|
|
};
|
|
var fromUint8Array = (u8a, urlsafe = false) => urlsafe ? _mkUriSafe(_fromUint8Array(u8a)) : _fromUint8Array(u8a);
|
|
|
|
// node_modules/@logto/browser/lib/utils/generators.js
|
|
var generateRandomString = (length = 64) => fromUint8Array(crypto.getRandomValues(new Uint8Array(length)), true);
|
|
var generateState = () => generateRandomString();
|
|
var generateCodeVerifier = () => generateRandomString();
|
|
var generateCodeChallenge = async (codeVerifier) => {
|
|
if (crypto.subtle === void 0) {
|
|
throw new LogtoError("crypto_subtle_unavailable");
|
|
}
|
|
const encodedCodeVerifier = new TextEncoder().encode(codeVerifier);
|
|
const codeChallenge = new Uint8Array(await crypto.subtle.digest("SHA-256", encodedCodeVerifier));
|
|
return fromUint8Array(codeChallenge, true);
|
|
};
|
|
|
|
// node_modules/@logto/chrome-extension/lib/storage.js
|
|
var keyPrefix = `logto`;
|
|
var ChromeExtensionStorage = class {
|
|
constructor(appId) {
|
|
this.appId = appId;
|
|
}
|
|
getKey(item) {
|
|
if (item === void 0) {
|
|
return `${keyPrefix}:${this.appId}`;
|
|
}
|
|
return `${keyPrefix}:${this.appId}:${item}`;
|
|
}
|
|
// eslint-disable-next-line @typescript-eslint/ban-types
|
|
async getItem(key) {
|
|
const storageKey = this.getKey(key);
|
|
const object = await chrome.storage.local.get(storageKey);
|
|
return object[storageKey] ? String(object[storageKey]) : null;
|
|
}
|
|
async setItem(key, value) {
|
|
await chrome.storage.local.set({ [this.getKey(key)]: value });
|
|
}
|
|
async removeItem(key) {
|
|
await chrome.storage.local.remove(this.getKey(key));
|
|
}
|
|
};
|
|
|
|
// node_modules/@logto/chrome-extension/lib/index.js
|
|
var LogtoClient2 = class extends LogtoClient {
|
|
/**
|
|
* @param config The configuration object for the client.
|
|
*/
|
|
constructor(config) {
|
|
const requester = createRequester(fetch);
|
|
const navigate = async (url, params) => {
|
|
switch (params.for) {
|
|
case "sign-in": {
|
|
const responseUrl = await chrome.identity.launchWebAuthFlow({ url, interactive: true });
|
|
if (!responseUrl) {
|
|
throw new LogtoClientError("user_cancelled");
|
|
}
|
|
await this.handleSignInCallback(responseUrl);
|
|
break;
|
|
}
|
|
case "sign-out": {
|
|
await chrome.identity.launchWebAuthFlow({
|
|
url,
|
|
interactive: false,
|
|
abortOnLoadForNonInteractive: false,
|
|
timeoutMsForNonInteractive: 1e4
|
|
});
|
|
break;
|
|
}
|
|
default: {
|
|
throw new Error(`Unsupported navigation for ${params.for}`);
|
|
}
|
|
}
|
|
};
|
|
super(config, {
|
|
requester,
|
|
navigate,
|
|
storage: new ChromeExtensionStorage(config.appId),
|
|
generateCodeChallenge,
|
|
generateCodeVerifier,
|
|
generateState
|
|
});
|
|
}
|
|
};
|
|
|
|
// src/background/auth/client.ts
|
|
function createLogtoAuthClient() {
|
|
const config = readAuthConfig();
|
|
const client = new LogtoClient2({
|
|
appId: config.appId,
|
|
endpoint: config.logtoEndpoint,
|
|
resources: [config.apiResource],
|
|
scopes: config.scopes
|
|
});
|
|
return {
|
|
getAccessToken(resource) {
|
|
return client.getAccessToken(resource);
|
|
},
|
|
getIdTokenClaims() {
|
|
return client.getIdTokenClaims();
|
|
},
|
|
isAuthenticated() {
|
|
return client.isAuthenticated();
|
|
},
|
|
signIn() {
|
|
return client.signIn(readChromeIdentity().getRedirectURL("/callback"));
|
|
},
|
|
signOut() {
|
|
return client.signOut(readChromeIdentity().getRedirectURL());
|
|
}
|
|
};
|
|
}
|
|
function readChromeIdentity() {
|
|
const identity = globalThis.chrome?.identity;
|
|
if (typeof identity?.getRedirectURL !== "function") {
|
|
throw new Error("chrome.identity.getRedirectURL is unavailable");
|
|
}
|
|
return {
|
|
getRedirectURL: identity.getRedirectURL.bind(identity)
|
|
};
|
|
}
|
|
|
|
// src/shared/auth-messages.ts
|
|
var authRequestTypes = /* @__PURE__ */ new Set([
|
|
"auth:get-state",
|
|
"auth:sign-in",
|
|
"auth:sign-out",
|
|
"auth:get-access-token"
|
|
]);
|
|
function isAuthRequestMessage(value) {
|
|
if (!value || typeof value !== "object") {
|
|
return false;
|
|
}
|
|
const candidate = value;
|
|
return typeof candidate.type === "string" && authRequestTypes.has(candidate.type);
|
|
}
|
|
function isAuthResponseMessage(value) {
|
|
if (!value || typeof value !== "object") {
|
|
return false;
|
|
}
|
|
const candidate = value;
|
|
if (candidate.ok === false) {
|
|
return candidate.type === "auth:error" && typeof candidate.error === "string";
|
|
}
|
|
if (candidate.ok !== true || typeof candidate.type !== "string") {
|
|
return false;
|
|
}
|
|
if (candidate.type === "auth:ack") {
|
|
return true;
|
|
}
|
|
if (candidate.type === "auth:token") {
|
|
return Boolean(
|
|
candidate.value && typeof candidate.value === "object" && typeof candidate.value.accessToken === "string"
|
|
);
|
|
}
|
|
if (candidate.type === "auth:state") {
|
|
return Boolean(
|
|
candidate.value && typeof candidate.value === "object" && typeof candidate.value.isAuthenticated === "boolean"
|
|
);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// src/shared/batch-submit-config.ts
|
|
var DEFAULT_BATCH_SUBMIT_BASE_URL = "http://192.168.31.21:8083";
|
|
|
|
// src/shared/batch-submit-client.ts
|
|
function createBatchSubmitClient(options) {
|
|
const baseUrl = options.baseUrl ?? DEFAULT_BATCH_SUBMIT_BASE_URL;
|
|
const fetchImpl = options.fetchImpl ?? fetch;
|
|
const getAccessToken = options.getAccessToken ?? (() => readAccessToken(options.sendMessage));
|
|
return {
|
|
async submitBatch(payload) {
|
|
const token = await getAccessToken();
|
|
const response = await fetchImpl(
|
|
buildBatchSubmitUrl(baseUrl),
|
|
{
|
|
body: JSON.stringify(payload),
|
|
headers: {
|
|
Authorization: `Bearer ${token}`,
|
|
"Content-Type": "application/json"
|
|
},
|
|
method: "POST"
|
|
}
|
|
);
|
|
if (response.status === 401 || response.status === 403) {
|
|
throw new Error("batch submit unauthorized");
|
|
}
|
|
if (!response.ok) {
|
|
throw new Error(`batch submit failed: ${response.status}`);
|
|
}
|
|
return readBatchSubmitResponse(await response.json());
|
|
}
|
|
};
|
|
}
|
|
function buildBatchSubmitUrl(baseUrl) {
|
|
return new URL("/api/v1/batch-status/batches", baseUrl).toString();
|
|
}
|
|
async function readAccessToken(sendMessage) {
|
|
const response = await sendMessage({ type: "auth:get-access-token" });
|
|
if (!isAuthResponseMessage(response) || !response.ok || response.type !== "auth:token" || !response.value.accessToken.trim()) {
|
|
throw new Error("batch submit token unavailable");
|
|
}
|
|
return response.value.accessToken;
|
|
}
|
|
function readBatchSubmitResponse(payload) {
|
|
if (!isRecord(payload)) {
|
|
throw new Error("batch submit response is invalid");
|
|
}
|
|
if (payload.success !== true) {
|
|
const message2 = typeof payload.msg === "string" && payload.msg.trim() ? payload.msg : "batch submit failed";
|
|
throw new Error(message2);
|
|
}
|
|
return "data" in payload ? payload.data : payload;
|
|
}
|
|
function isRecord(value) {
|
|
return typeof value === "object" && value !== null;
|
|
}
|
|
|
|
// src/shared/backend-metrics-config.ts
|
|
var DEFAULT_BACKEND_METRICS_BASE_URL = "https://talent-search.intelligrow.cn";
|
|
|
|
// src/shared/backend-metrics-client.ts
|
|
function createBackendMetricsClient(options) {
|
|
const baseUrl = options.baseUrl ?? DEFAULT_BACKEND_METRICS_BASE_URL;
|
|
const fetchImpl = options.fetchImpl ?? defaultFetch;
|
|
return {
|
|
async searchByStarIds(starIds) {
|
|
const response = await fetchImpl(buildBackendMetricsSearchUrl(baseUrl), {
|
|
body: JSON.stringify(buildBackendMetricsSearchRequestBody(starIds)),
|
|
headers: {
|
|
Authorization: `Bearer ${await options.getAccessToken()}`,
|
|
"Content-Type": "application/json"
|
|
},
|
|
method: "POST"
|
|
});
|
|
if (!response.ok) {
|
|
throw new Error("backend metrics request failed");
|
|
}
|
|
return mapBackendMetricsSearchResponse(await response.json());
|
|
}
|
|
};
|
|
}
|
|
function buildBackendMetricsSearchUrl(baseUrl) {
|
|
return new URL("/api/v1/history/talents/search", baseUrl).toString();
|
|
}
|
|
function buildBackendMetricsSearchRequestBody(starIds) {
|
|
return {
|
|
page: 1,
|
|
size: Math.max(20, starIds.length),
|
|
type: "star_id",
|
|
values: starIds
|
|
};
|
|
}
|
|
function mapBackendMetricsSearchResponse(payload) {
|
|
const rows = readResponseRows(payload);
|
|
if (!rows) {
|
|
throw new Error("backend metrics response is invalid");
|
|
}
|
|
return rows.flatMap((row) => {
|
|
if (!isRecord2(row) || typeof row.star_id !== "string") {
|
|
return [];
|
|
}
|
|
return [
|
|
{
|
|
a3IncreaseCount: formatDecimalValue(
|
|
readAverageA3IncreaseCount(row)
|
|
),
|
|
afterViewSearchCount: formatDecimalValue(row.avg_after_view_search_cnt),
|
|
afterViewSearchRate: formatRateValue(row.avg_after_view_search_rate),
|
|
cpSearch: formatDecimalValue(row.cp_search),
|
|
cpa3: formatDecimalValue(readCpa3Value(row)),
|
|
newA3Rate: formatRateValue(row.avg_new_a3_rate),
|
|
starId: row.star_id
|
|
}
|
|
];
|
|
});
|
|
}
|
|
function readAverageA3IncreaseCount(row) {
|
|
const directAverage = readFiniteNumber(row.avg_a3_increase_cnt);
|
|
if (directAverage !== null) {
|
|
return directAverage;
|
|
}
|
|
const totalNewA3 = readTotalNewA3Value(row);
|
|
const videoCount = readFiniteNumber(row.video_count) ?? readNestedVideoCount(row.videos);
|
|
if (totalNewA3 === null || videoCount === null || videoCount <= 0) {
|
|
return null;
|
|
}
|
|
return totalNewA3 / videoCount;
|
|
}
|
|
function readCpa3Value(row) {
|
|
const directCpa3 = readFiniteNumber(row.cpa3);
|
|
if (directCpa3 !== null) {
|
|
return directCpa3;
|
|
}
|
|
const totalCost = readFiniteNumber(row.total_estimated_video_cost);
|
|
const totalNewA3 = readTotalNewA3Value(row);
|
|
if (totalCost === null || totalNewA3 === null || totalNewA3 <= 0) {
|
|
return null;
|
|
}
|
|
return totalCost / totalNewA3;
|
|
}
|
|
function readTotalNewA3Value(row) {
|
|
const derivedFromTotals = deriveTotalNewA3FromTotals(row);
|
|
if (derivedFromTotals !== null) {
|
|
return derivedFromTotals;
|
|
}
|
|
return deriveTotalNewA3FromVideos(row.videos);
|
|
}
|
|
function deriveTotalNewA3FromTotals(row) {
|
|
const totalPlayCount = readFiniteNumber(row.total_play_cnt);
|
|
const averageNewA3Rate = readFiniteNumber(row.avg_new_a3_rate);
|
|
if (totalPlayCount === null || averageNewA3Rate === null) {
|
|
return null;
|
|
}
|
|
return totalPlayCount * averageNewA3Rate;
|
|
}
|
|
function deriveTotalNewA3FromVideos(value) {
|
|
if (!Array.isArray(value)) {
|
|
return null;
|
|
}
|
|
let total = 0;
|
|
let hasFiniteValue = false;
|
|
value.forEach((video) => {
|
|
if (!isRecord2(video)) {
|
|
return;
|
|
}
|
|
const newA3 = readFiniteNumber(video.new_a3);
|
|
if (newA3 === null) {
|
|
return;
|
|
}
|
|
hasFiniteValue = true;
|
|
total += newA3;
|
|
});
|
|
return hasFiniteValue ? total : null;
|
|
}
|
|
function readNestedVideoCount(value) {
|
|
return Array.isArray(value) ? value.length : null;
|
|
}
|
|
function readResponseRows(payload) {
|
|
if (!isRecord2(payload) || payload.success !== true) {
|
|
return null;
|
|
}
|
|
const topLevelData = isRecord2(payload.data) ? payload.data : null;
|
|
return Array.isArray(topLevelData?.data) ? topLevelData.data : null;
|
|
}
|
|
function formatRateValue(value) {
|
|
const number = typeof value === "number" ? value : Number(value);
|
|
if (Number.isFinite(number)) {
|
|
const percentage = number * 100;
|
|
const formatted = new Intl.NumberFormat("en-US", {
|
|
maximumFractionDigits: 2,
|
|
minimumFractionDigits: percentage % 1 === 0 ? 0 : 2
|
|
}).format(percentage);
|
|
return `${formatted}%`;
|
|
}
|
|
return "";
|
|
}
|
|
function formatDecimalValue(value) {
|
|
const number = typeof value === "number" ? value : Number(value);
|
|
if (!Number.isFinite(number)) {
|
|
return "";
|
|
}
|
|
return new Intl.NumberFormat("en-US", {
|
|
maximumFractionDigits: 2,
|
|
minimumFractionDigits: 2
|
|
}).format(number);
|
|
}
|
|
async function defaultFetch(input, init) {
|
|
return fetch(input, init);
|
|
}
|
|
function isRecord2(value) {
|
|
return typeof value === "object" && value !== null;
|
|
}
|
|
function readFiniteNumber(value) {
|
|
const number = typeof value === "number" ? value : Number(value);
|
|
return Number.isFinite(number) ? number : null;
|
|
}
|
|
|
|
// src/shared/backend-metrics-messages.ts
|
|
function isBackendMetricsSearchRequestMessage(value) {
|
|
if (!value || typeof value !== "object") {
|
|
return false;
|
|
}
|
|
const candidate = value;
|
|
return candidate.type === "backend-metrics:search" && Boolean(
|
|
candidate.value && typeof candidate.value === "object" && Array.isArray(candidate.value.starIds) && candidate.value.starIds.every(
|
|
(starId) => typeof starId === "string"
|
|
)
|
|
);
|
|
}
|
|
|
|
// src/background/index.ts
|
|
function registerBackgroundMessageHandler(chromeLike = readChromeLike(), dependencies = {}) {
|
|
let authController = dependencies.authController;
|
|
let searchBackendMetrics = dependencies.searchBackendMetrics;
|
|
let submitBatch = dependencies.submitBatch;
|
|
chromeLike.runtime?.onMessage?.addListener((message2, _sender, sendResponse) => {
|
|
if (isDownloadMarketCsvMessage(message2)) {
|
|
void triggerCsvDownload(chromeLike, message2).then(() => {
|
|
sendResponse({ ok: true });
|
|
}).catch((error) => {
|
|
sendResponse({
|
|
error: error instanceof Error ? error.message : String(error),
|
|
ok: false
|
|
});
|
|
});
|
|
return true;
|
|
}
|
|
if (isBatchSubmitMessage(message2)) {
|
|
authController ??= createAuthController({
|
|
authClient: createLogtoAuthClient()
|
|
});
|
|
submitBatch ??= createBatchSubmitClient({
|
|
baseUrl: DEFAULT_BATCH_SUBMIT_BASE_URL,
|
|
getAccessToken: () => authController.getAccessToken(),
|
|
sendMessage: () => Promise.reject(new Error("background batch submit does not use sendMessage"))
|
|
}).submitBatch;
|
|
void submitBatch(message2.payload).then((value) => {
|
|
sendResponse({
|
|
ok: true,
|
|
type: "batch:ack",
|
|
value
|
|
});
|
|
}).catch((error) => {
|
|
sendResponse({
|
|
error: error instanceof Error ? error.message : String(error),
|
|
ok: false,
|
|
type: "batch:error"
|
|
});
|
|
});
|
|
return true;
|
|
}
|
|
if (isBackendMetricsSearchRequestMessage(message2)) {
|
|
authController ??= createAuthController({
|
|
authClient: createLogtoAuthClient()
|
|
});
|
|
searchBackendMetrics ??= createBackendMetricsClient({
|
|
baseUrl: DEFAULT_BACKEND_METRICS_BASE_URL,
|
|
getAccessToken: () => authController.getAccessToken()
|
|
}).searchByStarIds;
|
|
void searchBackendMetrics(message2.value.starIds).then((rows) => {
|
|
sendResponse({
|
|
ok: true,
|
|
type: "backend-metrics:result",
|
|
value: {
|
|
rows
|
|
}
|
|
});
|
|
}).catch((error) => {
|
|
sendResponse({
|
|
error: error instanceof Error ? error.message : String(error),
|
|
ok: false,
|
|
type: "backend-metrics:error"
|
|
});
|
|
});
|
|
return true;
|
|
}
|
|
if (!isAuthRequestMessage(message2)) {
|
|
return;
|
|
}
|
|
authController ??= createAuthController({
|
|
authClient: createLogtoAuthClient()
|
|
});
|
|
void handleAuthMessage(authController, message2).then((response) => {
|
|
sendResponse(response);
|
|
}).catch((error) => {
|
|
sendResponse({
|
|
error: error instanceof Error ? error.message : String(error),
|
|
ok: false,
|
|
type: "auth:error"
|
|
});
|
|
});
|
|
return true;
|
|
});
|
|
}
|
|
async function handleAuthMessage(authController, message2) {
|
|
if (message2.type === "auth:get-state") {
|
|
return {
|
|
ok: true,
|
|
type: "auth:state",
|
|
value: await authController.getAuthState()
|
|
};
|
|
}
|
|
if (message2.type === "auth:get-access-token") {
|
|
return {
|
|
ok: true,
|
|
type: "auth:token",
|
|
value: {
|
|
accessToken: await authController.getAccessToken()
|
|
}
|
|
};
|
|
}
|
|
if (message2.type === "auth:sign-in") {
|
|
await authController.signIn();
|
|
return {
|
|
ok: true,
|
|
type: "auth:ack"
|
|
};
|
|
}
|
|
await authController.signOut();
|
|
return {
|
|
ok: true,
|
|
type: "auth:ack"
|
|
};
|
|
}
|
|
function readChromeLike() {
|
|
return globalThis.chrome ?? {};
|
|
}
|
|
async function triggerCsvDownload(chromeLike, message2) {
|
|
if (!chromeLike.downloads?.download) {
|
|
throw new Error("chrome.downloads.download is unavailable");
|
|
}
|
|
const csvUrl = `data:text/csv;charset=utf-8,${encodeURIComponent(`\uFEFF${message2.csv}`)}`;
|
|
await Promise.resolve(
|
|
chromeLike.downloads.download({
|
|
filename: message2.filename,
|
|
saveAs: false,
|
|
url: csvUrl
|
|
})
|
|
);
|
|
}
|
|
function isDownloadMarketCsvMessage(message2) {
|
|
if (!message2 || typeof message2 !== "object") {
|
|
return false;
|
|
}
|
|
const candidate = message2;
|
|
return candidate.type === "download-market-csv" && typeof candidate.csv === "string" && typeof candidate.filename === "string";
|
|
}
|
|
function isBatchSubmitMessage(message2) {
|
|
if (!message2 || typeof message2 !== "object") {
|
|
return false;
|
|
}
|
|
const candidate = message2;
|
|
return candidate.type === "batch:submit" && "payload" in candidate;
|
|
}
|
|
registerBackgroundMessageHandler();
|
|
})();
|
|
/*! Bundled license information:
|
|
|
|
@silverhand/essentials/lib/utilities/assertions.js:
|
|
(*!
|
|
* is-plain-object <https://github.com/jonschlinkert/is-plain-object>
|
|
*
|
|
* Copyright (c) 2014-2017, Jon Schlinkert.
|
|
* Released under the MIT License.
|
|
*)
|
|
*/
|