@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
192 lines (185 loc) • 5.78 kB
JavaScript
import {
extendURL,
formatCredentials,
isValidURL,
objFromBase64,
objToBase64,
validateFn,
validateURL
} from "./chunk-RGLWNWDM.js";
// src/consts.js
var vault = /* @__PURE__ */ new WeakMap();
// src/class/OAuth2Client.js
import { virtuals } from "@randajan/props";
var OAuth2Client = class {
constructor(Grant, options = {}) {
const grant = new Grant(this, options);
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
import { solids } from "@randajan/props";
var OAuth2Account = class {
constructor(client, credentials = {}) {
credentials = formatCredentials(credentials);
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/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 });
}
};
export {
vault,
OAuth2Client,
OAuth2Account,
RedirectError,
OAuth2Grant
};
//# sourceMappingURL=chunk-27ATWD3Y.js.map