@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
JavaScript
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