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

205 lines (198 loc) 6.1 kB
import { extendURL, isValidURL, objFromBase64, objToBase64, validateFn, validateURL } from "../chunk-S4SZTRR7.js"; // 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 import { google as google2 } from "googleapis"; // src/consts.js var vault = /* @__PURE__ */ new WeakMap(); // src/google/Account.js import { google } from "googleapis"; import { solids } from "@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); }); solids(this, { client, auth }); } oauth2() { return 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/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 google2.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); } } }; export { GoogleAccount, GoogleOAuth2, effaceScopes }; //# sourceMappingURL=index.js.map