mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-15 04:10:32 +00:00
add server to monorepo.
This commit is contained in:
23
packages/server/src/api/middleware/AsyncRenderMiddleware.ts
Normal file
23
packages/server/src/api/middleware/AsyncRenderMiddleware.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { Request, Response } from 'express';
|
||||
|
||||
const asyncRender = (app) => (path: string, attributes = {}) =>
|
||||
new Promise((resolve, reject) => {
|
||||
app.render(path, attributes, (error, data) => {
|
||||
if (error) { reject(error); }
|
||||
|
||||
resolve(data);
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Injects `asyncRender` method to response object.
|
||||
* @param {Request} req Express req Object
|
||||
* @param {Response} res Express res Object
|
||||
* @param {NextFunction} next Express next Function
|
||||
*/
|
||||
const asyncRenderMiddleware = (req: Request, res: Response, next: Function) => {
|
||||
res.asyncRender = asyncRender(req.app);
|
||||
next();
|
||||
};
|
||||
|
||||
export default asyncRenderMiddleware;
|
||||
@@ -0,0 +1,39 @@
|
||||
import { Container } from 'typedi';
|
||||
import { Request, Response } from 'express';
|
||||
|
||||
/**
|
||||
* Attach user to req.currentUser
|
||||
* @param {Request} req Express req Object
|
||||
* @param {Response} res Express res Object
|
||||
* @param {NextFunction} next Express next Function
|
||||
*/
|
||||
const attachCurrentUser = async (req: Request, res: Response, next: Function) => {
|
||||
const Logger = Container.get('logger');
|
||||
const { systemUserRepository } = Container.get('repositories');
|
||||
|
||||
try {
|
||||
Logger.info('[attach_user_middleware] finding system user by id.');
|
||||
const user = await systemUserRepository.findOneById(req.token.id);
|
||||
|
||||
if (!user) {
|
||||
Logger.info('[attach_user_middleware] the system user not found.');
|
||||
return res.boom.unauthorized();
|
||||
}
|
||||
if (!user.active) {
|
||||
Logger.info('[attach_user_middleware] the system user not found.');
|
||||
return res.boom.badRequest(
|
||||
'The authorized user is inactivated.',
|
||||
{ errors: [{ type: 'USER_INACTIVE', code: 100, }] },
|
||||
);
|
||||
}
|
||||
// Delete password property from user object.
|
||||
Reflect.deleteProperty(user, 'password');
|
||||
req.user = user;
|
||||
return next();
|
||||
} catch (e) {
|
||||
Logger.error('[attach_user_middleware] error attaching user to req: %o', e);
|
||||
return next(e);
|
||||
}
|
||||
};
|
||||
|
||||
export default attachCurrentUser;
|
||||
@@ -0,0 +1,92 @@
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
import { Container } from 'typedi';
|
||||
import { Ability } from '@casl/ability';
|
||||
import LruCache from 'lru-cache';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import { IRole, IRolePremission, ISystemUser } from '@/interfaces';
|
||||
|
||||
// store abilities of 1000 most active users
|
||||
export const ABILITIES_CACHE = new LruCache(1000);
|
||||
|
||||
/**
|
||||
* Retrieve ability for the given role.
|
||||
* @param {} role
|
||||
* @returns
|
||||
*/
|
||||
function getAbilityForRole(role) {
|
||||
const rules = getAbilitiesRolesConds(role);
|
||||
return new Ability(rules);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve abilities of the given role.
|
||||
* @param {IRole} role
|
||||
* @returns {}
|
||||
*/
|
||||
function getAbilitiesRolesConds(role: IRole) {
|
||||
switch (role.slug) {
|
||||
case 'admin': // predefined role.
|
||||
return getSuperAdminRules();
|
||||
default:
|
||||
return getRulesFromRolePermissions(role.permissions || []);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the super admin rules.
|
||||
* @returns {}
|
||||
*/
|
||||
function getSuperAdminRules() {
|
||||
return [{ action: 'manage', subject: 'all' }];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve CASL rules from role permissions.
|
||||
* @param {IRolePremission[]} permissions -
|
||||
* @returns {}
|
||||
*/
|
||||
function getRulesFromRolePermissions(permissions: IRolePremission[]) {
|
||||
return permissions
|
||||
.filter((permission: IRolePremission) => permission.value)
|
||||
.map((permission: IRolePremission) => {
|
||||
return {
|
||||
action: permission.ability,
|
||||
subject: permission.subject,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve ability for user.
|
||||
* @param {ISystemUser} user
|
||||
* @param {number} tenantId
|
||||
* @returns {}
|
||||
*/
|
||||
async function getAbilityForUser(user: ISystemUser, tenantId: number) {
|
||||
const tenancy = Container.get(HasTenancyService);
|
||||
const { User } = tenancy.models(tenantId);
|
||||
|
||||
const tenantUser = await User.query()
|
||||
.findOne('systemUserId', user.id)
|
||||
.withGraphFetched('role.permissions');
|
||||
|
||||
return getAbilityForRole(tenantUser.role);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Request} request -
|
||||
* @param {Response} response -
|
||||
* @param {NextFunction} next -
|
||||
*/
|
||||
export default async (req: Request, res: Response, next: NextFunction) => {
|
||||
const { tenantId, user } = req;
|
||||
|
||||
if (ABILITIES_CACHE.has(req.user.id)) {
|
||||
req.ability = ABILITIES_CACHE.get(req.user.id);
|
||||
} else {
|
||||
req.ability = await getAbilityForUser(req.user, tenantId);
|
||||
ABILITIES_CACHE.set(req.user.id, req.ability);
|
||||
}
|
||||
next();
|
||||
};
|
||||
18
packages/server/src/api/middleware/CheckPolicies.ts
Normal file
18
packages/server/src/api/middleware/CheckPolicies.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
import { ForbiddenError } from '@casl/ability';
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
export default (ability: string, subject: string) =>
|
||||
(req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
ForbiddenError.from(req.ability).throwUnlessCan(ability, subject);
|
||||
} catch (error) {
|
||||
return res.status(403).send({
|
||||
type: 'USER_PERMISSIONS_FORBIDDEN',
|
||||
message: `You are not allowed to ${error.action} on ${error.subjectType}`,
|
||||
});
|
||||
}
|
||||
next();
|
||||
};
|
||||
@@ -0,0 +1,13 @@
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
import deepMap from 'deep-map';
|
||||
import { convertEmptyStringToNull } from 'utils';
|
||||
|
||||
function convertEmptyStringsToNull(data) {
|
||||
return deepMap(data, (value) => convertEmptyStringToNull(value));
|
||||
}
|
||||
|
||||
export default (req: Request, res: Response, next: NextFunction) => {
|
||||
const transfomedBody = convertEmptyStringsToNull(req.body);
|
||||
req.body = transfomedBody;
|
||||
next();
|
||||
};
|
||||
@@ -0,0 +1,21 @@
|
||||
import { Container } from 'typedi';
|
||||
import { Request, Response } from 'express';
|
||||
|
||||
|
||||
export default (req: Request, res: Response, next: Function) => {
|
||||
const Logger = Container.get('logger');
|
||||
|
||||
if (!req.tenant) {
|
||||
Logger.info('[ensure_tenant_intialized_middleware] no tenant model.');
|
||||
throw new Error('Should load this middleware after `TenancyMiddleware`.');
|
||||
}
|
||||
if (!req.tenant.initializedAt) {
|
||||
Logger.info('[ensure_tenant_initialized_middleware] tenant database not initalized.');
|
||||
|
||||
return res.boom.badRequest(
|
||||
'Tenant database is not migrated with application schema yut.',
|
||||
{ errors: [{ type: 'TENANT.DATABASE.NOT.INITALIZED' }] },
|
||||
);
|
||||
}
|
||||
next();
|
||||
};
|
||||
21
packages/server/src/api/middleware/EnsureTenantIsSeeded.ts
Normal file
21
packages/server/src/api/middleware/EnsureTenantIsSeeded.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { Container } from 'typedi';
|
||||
import { Request, Response } from 'express';
|
||||
|
||||
export default (req: Request, res: Response, next: Function) => {
|
||||
const Logger = Container.get('logger');
|
||||
|
||||
if (!req.tenant) {
|
||||
Logger.info('[ensure_tenant_intialized_middleware] no tenant model.');
|
||||
throw new Error('Should load this middleware after `TenancyMiddleware`.');
|
||||
}
|
||||
if (!req.tenant.seededAt) {
|
||||
Logger.info(
|
||||
'[ensure_tenant_initialized_middleware] tenant databae not seeded.'
|
||||
);
|
||||
return res.boom.badRequest(
|
||||
'Tenant database is not seeded with initial data yet.',
|
||||
{ errors: [{ type: 'TENANT.DATABASE.NOT.SEED' }] }
|
||||
);
|
||||
}
|
||||
next();
|
||||
};
|
||||
18
packages/server/src/api/middleware/FeatureActivationGuard.ts
Normal file
18
packages/server/src/api/middleware/FeatureActivationGuard.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { Request, Response } from 'express';
|
||||
import { Features } from '@/interfaces';
|
||||
|
||||
export const FeatureActivationGuard =
|
||||
(feature: Features) => (req: Request, res: Response, next: Function) => {
|
||||
const { settings } = req;
|
||||
|
||||
const isActivated = settings.get({ group: 'features', key: feature });
|
||||
|
||||
if (!isActivated) {
|
||||
return res.status(400).send({
|
||||
errors: [
|
||||
{ type: 'FEATURE_NOT_ACTIVATED', code: 20, payload: { feature } },
|
||||
],
|
||||
});
|
||||
}
|
||||
next();
|
||||
};
|
||||
@@ -0,0 +1,37 @@
|
||||
import { Container } from 'typedi';
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import { injectI18nUtils } from './TenantDependencyInjection';
|
||||
|
||||
/**
|
||||
* I18n from organization settings.
|
||||
*/
|
||||
export default (req: Request, res: Response, next: NextFunction) => {
|
||||
const Logger = Container.get('logger');
|
||||
const I18n = Container.get('i18n');
|
||||
|
||||
const { tenantId, tenant } = req;
|
||||
|
||||
if (!req.user) {
|
||||
throw new Error('Should load this middleware after `JWTAuth`.');
|
||||
}
|
||||
if (!req.settings) {
|
||||
throw new Error('Should load this middleware after `SettingsMiddleware`.');
|
||||
}
|
||||
// Get the organization language from settings.
|
||||
const { language } = tenant.metadata;
|
||||
|
||||
if (language) {
|
||||
I18n.setLocale(req, language);
|
||||
}
|
||||
Logger.info('[i18n_authenticated_middleware] set locale language to i18n.', {
|
||||
language,
|
||||
user: req.user,
|
||||
});
|
||||
const tenantServices = Container.get(HasTenancyService);
|
||||
const tenantContainer = tenantServices.tenantContainer(tenantId);
|
||||
|
||||
tenantContainer.set('i18n', injectI18nUtils(req));
|
||||
|
||||
next();
|
||||
};
|
||||
22
packages/server/src/api/middleware/I18nMiddleware.ts
Normal file
22
packages/server/src/api/middleware/I18nMiddleware.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { Container } from 'typedi';
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
import { lowerCase } from 'lodash';
|
||||
|
||||
/**
|
||||
* Set the language from request `accept-language` header
|
||||
* or default application language.
|
||||
*/
|
||||
export default (req: Request, res: Response, next: NextFunction) => {
|
||||
const Logger = Container.get('logger');
|
||||
const I18n = Container.get('i18n');
|
||||
|
||||
// Parses the accepted language from request object.
|
||||
const language = lowerCase(req.headers['accept-language']) || 'en';
|
||||
|
||||
Logger.info('[i18n_middleware] set locale language to i18n.', {
|
||||
language,
|
||||
user: req.user,
|
||||
});
|
||||
// Initialise the global localization.
|
||||
I18n.init(req, res, next);
|
||||
};
|
||||
@@ -0,0 +1,37 @@
|
||||
import { snakeCase } from 'lodash';
|
||||
import { mapKeysDeep } from 'utils';
|
||||
|
||||
/**
|
||||
* Express middleware for intercepting and transforming json responses
|
||||
*
|
||||
* @param {function} [condition] - takes the req and res and returns a boolean indicating whether to run the transform on this response
|
||||
* @param {function} transform - takes an object passed to res.json and returns a replacement object
|
||||
* @return {function} the middleware
|
||||
*/
|
||||
export function JSONResponseTransformer(transform: Function) {
|
||||
const replaceJson = (res) => {
|
||||
var origJson = res.json;
|
||||
|
||||
res.json = function (val) {
|
||||
const json = JSON.parse(JSON.stringify(val));
|
||||
|
||||
return origJson.call(res, transform(json));
|
||||
};
|
||||
};
|
||||
|
||||
return function (req, res, next) {
|
||||
replaceJson(res);
|
||||
next();
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Transformes the given response keys to snake case.
|
||||
* @param response
|
||||
* @returns
|
||||
*/
|
||||
export const snakecaseResponseTransformer = (response) => {
|
||||
return mapKeysDeep(response, (value, key) => {
|
||||
return snakeCase(key);
|
||||
});
|
||||
};
|
||||
11
packages/server/src/api/middleware/LoggerMiddleware.ts
Normal file
11
packages/server/src/api/middleware/LoggerMiddleware.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { NextFunction, Request } from 'express';
|
||||
import { Container } from 'typedi';
|
||||
|
||||
function loggerMiddleware(request: Request, response: Response, next: NextFunction) {
|
||||
const Logger = Container.get('logger');
|
||||
|
||||
Logger.info(`[routes] ${request.method} ${request.path}`);
|
||||
next();
|
||||
}
|
||||
|
||||
export default loggerMiddleware;
|
||||
@@ -0,0 +1,24 @@
|
||||
import { Container } from 'typedi';
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
import config from '@/config';
|
||||
|
||||
const MAX_CONSECUTIVE_FAILS = config.throttler.login.points;
|
||||
|
||||
export default async (req: Request, res: Response, next: NextFunction) => {
|
||||
const { crediential } = req.body;
|
||||
const loginThrottler = Container.get('rateLimiter.login');
|
||||
|
||||
// Retrieve the rate limiter response of the given crediential.
|
||||
const emailRateRes = await loginThrottler.get(crediential);
|
||||
|
||||
if (emailRateRes !== null && emailRateRes.consumedPoints >= MAX_CONSECUTIVE_FAILS) {
|
||||
const retrySecs = Math.round(emailRateRes.msBeforeNext / 1000) || 1;
|
||||
|
||||
res.set('Retry-After', retrySecs);
|
||||
res.status(429).send({
|
||||
errors: [{ type: 'LOGIN_TO_MANY_ATTEMPTS', code: 400 }],
|
||||
});
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
import {
|
||||
ValidationError,
|
||||
NotFoundError,
|
||||
DBError,
|
||||
UniqueViolationError,
|
||||
NotNullViolationError,
|
||||
ForeignKeyViolationError,
|
||||
CheckViolationError,
|
||||
DataError,
|
||||
} from 'objection';
|
||||
|
||||
// In this example `res` is an express response object.
|
||||
export default function ObjectionErrorHandlerMiddleware(
|
||||
err: Error,
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
) {
|
||||
if (err instanceof ValidationError) {
|
||||
switch (err.type) {
|
||||
case 'ModelValidation':
|
||||
return res.status(400).send({
|
||||
message: err.message,
|
||||
type: err.type,
|
||||
data: err.data,
|
||||
});
|
||||
case 'RelationExpression':
|
||||
return res.status(400).send({
|
||||
message: err.message,
|
||||
type: 'RelationExpression',
|
||||
data: {},
|
||||
});
|
||||
|
||||
case 'UnallowedRelation':
|
||||
return res.status(400).send({
|
||||
message: err.message,
|
||||
type: err.type,
|
||||
data: {},
|
||||
});
|
||||
|
||||
case 'InvalidGraph':
|
||||
return res.status(400).send({
|
||||
message: err.message,
|
||||
type: err.type,
|
||||
data: {},
|
||||
});
|
||||
|
||||
default:
|
||||
return res.status(400).send({
|
||||
message: err.message,
|
||||
type: 'UnknownValidationError',
|
||||
data: {},
|
||||
});
|
||||
}
|
||||
} else if (err instanceof NotFoundError) {
|
||||
return res.status(404).send({
|
||||
message: err.message,
|
||||
type: 'NotFound',
|
||||
data: {},
|
||||
});
|
||||
} else if (err instanceof UniqueViolationError) {
|
||||
return res.status(409).send({
|
||||
message: err.message,
|
||||
type: 'UniqueViolation',
|
||||
data: {
|
||||
columns: err.columns,
|
||||
table: err.table,
|
||||
constraint: err.constraint,
|
||||
},
|
||||
});
|
||||
} else if (err instanceof NotNullViolationError) {
|
||||
return res.status(400).send({
|
||||
message: err.message,
|
||||
type: 'NotNullViolation',
|
||||
data: {
|
||||
column: err.column,
|
||||
table: err.table,
|
||||
},
|
||||
});
|
||||
} else if (err instanceof ForeignKeyViolationError) {
|
||||
return res.status(409).send({
|
||||
message: err.message,
|
||||
type: 'ForeignKeyViolation',
|
||||
data: {
|
||||
table: err.table,
|
||||
constraint: err.constraint,
|
||||
},
|
||||
});
|
||||
} else if (err instanceof CheckViolationError) {
|
||||
return res.status(400).send({
|
||||
message: err.message,
|
||||
type: 'CheckViolation',
|
||||
data: {
|
||||
table: err.table,
|
||||
constraint: err.constraint,
|
||||
},
|
||||
});
|
||||
} else if (err instanceof DataError) {
|
||||
return res.status(400).send({
|
||||
message: err.message,
|
||||
type: 'InvalidData',
|
||||
data: {},
|
||||
});
|
||||
} else if (err instanceof DBError) {
|
||||
return res.status(500).send({
|
||||
message: err.message,
|
||||
type: 'UnknownDatabaseError',
|
||||
data: {},
|
||||
});
|
||||
}
|
||||
next(err);
|
||||
}
|
||||
16
packages/server/src/api/middleware/RateLimiterMiddleware.ts
Normal file
16
packages/server/src/api/middleware/RateLimiterMiddleware.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { Container } from 'typedi';
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
|
||||
/**
|
||||
* Rate limiter middleware.
|
||||
*/
|
||||
export default (req: Request, res: Response, next: NextFunction) => {
|
||||
const requestRateLimiter = Container.get('rateLimiter.request');
|
||||
|
||||
requestRateLimiter.attempt(req.ip).then(() => {
|
||||
next();
|
||||
})
|
||||
.catch(() => {
|
||||
res.status(429).send('Too Many Requests');
|
||||
});
|
||||
}
|
||||
27
packages/server/src/api/middleware/SettingsMiddleware.ts
Normal file
27
packages/server/src/api/middleware/SettingsMiddleware.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
import { Container } from 'typedi';
|
||||
import SettingsStore from '@/services/Settings/SettingsStore';
|
||||
|
||||
export default async (req: Request, res: Response, next: NextFunction) => {
|
||||
const { tenantId } = req.user;
|
||||
|
||||
const Logger = Container.get('logger');
|
||||
const tenantContainer = Container.of(`tenant-${tenantId}`);
|
||||
|
||||
if (tenantContainer && !tenantContainer.has('settings')) {
|
||||
const { settingRepository } = tenantContainer.get('repositories');
|
||||
|
||||
const settings = new SettingsStore(settingRepository);
|
||||
tenantContainer.set('settings', settings);
|
||||
}
|
||||
const settings = tenantContainer.get('settings');
|
||||
|
||||
await settings.load();
|
||||
|
||||
req.settings = settings;
|
||||
|
||||
res.on('finish', async () => {
|
||||
await settings.save();
|
||||
});
|
||||
next();
|
||||
}
|
||||
41
packages/server/src/api/middleware/SubscriptionMiddleware.ts
Normal file
41
packages/server/src/api/middleware/SubscriptionMiddleware.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
import { Container } from 'typedi';
|
||||
|
||||
export default (subscriptionSlug = 'main') => async (
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
) => {
|
||||
const { tenant, tenantId } = req;
|
||||
const Logger = Container.get('logger');
|
||||
const { subscriptionRepository } = Container.get('repositories');
|
||||
|
||||
if (!tenant) {
|
||||
throw new Error('Should load `TenancyMiddlware` before this middleware.');
|
||||
}
|
||||
Logger.info('[subscription_middleware] trying get tenant main subscription.');
|
||||
const subscription = await subscriptionRepository.getBySlugInTenant(
|
||||
subscriptionSlug,
|
||||
tenantId
|
||||
);
|
||||
// Validate in case there is no any already subscription.
|
||||
if (!subscription) {
|
||||
Logger.info('[subscription_middleware] tenant has no subscription.', {
|
||||
tenantId,
|
||||
});
|
||||
return res.boom.badRequest('Tenant has no subscription.', {
|
||||
errors: [{ type: 'TENANT.HAS.NO.SUBSCRIPTION' }],
|
||||
});
|
||||
}
|
||||
// Validate in case the subscription is inactive.
|
||||
else if (subscription.inactive()) {
|
||||
Logger.info(
|
||||
'[subscription_middleware] tenant main subscription is expired.',
|
||||
{ tenantId }
|
||||
);
|
||||
return res.boom.badRequest(null, {
|
||||
errors: [{ type: 'ORGANIZATION.SUBSCRIPTION.INACTIVE' }],
|
||||
});
|
||||
}
|
||||
next();
|
||||
};
|
||||
36
packages/server/src/api/middleware/TenancyMiddleware.ts
Normal file
36
packages/server/src/api/middleware/TenancyMiddleware.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { Container } from 'typedi';
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
import tenantDependencyInjection from '@/api/middleware/TenantDependencyInjection';
|
||||
import { Tenant } from '@/system/models';
|
||||
|
||||
export default async (req: Request, res: Response, next: NextFunction) => {
|
||||
const Logger = Container.get('logger');
|
||||
const organizationId =
|
||||
req.headers['organization-id'] || req.query.organization;
|
||||
|
||||
const notFoundOrganization = () => {
|
||||
Logger.info('[tenancy_middleware] organization id not found.');
|
||||
return res.boom.unauthorized('Organization identication not found.', {
|
||||
errors: [{ type: 'ORGANIZATION.ID.NOT.FOUND', code: 100 }],
|
||||
});
|
||||
};
|
||||
// In case the given organization not found.
|
||||
if (!organizationId) {
|
||||
return notFoundOrganization();
|
||||
}
|
||||
const tenant = await Tenant.query()
|
||||
.findOne({ organizationId })
|
||||
.withGraphFetched('metadata');
|
||||
|
||||
// When the given organization id not found on the system storage.
|
||||
if (!tenant) {
|
||||
return notFoundOrganization();
|
||||
}
|
||||
// When user tenant not match the given organization id.
|
||||
if (tenant.id !== req.user.tenantId) {
|
||||
Logger.info('[tenancy_middleware] authorized user not match org. tenant.');
|
||||
return res.boom.unauthorized();
|
||||
}
|
||||
tenantDependencyInjection(req, tenant);
|
||||
next();
|
||||
};
|
||||
@@ -0,0 +1,46 @@
|
||||
import { Container } from 'typedi';
|
||||
import { ITenant } from '@/interfaces';
|
||||
import { Request } from 'express';
|
||||
import TenancyService from '@/services/Tenancy/TenancyService';
|
||||
import TenantsManagerService from '@/services/Tenancy/TenantsManager';
|
||||
import rtlDetect from 'rtl-detect';
|
||||
|
||||
export default (req: Request, tenant: ITenant) => {
|
||||
const { id: tenantId, organizationId } = tenant;
|
||||
|
||||
const tenantServices = Container.get(TenancyService);
|
||||
const tenantsManager = Container.get(TenantsManagerService);
|
||||
|
||||
// Initialize the knex instance.
|
||||
tenantsManager.setupKnexInstance(tenant);
|
||||
|
||||
const tenantContainer = tenantServices.tenantContainer(tenantId);
|
||||
|
||||
tenantContainer.set('i18n', injectI18nUtils(req));
|
||||
|
||||
const knexInstance = tenantServices.knex(tenantId);
|
||||
const models = tenantServices.models(tenantId);
|
||||
const repositories = tenantServices.repositories(tenantId);
|
||||
const cacheInstance = tenantServices.cache(tenantId);
|
||||
|
||||
req.knex = knexInstance;
|
||||
req.organizationId = organizationId;
|
||||
req.tenant = tenant;
|
||||
req.tenantId = tenant.id;
|
||||
req.models = models;
|
||||
req.repositories = repositories;
|
||||
req.cache = cacheInstance;
|
||||
};
|
||||
|
||||
export const injectI18nUtils = (req) => {
|
||||
const locale = req.getLocale();
|
||||
const direction = rtlDetect.getLangDir(locale);
|
||||
|
||||
return {
|
||||
locale,
|
||||
__: req.__,
|
||||
direction,
|
||||
isRtl: direction === 'rtl',
|
||||
isLtr: direction === 'ltr',
|
||||
};
|
||||
};
|
||||
14
packages/server/src/api/middleware/asyncMiddleware.ts
Normal file
14
packages/server/src/api/middleware/asyncMiddleware.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
import { Container } from 'typedi';
|
||||
|
||||
export default (
|
||||
fn: (rq: Request, rs: Response, next?: NextFunction) => {}) =>
|
||||
(req: Request, res: Response, next: NextFunction) => {
|
||||
const Logger = Container.get('logger');
|
||||
|
||||
Promise.resolve(fn(req, res, next))
|
||||
.catch((error) => {
|
||||
Logger.error('[async_middleware] error.', { error });
|
||||
next(error);
|
||||
});
|
||||
};
|
||||
32
packages/server/src/api/middleware/jwtAuth.ts
Normal file
32
packages/server/src/api/middleware/jwtAuth.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
import { Container } from 'typedi';
|
||||
import jwt from 'jsonwebtoken';
|
||||
import config from '@/config';
|
||||
|
||||
const authMiddleware = (req: Request, res: Response, next: NextFunction) => {
|
||||
const Logger = Container.get('logger');
|
||||
const token = req.headers['x-access-token'] || req.query.token;
|
||||
|
||||
const onError = () => {
|
||||
Logger.info('[auth_middleware] jwt verify error.');
|
||||
res.boom.unauthorized();
|
||||
};
|
||||
const onSuccess = (decoded) => {
|
||||
req.token = decoded;
|
||||
Logger.info('[auth_middleware] jwt verify success.');
|
||||
next();
|
||||
};
|
||||
if (!token) { return onError(); }
|
||||
|
||||
const verify = new Promise((resolve, reject) => {
|
||||
jwt.verify(token, config.jwtSecret, async (error, decoded) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
} else {
|
||||
resolve(decoded);
|
||||
}
|
||||
});
|
||||
});
|
||||
verify.then(onSuccess).catch(onError);
|
||||
};
|
||||
export default authMiddleware;
|
||||
Reference in New Issue
Block a user