@frontegg/client
Advanced tools
Comparing version
@@ -61,3 +61,3 @@ "use strict"; | ||
switch (_a.label) { | ||
case 0: return [4 /*yield*/, this.authenticate()]; | ||
case 0: return [4 /*yield*/, this.authenticate(true)]; | ||
case 1: | ||
@@ -86,3 +86,4 @@ _a.sent(); | ||
}; | ||
FronteggAuthenticator.prototype.authenticate = function () { | ||
FronteggAuthenticator.prototype.authenticate = function (force) { | ||
if (force === void 0) { force = false; } | ||
return __awaiter(this, void 0, void 0, function () { | ||
@@ -94,2 +95,5 @@ var response, e_1, _a, token, expiresIn, nextRefresh; | ||
case 0: | ||
if (this.accessToken !== '' && !force) { | ||
return [2 /*return*/]; | ||
} | ||
logger_1.default.info('posting authentication request'); | ||
@@ -96,0 +100,0 @@ _b.label = 1; |
@@ -72,11 +72,12 @@ "use strict"; | ||
return __awaiter(this, void 0, void 0, function () { | ||
var authenticator, response, publicKey; | ||
return __generator(this, function (_a) { | ||
switch (_a.label) { | ||
var authenticator, _a, FRONTEGG_CLIENT_ID, FRONTEGG_API_KEY, response, publicKey; | ||
return __generator(this, function (_b) { | ||
switch (_b.label) { | ||
case 0: | ||
authenticator = new authenticator_1.FronteggAuthenticator(); | ||
logger_1.default.info('going to authenticate'); | ||
return [4 /*yield*/, authenticator.init(middleware_1.FRONTEGG_CLIENT_ID || process.env.FRONTEGG_CLIENT_ID || '', middleware_1.FRONTEGG_API_KEY || process.env.FRONTEGG_API_KEY || '')]; | ||
_a = middleware_1.ContextHolder.getContext(), FRONTEGG_CLIENT_ID = _a.FRONTEGG_CLIENT_ID, FRONTEGG_API_KEY = _a.FRONTEGG_API_KEY; | ||
return [4 /*yield*/, authenticator.init(FRONTEGG_CLIENT_ID || process.env.FRONTEGG_CLIENT_ID || '', FRONTEGG_API_KEY || process.env.FRONTEGG_API_KEY || '')]; | ||
case 1: | ||
_a.sent(); | ||
_b.sent(); | ||
logger_1.default.info('going to get identity service configuration'); | ||
@@ -89,3 +90,3 @@ return [4 /*yield*/, axios_1.default.get(config_1.config.urls.identityService + "/resources/configurations/v1", { | ||
case 2: | ||
response = _a.sent(); | ||
response = _b.sent(); | ||
logger_1.default.info('got identity service configuration'); | ||
@@ -92,0 +93,0 @@ publicKey = response.data.publicKey; |
@@ -1,36 +0,4 @@ | ||
/// <reference types="node" /> | ||
import { NextFunction, Request, Response } from 'express'; | ||
import { IncomingMessage, ServerResponse } from 'http'; | ||
import { FronteggPermissions } from '../permissions'; | ||
export declare let FRONTEGG_CLIENT_ID: string; | ||
export declare let FRONTEGG_API_KEY: string; | ||
declare type fronteggContextResolver = (req: Request) => Promise<{ | ||
tenantId: string; | ||
userId: string; | ||
permissions: FronteggPermissions[]; | ||
}>; | ||
declare type AuthMiddleware = (req: Request, res: Response, next: NextFunction) => Promise<any> | any; | ||
export interface IFronteggOptions { | ||
clientId: string; | ||
apiKey: string; | ||
contextResolver: fronteggContextResolver; | ||
authMiddleware?: AuthMiddleware; | ||
disableCors?: boolean; | ||
cookieDomainRewrite?: string; | ||
maxRetries?: number; | ||
} | ||
export declare function frontegg(options: IFronteggOptions): (req: any, res: any) => Promise<any>; | ||
declare type ProxyIncomingMessage = IncomingMessage & Partial<{ | ||
host: string; | ||
hostname: string; | ||
originalUrl: string; | ||
}>; | ||
declare type ProxyServerResponse<T = any> = ServerResponse & { | ||
status: (statusCode: number) => ProxyServerResponse<T>; | ||
send: (body: T) => void; | ||
}; | ||
declare type INextJsFronteggOptions = IFronteggOptions & { | ||
prefixRoute: string; | ||
}; | ||
export declare const fronteggNextJs: (options: INextJsFronteggOptions) => (req: ProxyIncomingMessage, res: ProxyServerResponse<any>) => Promise<any>; | ||
export {}; | ||
export * from './fronteggMiddleware'; | ||
export * from './fronteggNextJs'; | ||
export * from './types'; | ||
export * from './ContextHolder'; |
"use strict"; | ||
var __assign = (this && this.__assign) || function () { | ||
__assign = Object.assign || function(t) { | ||
for (var s, i = 1, n = arguments.length; i < n; i++) { | ||
s = arguments[i]; | ||
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) | ||
t[p] = s[p]; | ||
} | ||
return t; | ||
}; | ||
return __assign.apply(this, arguments); | ||
}; | ||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||
return new (P || (P = Promise))(function (resolve, reject) { | ||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } | ||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } | ||
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } | ||
step((generator = generator.apply(thisArg, _arguments || [])).next()); | ||
}); | ||
}; | ||
var __generator = (this && this.__generator) || function (thisArg, body) { | ||
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; | ||
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; | ||
function verb(n) { return function (v) { return step([n, v]); }; } | ||
function step(op) { | ||
if (f) throw new TypeError("Generator is already executing."); | ||
while (_) try { | ||
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; | ||
if (y = 0, t) op = [op[0] & 2, t.value]; | ||
switch (op[0]) { | ||
case 0: case 1: t = op; break; | ||
case 4: _.label++; return { value: op[1], done: false }; | ||
case 5: _.label++; y = op[1]; op = [0]; continue; | ||
case 7: op = _.ops.pop(); _.trys.pop(); continue; | ||
default: | ||
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } | ||
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } | ||
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } | ||
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } | ||
if (t[2]) _.ops.pop(); | ||
_.trys.pop(); continue; | ||
} | ||
op = body.call(thisArg, _); | ||
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } | ||
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; | ||
} | ||
}; | ||
var _this = this; | ||
function __export(m) { | ||
for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p]; | ||
} | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
var httpProxy = require("http-proxy"); | ||
var authenticator_1 = require("../authenticator"); | ||
var logger_1 = require("../helpers/logger"); | ||
var permissions_1 = require("../permissions"); | ||
var FronteggRoutes_1 = require("./FronteggRoutes"); | ||
var proxy = httpProxy.createProxyServer({ secure: false, changeOrigin: true }); | ||
var target = process.env.FRONTEGG_API_GATEWAY_URL || 'https://api.frontegg.com/'; | ||
var authenticator = new authenticator_1.FronteggAuthenticator(); | ||
var Whitelist = ['/metadata']; | ||
var MAX_RETRIES = 3; | ||
function getUrlWithoutQueryParams(req) { | ||
return req.url.split('?').shift(); | ||
} | ||
function rewriteCookieDomain(header, oldDomain, newDomain) { | ||
if (Array.isArray(header)) { | ||
return header.map(function (headerElement) { | ||
return rewriteCookieDomain(headerElement, oldDomain, newDomain); | ||
}); | ||
} | ||
return header.replace(new RegExp("(;\\s*domain=)" + oldDomain + ";", 'i'), "$1" + newDomain + ";"); | ||
} | ||
function proxyRequest(req, res, context) { | ||
return __awaiter(this, void 0, void 0, function () { | ||
return __generator(this, function (_a) { | ||
switch (_a.label) { | ||
case 0: | ||
logger_1.default.log("going to proxy request - " + req.originalUrl + " to " + target); | ||
return [4 /*yield*/, proxy.web(req, res, { | ||
target: target, | ||
headers: { | ||
'x-access-token': authenticator.accessToken, | ||
'frontegg-tenant-id': context && context.tenantId ? context.tenantId : '', | ||
'frontegg-user-id': context && context.userId ? context.userId : '', | ||
'frontegg-vendor-host': req.hostname, | ||
}, | ||
})]; | ||
case 1: | ||
_a.sent(); | ||
return [2 /*return*/]; | ||
} | ||
}); | ||
}); | ||
} | ||
function flattenPermissions(permissions) { | ||
var output = []; | ||
for (var _i = 0, permissions_2 = permissions; _i < permissions_2.length; _i++) { | ||
var p = permissions_2[_i]; | ||
// noinspection SuspiciousTypeOfGuard | ||
if (typeof (p) === 'string') { | ||
output.push(p); | ||
} | ||
else if (Array.isArray(p)) { | ||
for (var _a = 0, p_1 = p; _a < p_1.length; _a++) { | ||
var item = p_1[_a]; | ||
output.push(item); | ||
} | ||
} | ||
else { | ||
logger_1.default.log('running keys on - ', p); | ||
var keys = Object.keys(p); | ||
for (var _b = 0, keys_1 = keys; _b < keys_1.length; _b++) { | ||
var key = keys_1[_b]; | ||
output.push.apply(output, flattenPermissions([p[key]])); | ||
} | ||
} | ||
} | ||
return output; | ||
} | ||
function validatePermissions(req, res, context) { | ||
return __awaiter(this, void 0, void 0, function () { | ||
var permissions, url, _i, Whitelist_1, whiteListed, allowedOperations, _a, allowedOperations_1, operation, allowedMethod, route; | ||
return __generator(this, function (_b) { | ||
permissions = context.permissions; | ||
if (!permissions) { | ||
logger_1.default.error('No permissions were passed for frontegg middleware'); | ||
throw new Error('No permissions were passed for frontegg middleware'); | ||
} | ||
if (!permissions.length) { | ||
logger_1.default.error('Permissions array is empty for frontegg middleware'); | ||
throw new Error('Permissions array is empty for frontegg middleware'); | ||
} | ||
// We allow OPTIONS | ||
if (req.method === 'OPTIONS') { | ||
logger_1.default.log('OPTIONS is allowed'); | ||
return [2 /*return*/]; | ||
} | ||
if (permissions.includes(permissions_1.FronteggPermissions.All)) { | ||
logger_1.default.log('User is authorized for ALL actions in the system'); | ||
return [2 /*return*/]; | ||
} | ||
url = getUrlWithoutQueryParams(req); | ||
for (_i = 0, Whitelist_1 = Whitelist; _i < Whitelist_1.length; _i++) { | ||
whiteListed = Whitelist_1[_i]; | ||
if (url.startsWith(whiteListed)) { | ||
logger_1.default.log("URL " + url + " is whitelisted"); | ||
return [2 /*return*/]; | ||
} | ||
} | ||
allowedOperations = flattenPermissions(permissions); | ||
logger_1.default.log("allowedOperations for this user - ", allowedOperations); | ||
for (_a = 0, allowedOperations_1 = allowedOperations; _a < allowedOperations_1.length; _a++) { | ||
operation = allowedOperations_1[_a]; | ||
if (operation === '*') { | ||
logger_1.default.log("All operations are allowed for this user"); | ||
return [2 /*return*/]; | ||
} | ||
allowedMethod = operation.split(' ')[0]; | ||
route = operation.split(' ')[1]; | ||
if (allowedMethod === '*' && url.startsWith(route)) { | ||
logger_1.default.log("User is authorized for ALL route"); | ||
return [2 /*return*/]; | ||
} | ||
if (url === route && req.method === allowedMethod) { | ||
logger_1.default.log("User is authorized for " + req.method + " " + req.baseUrl); | ||
return [2 /*return*/]; | ||
} | ||
} | ||
logger_1.default.error("No matching permission for " + req.method + " " + url + ". Permissions - " + allowedOperations); | ||
throw new Error("No matching permission for " + req.method + " " + url); | ||
}); | ||
}); | ||
} | ||
function callMiddleware(req, res, middleware) { | ||
return __awaiter(this, void 0, void 0, function () { | ||
var middlewareWrap, nextValue; | ||
var _this = this; | ||
return __generator(this, function (_a) { | ||
switch (_a.label) { | ||
case 0: | ||
middlewareWrap = new Promise(function (next, reject) { return __awaiter(_this, void 0, void 0, function () { | ||
var e_1; | ||
return __generator(this, function (_a) { | ||
switch (_a.label) { | ||
case 0: | ||
_a.trys.push([0, 2, , 3]); | ||
return [4 /*yield*/, middleware(req, res, next)]; | ||
case 1: | ||
_a.sent(); | ||
return [3 /*break*/, 3]; | ||
case 2: | ||
e_1 = _a.sent(); | ||
reject(e_1); | ||
return [3 /*break*/, 3]; | ||
case 3: | ||
next(); | ||
return [2 /*return*/]; | ||
} | ||
}); | ||
}); }); | ||
return [4 /*yield*/, middlewareWrap]; | ||
case 1: | ||
nextValue = _a.sent(); | ||
if (nextValue) { | ||
throw new Error(nextValue); | ||
} | ||
return [2 /*return*/]; | ||
} | ||
}); | ||
}); | ||
} | ||
function frontegg(options) { | ||
var _this = this; | ||
var _a = options.maxRetries, maxRetries = _a === void 0 ? MAX_RETRIES : _a; | ||
logger_1.default.debug('you got to my frontegg middleware'); | ||
if (!options) { | ||
throw new Error('Missing options'); | ||
} | ||
if (!options.clientId) { | ||
throw new Error('Missing client ID'); | ||
} | ||
if (!options.apiKey) { | ||
throw new Error('Missing api key'); | ||
} | ||
if (!options.contextResolver) { | ||
throw new Error('Missing context resolver'); | ||
} | ||
exports.FRONTEGG_CLIENT_ID = options.clientId; | ||
exports.FRONTEGG_API_KEY = options.apiKey; | ||
var authInitedPromise = authenticator.init(options.clientId, options.apiKey); | ||
proxy.on('error', function (err, req, res, _) { return __awaiter(_this, void 0, void 0, function () { | ||
var context; | ||
return __generator(this, function (_a) { | ||
switch (_a.label) { | ||
case 0: | ||
logger_1.default.error("Failed proxy request to " + req.url + " - ", err); | ||
req.frontegg.retryCount++; | ||
logger_1.default.info("retry count of " + req.url + " - ", req.frontegg.retryCount); | ||
if (req.frontegg.retryCount >= maxRetries) { | ||
res.writeHead(500).end('Frontegg request failed'); | ||
return [2 /*return*/]; | ||
} | ||
return [4 /*yield*/, options.contextResolver(req)]; | ||
case 1: | ||
context = _a.sent(); | ||
return [2 /*return*/, proxyRequest(req, res, context)]; | ||
} | ||
}); | ||
}); }); | ||
proxy.on('proxyRes', function (proxyRes, req, res) { return __awaiter(_this, void 0, void 0, function () { | ||
var context, host_1; | ||
return __generator(this, function (_a) { | ||
switch (_a.label) { | ||
case 0: | ||
logger_1.default.debug("proxyRes - returned for " + req.originalUrl); | ||
if (!options.disableCors) { | ||
enableCors(req, proxyRes); | ||
} | ||
else { | ||
delete proxyRes.headers['access-control-allow-methods']; | ||
delete proxyRes.headers['access-control-allow-headers']; | ||
delete proxyRes.headers['access-control-allow-origin']; | ||
delete proxyRes.headers['access-control-allow-credentials']; | ||
} | ||
if (!(proxyRes.statusCode === 401)) return [3 /*break*/, 3]; | ||
req.frontegg.retryCount = req.frontegg.retryCount + 1; | ||
logger_1.default.log(req.url + " failed with authentication error from proxy - retryCount - ", req.frontegg.retryCount); | ||
if (!(req.frontegg.retryCount <= MAX_RETRIES)) return [3 /*break*/, 3]; | ||
logger_1.default.warn('going to refresh authentication'); | ||
return [4 /*yield*/, authenticator.refreshAuthentication()]; | ||
case 1: | ||
_a.sent(); | ||
logger_1.default.warn('refreshed authentication'); | ||
return [4 /*yield*/, options.contextResolver(req)]; | ||
case 2: | ||
context = _a.sent(); | ||
return [2 /*return*/, proxyRequest(req, res, context)]; | ||
case 3: | ||
if (options.cookieDomainRewrite) { | ||
host_1 = req.headers.host; | ||
Object.keys(proxyRes.headers).forEach(function (key) { | ||
if (key.toLowerCase() === 'set-cookie') { | ||
proxyRes.headers[key] = rewriteCookieDomain(proxyRes.headers[key], host_1, options.cookieDomainRewrite); | ||
} | ||
}); | ||
} | ||
return [2 /*return*/]; | ||
} | ||
}); | ||
}); }); | ||
function enableCors(req, res) { | ||
if (req.headers['access-control-request-method']) { | ||
logger_1.default.debug("enableCors - going to set access-control-request-method"); | ||
res.headers['access-control-allow-methods'] = req.headers['access-control-request-method']; | ||
} | ||
if (req.headers['access-control-request-headers']) { | ||
logger_1.default.debug("enableCors - going to set access-control-request-headers"); | ||
res.headers['access-control-allow-headers'] = req.headers['access-control-request-headers']; | ||
} | ||
if (req.headers.origin) { | ||
logger_1.default.debug("enableCors - going to set access-control-allow-origin to " + req.headers.origin); | ||
res.headers['access-control-allow-origin'] = req.headers.origin; | ||
res.headers['access-control-allow-credentials'] = 'true'; | ||
} | ||
} | ||
proxy.on('proxyReq', function (proxyReq, req, res, _) { | ||
if (req.hostname) { | ||
proxyReq.setHeader('frontegg-vendor-host', req.hostname); | ||
} | ||
if (req.body) { | ||
var bodyData = Buffer.isBuffer(req.body) ? req.body : JSON.stringify(req.body); | ||
// in case if content-type is application/x-www-form-urlencoded -> we need to change to application/json | ||
proxyReq.setHeader('Content-Type', 'application/json'); | ||
proxyReq.setHeader('Content-Length', Buffer.byteLength(bodyData)); | ||
// stream the content | ||
proxyReq.write(bodyData); | ||
} | ||
}); | ||
// tslint:disable-next-line:only-arrow-functions | ||
return function (req, res) { return __awaiter(_this, void 0, void 0, function () { | ||
var _a, e_2, context, e_3; | ||
return __generator(this, function (_b) { | ||
switch (_b.label) { | ||
case 0: return [4 /*yield*/, authInitedPromise]; | ||
case 1: | ||
_b.sent(); | ||
_a = options.authMiddleware; | ||
if (!_a) return [3 /*break*/, 3]; | ||
return [4 /*yield*/, FronteggRoutes_1.fronteggRoutes.isFronteggPublicRoute(req)]; | ||
case 2: | ||
_a = !(_b.sent()); | ||
_b.label = 3; | ||
case 3: | ||
if (!_a) return [3 /*break*/, 7]; | ||
logger_1.default.debug('will pass request threw the auth middleware'); | ||
_b.label = 4; | ||
case 4: | ||
_b.trys.push([4, 6, , 7]); | ||
return [4 /*yield*/, callMiddleware(req, res, options.authMiddleware)]; | ||
case 5: | ||
_b.sent(); | ||
if (res.headersSent) { | ||
// response was already sent from the middleware, we have nothing left to do | ||
return [2 /*return*/]; | ||
} | ||
return [3 /*break*/, 7]; | ||
case 6: | ||
e_2 = _b.sent(); | ||
logger_1.default.error("Failed to call middleware - ", e_2); | ||
return [2 /*return*/, res.status(401).send(e_2.message)]; | ||
case 7: | ||
logger_1.default.debug("going to resolve resolve context"); | ||
return [4 /*yield*/, options.contextResolver(req)]; | ||
case 8: | ||
context = _b.sent(); | ||
logger_1.default.debug("context resolved - " + JSON.stringify(context)); | ||
if (req.method === 'OPTIONS') { | ||
// tslint:disable-next-line:no-console | ||
console.log('OPTIONS call to frontegg middleware - returning STATUS 204'); | ||
res.status(204).send(); | ||
return [2 /*return*/]; | ||
} | ||
logger_1.default.debug("going to validate permissions for - ", req.url); | ||
_b.label = 9; | ||
case 9: | ||
_b.trys.push([9, 11, , 12]); | ||
return [4 /*yield*/, validatePermissions(req, res, context)]; | ||
case 10: | ||
_b.sent(); | ||
return [3 /*break*/, 12]; | ||
case 11: | ||
e_3 = _b.sent(); | ||
logger_1.default.error('Failed at permissions check - ', e_3); | ||
return [2 /*return*/, res.status(403).send()]; | ||
case 12: | ||
if (!req.frontegg) { | ||
req.frontegg = {}; | ||
} | ||
req.frontegg.retryCount = 0; | ||
logger_1.default.debug("going to proxy request"); | ||
return [2 /*return*/, proxyRequest(req, res, context)]; | ||
} | ||
}); | ||
}); }; | ||
} | ||
exports.frontegg = frontegg; | ||
exports.fronteggNextJs = function (options) { | ||
var fronteggProxy = frontegg(__assign({ maxRetries: 0 }, options)); | ||
return function (req, res) { return __awaiter(_this, void 0, void 0, function () { | ||
return __generator(this, function (_a) { | ||
if (!req.url) { | ||
res.status(500).send('Internal Server Error'); | ||
return [2 /*return*/]; | ||
} | ||
req.host = req.headers.host; | ||
req.hostname = req.headers.host; | ||
req.originalUrl = req.url; | ||
if (req.url.startsWith(options.prefixRoute)) { | ||
req.url = req.url.substring(options.prefixRoute.length); | ||
} | ||
return [2 /*return*/, fronteggProxy(req, res)]; | ||
}); | ||
}); }; | ||
}; | ||
__export(require("./fronteggMiddleware")); | ||
__export(require("./fronteggNextJs")); | ||
__export(require("./ContextHolder")); | ||
//# sourceMappingURL=index.js.map |
{ | ||
"name": "@frontegg/client", | ||
"version": "1.2.2", | ||
"version": "1.2.3", | ||
"description": "Frontegg Javascript Library for backend", | ||
@@ -5,0 +5,0 @@ "main": "dist/index.js", |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
243351
9.08%117
14.71%3605
8.19%5
25%2
100%