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

342 lines (328 loc) 10.6 kB
var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; 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 __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/google/index.js var google_exports = {}; __export(google_exports, { GoogleAccount: () => GoogleAccount, GoogleGrant: () => GoogleGrant, createGoogleOAuth2: () => createGoogleOAuth2, default: () => google_default }); module.exports = __toCommonJS(google_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/google/GoogleAccount.js var import_googleapis = require("googleapis"); var import_props3 = require("@randajan/props"); // 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 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/google/GoogleAccount.js var GoogleAccount = class extends OAuth2Account { constructor(client, credentials = {}) { super(client, credentials); const grant = vault.get(client); const auth = grant.createAuth(); auth.setCredentials(this.credentials); auth.on("tokens", (_) => { grant.onRenew(this); }); (0, import_props3.solids)(this, { auth }); } oauth2() { return import_googleapis.google.oauth2({ auth: this.auth, version: "v2" }); } async profile() { const { data } = await this.oauth2().userinfo.get(); return data; } async tokens() { const { token } = await this.auth.getAccessToken(); return { ...this.auth.credentials, access_token: token }; } async scopes() { const { auth } = this; if (auth.credentials?.scope) { return super.scopes(auth.credentials.scope); } const { token } = await auth.getAccessToken(); if (!token) { return []; } const info = await auth.getTokenInfo(token); if (!info) { return []; } return super.scopes(info.scopes); } }; // src/google/GoogleGrant.js var import_googleapis2 = require("googleapis"); // 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/google/GoogleGrant.js var GoogleGrant = class extends OAuth2Grant { static name = "google"; static uidKey = "id"; static scopePrefix = "https://www.googleapis.com/auth/"; static scopesCommon = ["openid", "userinfo.profile", "userinfo.email"]; static scopesNoPrefix = ["openid"]; static Account = GoogleAccount; constructor(client, opt = {}) { super(client, opt); const { optExtra, clientId, clientSecret, redirectUri } = this; this.optAuth = { ...optExtra, clientId, clientSecret, redirectUri }; this.auth = this.createAuth(); } createAuth() { return new import_googleapis2.google.auth.OAuth2(this.optAuth); } generateAuthUrl(scope, state, extra) { return this.auth.generateAuthUrl({ ...extra, access_type: this.isOffline ? "offline" : "online", scope, state }); } async swapCodeForTokens(code) { const { tokens } = await this.auth.getToken(code); return tokens; } }; // src/google/index.js var createGoogleOAuth2 = (options = {}) => new OAuth2Client(GoogleGrant, options); var google_default = createGoogleOAuth2; //# sourceMappingURL=index.js.map