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

259 lines (250 loc) 8.01 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, GoogleOAuth2: () => GoogleOAuth2, effaceScopes: () => effaceScopes }); module.exports = __toCommonJS(google_exports); // src/google/scopes.js var _scopePrefix = "https://www.googleapis.com/auth/"; var _defaultScopes = ["openid", "userinfo.profile", "userinfo.email"]; var effaceScope = (scope, full = false) => { scope = String(scope).replace(/[\s\n\r]+/g, " ").trim().toLowerCase(); if (scope === "openid") { return scope; } const sw = scope.startsWith(_scopePrefix); return sw === full ? scope : full ? _scopePrefix + scope : scope.substring(_scopePrefix.length); }; var effaceScopes = (scopes, full = false, ensureScopes = []) => { if (typeof scopes === "string") { scopes = scopes.split(" "); } if (!Array.isArray(scopes)) { scopes = ensureScopes; } else { scopes = scopes.concat(ensureScopes); } const r = /* @__PURE__ */ new Set(); for (let scope of scopes) { if (scope) { scope = effaceScope(scope, full); } if (scope) { r.add(scope); } } return [...r]; }; // src/google/Client.js var import_googleapis2 = require("googleapis"); // src/consts.js var vault = /* @__PURE__ */ new WeakMap(); // src/google/Account.js var import_googleapis = require("googleapis"); var import_props = require("@randajan/props"); var GoogleAccount = class { constructor(client, credentials = {}) { const { access_token, refresh_token, expiry_date } = credentials; if (refresh_token && !expiry_date) { throw new Error(`OAuth2 account credentials 'refresh_token' must be provided with 'expiry_date'`); } if (!access_token && !refresh_token) { throw new Error(`OAuth2 account credentials 'access_token' of 'refresh_token' must be provided`); } const { createAuth, onRenew } = vault.get(client); const auth = createAuth(); auth.setCredentials(credentials); auth.on("tokens", (_) => { onRenew(this); }); (0, import_props.solids)(this, { client, auth }); } oauth2() { return import_googleapis.google.oauth2({ auth: this.auth, version: "v2" }); } async uid() { const { id } = await this.profile(); return `google:${id}`; } 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 effaceScopes(auth.credentials.scope); } const { token } = await auth.getAccessToken(); if (!token) { return []; } const info = await auth.getTokenInfo(token); if (!info) { return []; } return effaceScopes(info.scopes); } }; // 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 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/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/google/Client.js var GoogleOAuth2 = class { constructor(options = {}) { const { isOffline, scopes, clientId, clientSecret, redirectUri, landingUri, fallbackUri, onAuth, onRenew, getCredentials, extra } = options; const _p = {}; _p.isOffline = !!isOffline; _p.defaultScopes = effaceScopes(scopes, true, _defaultScopes); _p.landingUri = validateURL(false, landingUri, "options.landingUri"); _p.fallbackUri = validateURL(true, fallbackUri, "options.fallbackUri"); _p.onAuth = validateFn(true, onAuth, "options.onAuth"); _p.onRenew = validateFn(true, onRenew, "options.onRenew"); _p.getCredentials = validateFn(false, getCredentials, "options.getCredentials"); const commonOptions = { ...extra || {}, clientId, clientSecret, redirectUri: validateURL(true, redirectUri, "options.redirectUri") }; _p.createAuth = (_) => new import_googleapis2.google.auth.OAuth2(commonOptions); _p.auth = _p.createAuth(); this._fallbackRedirect = (majorCode, err) => { const c = RedirectError.is(err) ? err.code : 0; return extendURL(_p.fallbackUri, { errorCode: majorCode * 100 + c, errorMessage: err.message }); }; vault.set(this, _p); } account(credentials, ...args) { const { getCredentials } = vault.get(this); const c = getCredentials ? getCredentials(credentials, ...args) : credentials; return c instanceof Promise ? c.then((cr) => new GoogleAccount(this, cr)) : new GoogleAccount(this, c); } _getInitAuthURL(options = {}) { const _p = vault.get(this); const { landingUri, state, scopes, extra } = options; if ((landingUri || !_p.landingUri) && !isValidURL(landingUri)) { throw new RedirectError(1, "Bad request. Missing valid 'landingUri'"); } return _p.auth.generateAuthUrl({ ...extra || {}, access_type: _p.isOffline ? "offline" : "online", scope: effaceScopes(scopes, true, _p.defaultScopes), state: objToBase64([landingUri || _p.landingUri, state]) }); } getInitAuthURL(options = {}) { try { return this._getInitAuthURL(options); } catch (err) { return this._fallbackRedirect(1, err); } } async _getExitAuthURL({ code, state }, context) { const _p = vault.get(this); if (!code) { throw new RedirectError(201, "Bad request. Missing 'code'"); } const [landingUri, passedState] = objFromBase64(state); if (!isValidURL(landingUri)) { throw new RedirectError(202, "Bad request. Missing valid 'state'"); } const { tokens } = await _p.auth.getToken(code); const account = new GoogleAccount(this, tokens); const customUri = await _p.onAuth(account, { context, landingUri, state: passedState }); return customUri || landingUri; } getExitAuthURL({ code, state }, context) { try { return this._getExitAuthURL({ code, state }, context); } catch (err) { return this._fallbackRedirect(2, err); } } }; //# sourceMappingURL=index.js.map