UNPKG

@randajan/oauth2-client

Version:

Lightweight Node.js helper that streamlines OAuth 2.0 and service-account authentication for all Google APIs, giving downstream packages hassle-free token acquisition and refresh

357 lines (344 loc) 11.4 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 __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; 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 )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/facebook/index.js var facebook_exports = {}; __export(facebook_exports, { FacebookAccount: () => FacebookAccount, FacebookGrant: () => FacebookGrant, createFacebookOAuth2: () => createFacebookOAuth2, default: () => facebook_default }); module.exports = __toCommonJS(facebook_exports); // src/consts.js var vault = /* @__PURE__ */ new WeakMap(); // src/class/OAuth2Client.js var import_props = require("@randajan/props"); var OAuth2Client = class { constructor(Grant, options = {}) { const grant = new Grant(this, options); (0, import_props.virtuals)(this, { name: (_) => grant.name, uidKey: (_) => grant.uidKey }); vault.set(this, grant); } account(credentials, ...args) { const grant = vault.get(this); const c = grant.getCredentials ? grant.getCredentials(credentials, ...args) : credentials; return c instanceof Promise ? c.then((cr) => grant.createAccount(cr)) : grant.createAccount(c); } getInitAuthURL(options = {}) { const grant = vault.get(this); try { return grant.getInitAuthURL(options); } catch (err) { return grant.fallbackRedirect(1, err); } } getExitAuthURL({ code, state }, context) { const grant = vault.get(this); try { return grant.getExitAuthURL({ code, state }, context); } catch (err) { return grant.fallbackRedirect(2, err); } } }; // src/class/OAuth2Account.js var import_props2 = require("@randajan/props"); // src/tools.js var extendURL = (url, query = {}) => { const u = new URL(url); for (let i in query) { if (query[i] != null) { u.searchParams.append(i, query[i]); } } return u.toString(); }; var isValidURL = (str) => { try { new URL(str); } catch (e) { return false; } return true; }; var validateURL = (required, url, errProp) => { if (!url && !required) { return; } if (isValidURL(url)) { return url; } throw new Error(`${errProp} is not a valid URL`); }; var validateFn = (required, fn, errProp) => { if (!fn && !required) { return; } if (typeof fn === "function") { return fn; } throw new Error(`${errProp} is not a valid function`); }; var validateStr = (required, str, errProp) => { if (!str && !required) { return; } if (typeof str === "string") { return str; } throw new Error(`${errProp} is not a valid string`); }; var formatCredentials = (credentials = {}) => { const c = { ...credentials }; if (c.expires_in && !c.expiry_date) { c.expiry_date = Date.now() + c.expires_in * 1e3; } if (c.refresh_token && !c.expiry_date) { throw new Error(`OAuth2 credentials 'refresh_token' must be provided with 'expiry_date'`); } if (!c.access_token && !c.refresh_token) { throw new Error(`OAuth2 credentials 'access_token' of 'refresh_token' must be provided`); } return c; }; var strToBase64 = (str) => Buffer.from(str, "utf8").toString("base64"); var strFromBase64 = (strEncoded) => Buffer.from(strEncoded, "base64").toString("utf8"); var objToBase64 = (obj) => strToBase64(JSON.stringify(obj)); var objFromBase64 = (objEncoded) => JSON.parse(strFromBase64(objEncoded)); // src/class/OAuth2Account.js var OAuth2Account = class { constructor(client, credentials = {}) { credentials = formatCredentials(credentials); (0, import_props2.solids)(this, { client, credentials }, false); } async uid() { const profile = await this.profile(); const { name, uidKey } = this.client; const id = profile?.[uidKey]; if (id) { return `${name}:${id}`; } } async profile() { return {}; } async tokens() { return {}; } async scopes(scopes) { const grant = vault.get(this.client); return grant.effaceScopes(scopes); } }; // src/facebook/FacebookAccount.js var FacebookAccount = class extends OAuth2Account { async profile(fields = ["id", "name", "email", "picture"]) { const grant = vault.get(this.client); const { access_token } = this.credentials; return grant.fetchApi(`/me`, { access_token, fields: fields.join(",") }, 3); } async tokens() { return this.credentials; } async scopes() { const grant = vault.get(this.client); const { access_token } = this.credentials; const { data } = await grant.fetchApi("/me/permisssions", { access_token }, 3); return super.scopes(data.filter((p) => p.status === "granted").map((p) => p.permission)); } }; // src/facebook/FacebookGrant.js var import_node_fetch = __toESM(require("node-fetch"), 1); // src/errors.js var RedirectError = class _RedirectError extends Error { static is(any) { return any instanceof _RedirectError; } constructor(code, message, options) { super(message, options); this.code = code; } }; // src/class/OAuth2Grant.js var OAuth2Grant = class { static name = ""; static uidKey = ""; static scopePrefix = ""; static scopesCommon = []; static scopesNoPrefix = []; static Account = OAuth2Account; constructor(client, opt = {}) { const { name, uidKey, scopesCommon } = this.constructor; this.client = client; this.name = opt.name || name; this.uidKey = opt.uidKey || uidKey; this.isOffline = !!opt.isOffline; this.clientId = opt.clientId; this.clientSecret = opt.clientSecret; this.redirectUri = validateURL(true, opt.redirectUri, "options.redirectUri"); this.fallbackUri = validateURL(true, opt.fallbackUri, "options.fallbackUri"); this.landingUri = validateURL(false, opt.landingUri, "options.landingUri"); this.onAuth = validateFn(true, opt.onAuth, "options.onAuth"); this.onRenew = validateFn(true, opt.onRenew, "options.onRenew"); this.getCredentials = validateFn(false, opt.getCredentials, "options.getCredentials"); this.optExtra = opt.extra || {}; this.scopesRequired = this.effaceScopes([...scopesCommon, ...opt.scopes || []]); } createAccount(credentials) { const { client, constructor: { Account } } = this; return new Account(client, credentials); } effaceScope(scope, withPrefix = false) { const { scopePrefix, scopesNoPrefix } = this.constructor; if (scope == null) { return; } scope = String(scope).replace(/[\s\n\r]+/g, " ").trim().toLowerCase(); if (!scopePrefix || scopesNoPrefix.includes(scope)) { return scope; } const sw = scope.startsWith(scopePrefix); return sw === withPrefix ? scope : withPrefix ? scopePrefix + scope : scope.substring(scopePrefix.length); } effaceScopes(scopes, withPrefix = false, withRequired = false) { const { scopesRequired } = this; if (typeof scopes === "string") { scopes = scopes.split(" "); } if (!Array.isArray(scopes)) { scopes = []; } if (withRequired) { scopes = scopes.concat(scopesRequired); } const r = /* @__PURE__ */ new Set(); for (let scope of scopes) { if (scope) { scope = this.effaceScope(scope, withPrefix); } if (scope) { r.add(scope); } } return [...r]; } generateAuthUrl(scope, state, extra = {}) { } getInitAuthURL(options = {}) { const { landingUri, state: stateObj, scopes, extra } = options; if ((landingUri || !this.landingUri) && !isValidURL(landingUri)) { throw new RedirectError(1, "Bad request. Missing valid 'landingUri'"); } const scope = this.effaceScopes(scopes, true, true); const state = objToBase64([landingUri || this.landingUri, stateObj]); return this.generateAuthUrl(scope, state, extra || {}); } async swapCodeForTokens(code) { } async getExitAuthURL({ code, state }, context) { if (!code) { throw new RedirectError(201, "Bad request. Missing 'code'"); } const [landingUri, stateObj] = objFromBase64(state); if (!isValidURL(landingUri)) { throw new RedirectError(202, "Bad request. Missing valid 'state'"); } const tokens = await this.swapCodeForTokens(code); const account = this.createAccount(tokens); const customUri = await this.onAuth(account, { context, landingUri, state: stateObj }); return customUri || landingUri; } fallbackRedirect(majorCode, err) { const c = RedirectError.is(err) ? err.code : 0; return extendURL(this.fallbackUri, { errorCode: majorCode * 100 + c, errorMessage: err.message }); } }; // src/facebook/FacebookGrant.js var FacebookGrant = class extends OAuth2Grant { static name = "facebook"; static uidKey = "id"; static scopePrefix = ""; static scopesCommon = []; static scopesNoPrefix = []; static Account = FacebookAccount; constructor(client, opt = {}) { super(client, opt); this.apiVersion = validateStr(true, opt.apiVersion, "options.apiVersion"); } createApiUrl(subdomain, path, query = {}) { const { apiVersion } = this; return extendURL( `https://${subdomain}.facebook.com/${apiVersion}${path}`, query ); } async fetchApi(path, query, errorCode = 2) { const url = this.createApiUrl("graph", path, query); const res = await (0, import_node_fetch.default)(url); if (!res.ok) { throw new RedirectError(errorCode, await res.text()); } return res.json(); } generateAuthUrl(scope, state, extra) { const { clientId, redirectUri } = this; return this.createApiUrl("www", `/dialog/oauth`, { ...extra, response_type: "code", client_id: clientId, redirect_uri: redirectUri, scope: scope.join(","), // FB odděluje čárkou state }); } async swapCodeForTokens(code) { const { clientId, clientSecret, redirectUri } = this; return this.fetchApi(`/oauth/access_token`, { client_id: clientId, client_secret: clientSecret, redirect_uri: redirectUri, code }); } }; // src/facebook/index.js var createFacebookOAuth2 = (options = {}) => new OAuth2Client(FacebookGrant, options); var facebook_default = createFacebookOAuth2; //# sourceMappingURL=index.js.map