UNPKG

@hellocoop/helper-browser

Version:

Hellō helper functions for the browser

366 lines (357 loc) 12 kB
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 };