@hellocoop/helper-browser
Version:
Hellō helper functions for the browser
366 lines (357 loc) • 12 kB
JavaScript
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __commonJS = (cb, mod) => function __require() {
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
// ../../definitions/dist/index.js
var require_dist = __commonJS({
"../../definitions/dist/index.js"(exports) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Button = exports.NotLoggedIn = exports.VALID_PROVIDER_HINT = exports.VALID_RESPONSE_MODE = exports.VALID_RESPONSE_TYPE = exports.VALID_SCOPES = exports.VALID_IDENTITY_CLAIMS = exports.ORG_CLAIM = exports.VALID_IDENTITY_ACCOUNT_CLAIMS = exports.VALID_IDENTITY_STRING_CLAIMS = exports.DEFAULT_PATH = exports.DEFAULT_RESPONSE_MODE = exports.DEFAULT_RESPONSE_TYPE = exports.DEFAULT_SCOPE = exports.PRODUCTION_WALLET = void 0;
exports.PRODUCTION_WALLET = "https://wallet.hello.coop";
exports.DEFAULT_SCOPE = ["openid", "name", "email", "picture"];
exports.DEFAULT_RESPONSE_TYPE = "code";
exports.DEFAULT_RESPONSE_MODE = "query";
exports.DEFAULT_PATH = "/authorize?";
exports.VALID_IDENTITY_STRING_CLAIMS = [
"name",
"nickname",
"preferred_username",
"given_name",
"family_name",
"email",
"phone",
"picture",
"ethereum"
];
exports.VALID_IDENTITY_ACCOUNT_CLAIMS = [
"discord",
"twitter",
"github",
"gitlab"
];
exports.ORG_CLAIM = "org";
exports.VALID_IDENTITY_CLAIMS = [
...exports.VALID_IDENTITY_STRING_CLAIMS,
...exports.VALID_IDENTITY_ACCOUNT_CLAIMS,
"org",
"email_verified",
"phone_verified"
];
exports.VALID_SCOPES = [
...exports.VALID_IDENTITY_STRING_CLAIMS,
...exports.VALID_IDENTITY_ACCOUNT_CLAIMS,
"profile",
"openid",
"profile_update"
];
exports.VALID_RESPONSE_TYPE = ["id_token", "code"];
exports.VALID_RESPONSE_MODE = ["fragment", "query", "form_post"];
exports.VALID_PROVIDER_HINT = [
"apple",
"discord",
"facebook",
"github",
"gitlab",
"google",
"twitch",
"twitter",
"tumblr",
"mastodon",
"microsoft",
"line",
"wordpress",
"yahoo",
"phone",
"ethereum",
"qrcode",
"apple--",
"microsoft--"
];
exports.NotLoggedIn = { isLoggedIn: false };
var Button;
(function(Button2) {
Button2.STYLES_URL = "https://cdn.hello.coop/css/hello-btn.css";
Button2.HOVER_MAPPING = {
"pop": "",
"glow": "hello-btn-hover-glow",
"flare": "hello-btn-hover-flare",
"none": "hello-btn-hover-none"
};
Button2.CLASS_MAPPING = {
black: {
"ignore-light": "",
"ignore-dark": "hello-btn-black-on-dark",
"aware-invert": "hello-btn-black-and-invert",
"aware-static": "hello-btn-black-and-static"
},
white: {
"ignore-light": "hello-btn-white-on-light",
"ignore-dark": "hello-btn-white-on-dark",
"aware-invert": "hello-btn-white-and-invert",
"aware-static": "hello-btn-white-and-static"
}
};
})(Button || (exports.Button = Button = {}));
}
});
// ../src/common/pkce.ts
var crypto2;
var uuidv4;
var setCrypto = function(c) {
crypto2 = c;
uuidv4 = c.randomUUID.bind(c);
};
var VERIFIER_LENGTH = 43;
function generateVerifier() {
const mask = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~";
let result = "";
const randomUints = crypto2.getRandomValues(new Uint8Array(VERIFIER_LENGTH));
for (let i = 0; i < VERIFIER_LENGTH; i++) {
const randomIndex = randomUints[i] % mask.length;
result += mask[randomIndex];
}
return result;
}
async function generateChallenge(code_verifier) {
const buffer = await crypto2.subtle.digest(
"SHA-256",
new TextEncoder().encode(code_verifier)
);
return btoa(String.fromCharCode(...new Uint8Array(buffer))).replace(/\//g, "_").replace(/\+/g, "-").replace(/=/g, "");
}
async function pkce() {
const verifier = generateVerifier();
const challenge = await generateChallenge(verifier);
return {
code_verifier: verifier,
code_challenge: challenge
};
}
async function verifyChallenge(code_verifier, expectedChallenge) {
const actualChallenge = await generateChallenge(code_verifier);
return actualChallenge === expectedChallenge;
}
// ../src/common/createAuthRequest.ts
var import_definitions = __toESM(require_dist());
function isValidScope(scope) {
return import_definitions.VALID_SCOPES.includes(scope);
}
function areScopesValid(scopes) {
return scopes.every((scope) => isValidScope(scope));
}
async function createAuthRequest(config) {
if (!config.client_id) {
throw new Error("client_id is required in the authorization request.");
}
if (!config.redirect_uri) {
throw new Error("redirect_uri is required in the authorization request.");
}
if (config.scope) {
if (!areScopesValid(config.scope))
throw new Error("One or more passed scopes are invalid.");
config.scope = Array.from(/* @__PURE__ */ new Set([...config.scope, "openid"]));
}
if (config.response_type) {
if (!import_definitions.VALID_RESPONSE_TYPE.includes(config.response_type))
throw new Error("Invalid response_type.");
}
if (config.response_mode) {
if (!import_definitions.VALID_RESPONSE_MODE.includes(config.response_mode))
throw new Error("Invalid response_mode.");
}
const nonce = config.nonce || uuidv4();
let code_verifier = "";
const scopeArray = config.scope || import_definitions.DEFAULT_SCOPE;
const scope = scopeArray.join(" ");
const params = {
client_id: config.client_id,
redirect_uri: config.redirect_uri,
scope,
response_type: config.response_type || import_definitions.DEFAULT_RESPONSE_TYPE,
response_mode: config.response_mode || import_definitions.DEFAULT_RESPONSE_MODE,
nonce
};
if (config.prompt) {
params.prompt = config.prompt;
}
if (params.response_type === "code") {
const pkceMaterial = await pkce();
code_verifier = pkceMaterial.code_verifier;
params.code_challenge = pkceMaterial.code_challenge;
params.code_challenge_method = "S256";
}
if (config.provider_hint) {
params.provider_hint = config.provider_hint.join(" ");
}
if (config.login_hint) {
params.login_hint = config.login_hint;
}
if (config.domain_hint) {
params.domain_hint = config.domain_hint;
}
const url = (config.wallet || import_definitions.PRODUCTION_WALLET) + import_definitions.DEFAULT_PATH + new URLSearchParams(params).toString();
return {
url,
nonce,
code_verifier
};
}
// ../src/common/createInviteRequest.ts
var import_definitions2 = __toESM(require_dist());
function createInviteRequest(config) {
if (!config.inviter) {
throw new Error("inviter is required in the invite request.");
}
if (!config.client_id) {
throw new Error("client_id is required in the invite request.");
}
if (!config.initiate_login_uri) {
throw new Error("initiate_login_uri is required in the invite request.");
}
if (!config.return_uri) {
throw new Error("return_uri is required in the invite request.");
}
const url = new URL("/invite", config.wallet || import_definitions2.PRODUCTION_WALLET);
url.searchParams.set("inviter", config.inviter);
url.searchParams.set("client_id", config.client_id);
url.searchParams.set("initiate_login_uri", config.initiate_login_uri);
url.searchParams.set("return_uri", config.return_uri);
if (config.app_name) {
url.searchParams.set("app_name", config.app_name);
}
if (config.prompt) {
url.searchParams.set("prompt", config.prompt);
}
if (config.role) {
url.searchParams.set("role", config.role);
}
if (config.tenant) {
url.searchParams.set("tenant", config.tenant);
}
if (config.state) {
url.searchParams.set("state", config.state);
}
if (config.events_uri) {
url.searchParams.set("events_uri", config.events_uri);
}
return { url: url.href };
}
// ../src/common/fetchToken.ts
var import_definitions3 = __toESM(require_dist());
var DEFAULT_ENDPOINT = "/oauth/token";
async function fetchToken({ code, code_verifier, client_id, redirect_uri, wallet }) {
const params = {
code,
code_verifier,
client_id,
redirect_uri,
grant_type: "authorization_code"
};
const body = new URLSearchParams(params).toString();
const tokenEndpoint = (wallet || import_definitions3.PRODUCTION_WALLET) + DEFAULT_ENDPOINT;
try {
const r = await fetch(tokenEndpoint, {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body
});
const json = await r.json();
if (!r.ok) {
const message = `Fetch ${tokenEndpoint} failed with ${r.status}. ` + (json.error ? json.error + "." : "");
throw new Error(message);
}
if (json.error)
throw new Error(json.error);
if (!json.id_token)
throw new Error("No id_token in response.");
return json.id_token;
} catch (error) {
throw new Error(error);
}
}
// ../src/browser/parseToken.ts
function parseJwt(token) {
var base64 = token.replace(/-/g, "+").replace(/_/g, "/");
var jsonPayload = decodeURIComponent(window.atob(base64).split("").map(function(c) {
return "%" + ("00" + c.charCodeAt(0).toString(16)).slice(-2);
}).join(""));
return JSON.parse(jsonPayload);
}
function parseToken(token) {
try {
const [headerEncoded, tokenEncoded] = token.split(".");
const header = parseJwt(headerEncoded);
const payload = parseJwt(tokenEncoded);
return {
header,
payload
};
} catch (error) {
throw new Error(error);
}
}
// ../src/browser/validateToken.ts
var import_definitions4 = __toESM(require_dist());
var DEFAULT_ENDPOINT2 = "/oauth/introspect";
async function validateToken({ token, client_id, nonce, wallet }) {
const params = {
token,
client_id,
nonce
};
const body = new URLSearchParams(params).toString();
const introspectEndpoint = (wallet || import_definitions4.PRODUCTION_WALLET) + DEFAULT_ENDPOINT2;
try {
const r = await fetch(introspectEndpoint, {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body
});
const json = await r.json();
if (!r.ok) {
const message = `Fetch ${introspectEndpoint} failed with ${r.status}. ` + (json.error ? json.error + "." : "");
throw new Error(message);
}
if (json.error)
throw new Error(json.error);
return json;
} catch (error) {
throw new Error(error);
}
}
// ../src/browser/index.ts
setCrypto(crypto);
export {
areScopesValid,
createAuthRequest,
createInviteRequest,
fetchToken,
generateChallenge,
isValidScope,
parseToken,
pkce as pkceChallenge,
validateToken,
verifyChallenge
};