Skip to content

Commit

Permalink
feat(tenant-management): change the table name to tenant mgmt configs
Browse files Browse the repository at this point in the history
add another interceptor for callback methods

GH-47
  • Loading branch information
yeshamavani committed Oct 7, 2024
1 parent add92c6 commit ff7705e
Show file tree
Hide file tree
Showing 16 changed files with 191 additions and 74 deletions.
Original file line number Diff line number Diff line change
@@ -1 +1 @@
drop table main.tenant_configs;
drop table main.tenant_mgmt_configs;
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
CREATE TABLE IF NOT EXISTS main.tenant_configs
CREATE TABLE IF NOT EXISTS main.tenant_mgmt_configs
(
id uuid NOT NULL DEFAULT (md5(((random())::text || (clock_timestamp())::text)))::uuid,
config_key varchar(100) NOT NULL,
Expand Down Expand Up @@ -28,6 +28,6 @@ END;
$function$;

CREATE TRIGGER mdt_tenant_configs
BEFORE UPDATE ON main.tenant_configs
BEFORE UPDATE ON main.tenant_mgmt_configs
FOR EACH ROW
EXECUTE FUNCTION main.moddatetime('modified_on');
16 changes: 8 additions & 8 deletions services/tenant-management-service/src/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ import {
LeadController,
LeadTenantController,
PingController,
TenantConfigController,
TenantConfigTenantController,
TenantMgmtConfigController,
TenantMgmtConfigTenantController,
TenantController,
} from './controllers';
import {InvoiceController} from './controllers/invoice.controller';
Expand All @@ -58,7 +58,7 @@ import {
ProvisioningDTO,
Resource,
Tenant,
TenantConfig,
TenantMgmtConfig,
TenantOnboardDTO,
VerifyLeadResponseDTO,
WebhookDTO,
Expand All @@ -72,7 +72,7 @@ import {
LeadRepository,
LeadTokenRepository,
ResourceRepository,
TenantConfigRepository,
TenantMgmtConfigRepository,
TenantRepository,
WebhookSecretRepository,
} from './repositories';
Expand Down Expand Up @@ -126,7 +126,7 @@ export class TenantManagementServiceComponent implements Component {
ResourceRepository,
TenantRepository,
WebhookSecretRepository,
TenantConfigRepository,
TenantMgmtConfigRepository,
];

this.models = [
Expand All @@ -143,7 +143,7 @@ export class TenantManagementServiceComponent implements Component {
TenantOnboardDTO,
VerifyLeadResponseDTO,
WebhookDTO,
TenantConfig,
TenantMgmtConfig,
];

this.controllers = [
Expand All @@ -154,8 +154,8 @@ export class TenantManagementServiceComponent implements Component {
LeadController,
PingController,
TenantController,
TenantConfigController,
TenantConfigTenantController,
TenantMgmtConfigController,
TenantMgmtConfigTenantController,
];

this.bindings = [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
} from '@sourceloop/core';
import {authorize} from 'loopback4-authorization';
import {ratelimit} from 'loopback4-ratelimiter';
import {TenantManagementServiceBindings, WEBHOOK_VERIFIER} from '../keys';
import {TenantManagementServiceBindings, CALLABCK_VERIFIER} from '../keys';
import {IdpDetailsDTO} from '../models/dtos/idp-details-dto.model';
import {ConfigureIdpFunc, IdPKey, IdpResp} from '../types';

Expand All @@ -20,7 +20,8 @@ export class IdpController {
@inject(TenantManagementServiceBindings.IDP_AUTH0)
private readonly idpAuth0Provider: ConfigureIdpFunc<IdpResp>,
) {}
@intercept(WEBHOOK_VERIFIER)

@intercept(CALLABCK_VERIFIER)
@ratelimit(true, {
max: parseInt(process.env.WEBHOOK_API_MAX_ATTEMPTS ?? '10'),
keyGenerator: rateLimitKeyGenPublic,
Expand Down
4 changes: 2 additions & 2 deletions services/tenant-management-service/src/controllers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@ export * from './lead-tenant.controller';
export * from './tenant.controller';
export * from './webhook.controller';
export * from './invoice.controller';
export * from './tenant-config.controller';
export * from './tenant-config-tenant.controller';
export * from './tenant-mgmt-config.controller';
export * from './tenant-mgmt-config-tenant.controller';
export * from './idp.controller';
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
import {repository} from '@loopback/repository';
import {param, get, getModelSchemaRef} from '@loopback/rest';
import {TenantConfig, Tenant} from '../models';
import {TenantConfigRepository} from '../repositories';
import {TenantMgmtConfig, Tenant} from '../models';
import {TenantMgmtConfigRepository} from '../repositories';
import {authenticate, STRATEGY} from 'loopback4-authentication';
import {authorize} from 'loopback4-authorization';
import {PermissionKey} from '../permissions';
import {OPERATION_SECURITY_SPEC, STATUS_CODE} from '@sourceloop/core';

const basePath = '/tenant-configs/{id}/tenant';
export class TenantConfigTenantController {
export class TenantMgmtConfigTenantController {
constructor(
@repository(TenantConfigRepository)
public tenantConfigRepository: TenantConfigRepository,
@repository(TenantMgmtConfigRepository)
public tenantConfigRepository: TenantMgmtConfigRepository,
) {}
@authorize({
permissions: [PermissionKey.ViewTenantConfig],
Expand All @@ -32,7 +33,7 @@ export class TenantConfigTenantController {
},
})
async getTenant(
@param.path.string('id') id: typeof TenantConfig.prototype.id,
@param.path.string('id') id: typeof TenantMgmtConfig.prototype.id,
): Promise<Tenant> {
return this.tenantConfigRepository.tenant(id);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ import {
del,
requestBody,
} from '@loopback/rest';
import {TenantConfig} from '../models';
import {TenantConfigRepository} from '../repositories';
import {TenantMgmtConfig} from '../models';
import {TenantMgmtConfigRepository} from '../repositories';
import {authenticate, STRATEGY} from 'loopback4-authentication';
import {authorize} from 'loopback4-authorization';
import {PermissionKey} from '../permissions';
Expand All @@ -27,10 +27,10 @@ import {
STATUS_CODE,
} from '@sourceloop/core';
const basePath = '/tenant-configs';
export class TenantConfigController {
export class TenantMgmtConfigController {
constructor(
@repository(TenantConfigRepository)
public tenantConfigRepository: TenantConfigRepository,
@repository(TenantMgmtConfigRepository)
public tenantConfigRepository: TenantMgmtConfigRepository,
) {}
@authorize({
permissions: [PermissionKey.CreateTenantConfig],
Expand All @@ -44,7 +44,7 @@ export class TenantConfigController {
[STATUS_CODE.OK]: {
description: 'Tenant Config model instance',
content: {
[CONTENT_TYPE.JSON]: {schema: getModelSchemaRef(TenantConfig)},
[CONTENT_TYPE.JSON]: {schema: getModelSchemaRef(TenantMgmtConfig)},
},
},
},
Expand All @@ -53,15 +53,15 @@ export class TenantConfigController {
@requestBody({
content: {
'application/json': {
schema: getModelSchemaRef(TenantConfig, {
schema: getModelSchemaRef(TenantMgmtConfig, {
title: 'NewTenantConfig',
exclude: ['id'],
}),
},
},
})
tenantConfig: Omit<TenantConfig, 'id'>,
): Promise<TenantConfig> {
tenantConfig: Omit<TenantMgmtConfig, 'id'>,
): Promise<TenantMgmtConfig> {
return this.tenantConfigRepository.create(tenantConfig);
}
@authorize({
Expand All @@ -80,7 +80,7 @@ export class TenantConfigController {
},
})
async count(
@param.where(TenantConfig) where?: Where<TenantConfig>,
@param.where(TenantMgmtConfig) where?: Where<TenantMgmtConfig>,
): Promise<Count> {
return this.tenantConfigRepository.count(where);
}
Expand All @@ -99,16 +99,18 @@ export class TenantConfigController {
[CONTENT_TYPE.JSON]: {
schema: {
type: 'array',
items: getModelSchemaRef(TenantConfig, {includeRelations: true}),
items: getModelSchemaRef(TenantMgmtConfig, {
includeRelations: true,
}),
},
},
},
},
},
})
async find(
@param.filter(TenantConfig) filter?: Filter<TenantConfig>,
): Promise<TenantConfig[]> {
@param.filter(TenantMgmtConfig) filter?: Filter<TenantMgmtConfig>,
): Promise<TenantMgmtConfig[]> {
return this.tenantConfigRepository.find(filter);
}
@authorize({
Expand All @@ -124,7 +126,7 @@ export class TenantConfigController {
description: 'Tenant Config PATCH success',
content: {
[CONTENT_TYPE.JSON]: {
schema: getModelSchemaRef(TenantConfig),
schema: getModelSchemaRef(TenantMgmtConfig),
},
},
},
Expand All @@ -134,12 +136,12 @@ export class TenantConfigController {
@requestBody({
content: {
'application/json': {
schema: getModelSchemaRef(TenantConfig, {partial: true}),
schema: getModelSchemaRef(TenantMgmtConfig, {partial: true}),
},
},
})
tenantConfig: TenantConfig,
@param.where(TenantConfig) where?: Where<TenantConfig>,
tenantConfig: TenantMgmtConfig,
@param.where(TenantMgmtConfig) where?: Where<TenantMgmtConfig>,
): Promise<Count> {
return this.tenantConfigRepository.updateAll(tenantConfig, where);
}
Expand All @@ -155,16 +157,16 @@ export class TenantConfigController {
[STATUS_CODE.OK]: {
description: 'Tenant Config model instance',
content: {
[CONTENT_TYPE.JSON]: {schema: getModelSchemaRef(TenantConfig)},
[CONTENT_TYPE.JSON]: {schema: getModelSchemaRef(TenantMgmtConfig)},
},
},
},
})
async findById(
@param.path.string('id') id: string,
@param.filter(TenantConfig, {exclude: 'where'})
filter?: FilterExcludingWhere<TenantConfig>,
): Promise<TenantConfig> {
@param.filter(TenantMgmtConfig, {exclude: 'where'})
filter?: FilterExcludingWhere<TenantMgmtConfig>,
): Promise<TenantMgmtConfig> {
return this.tenantConfigRepository.findById(id, filter);
}
@authorize({
Expand All @@ -180,7 +182,7 @@ export class TenantConfigController {
description: 'Tenant Config PATCH success',
content: {
[CONTENT_TYPE.JSON]: {
schema: getModelSchemaRef(TenantConfig),
schema: getModelSchemaRef(TenantMgmtConfig),
},
},
},
Expand All @@ -191,11 +193,11 @@ export class TenantConfigController {
@requestBody({
content: {
'application/json': {
schema: getModelSchemaRef(TenantConfig, {partial: true}),
schema: getModelSchemaRef(TenantMgmtConfig, {partial: true}),
},
},
})
tenantConfig: TenantConfig,
tenantConfig: TenantMgmtConfig,
): Promise<void> {
await this.tenantConfigRepository.updateById(id, tenantConfig);
}
Expand All @@ -215,7 +217,7 @@ export class TenantConfigController {
})
async replaceById(
@param.path.string('id') id: string,
@requestBody() tenantConfig: TenantConfig,
@requestBody() tenantConfig: TenantMgmtConfig,
): Promise<void> {
await this.tenantConfigRepository.replaceById(id, tenantConfig);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import {
Interceptor,
InvocationContext,
Provider,
Setter,
ValueOrPromise,
inject,
} from '@loopback/core';
import {AnyObject, repository} from '@loopback/repository';
import {HttpErrors, RequestContext} from '@loopback/rest';
import {ILogger, LOGGER} from '@sourceloop/core';
import {createHmac, timingSafeEqual} from 'crypto';
import {AuthenticationBindings, IAuthUser} from 'loopback4-authentication';
import {SYSTEM_USER} from '../keys';
import {WebhookSecretRepository} from '../repositories';

const DEFAULT_TIME_TOLERANCE = 10000;

export class CallbackVerifierProvider implements Provider<Interceptor> {
constructor(
@inject(LOGGER.LOGGER_INJECT)
private readonly logger: ILogger,
@repository(WebhookSecretRepository)
private readonly webhookSecretRepo: WebhookSecretRepository,
@inject.setter(AuthenticationBindings.CURRENT_USER)
private readonly setCurrentUser: Setter<IAuthUser>,
@inject(SYSTEM_USER)
private readonly systemUser: IAuthUser,
) {}

value() {
return this.intercept.bind(this);
}

async intercept<T>(
invocationCtx: InvocationContext,
next: () => ValueOrPromise<T>,
) {
const {request} = invocationCtx.parent as RequestContext;
const value: AnyObject = request.body;
const TIMESTAMP_TOLERANCE = +DEFAULT_TIME_TOLERANCE;
const timestamp = Number(request.headers['x-timestamp']);
if (isNaN(timestamp)) {
this.logger.error('Invalid timestamp');
throw new HttpErrors.Unauthorized();
}

const signature = request.headers['x-signature'];
if (!signature || typeof signature !== 'string') {
this.logger.error('Missing signature string');
throw new HttpErrors.Unauthorized();
}

const tenantId = value.tenant?.id;
if (!tenantId) {
this.logger.error('Missing secret');
throw new HttpErrors.Unauthorized();
}

const secretInfo = await this.webhookSecretRepo.get(tenantId);
if (!secretInfo) {
this.logger.error('No secret found for this initiator');
throw new HttpErrors.Unauthorized();
}

const expectedSignature = createHmac('sha256', secretInfo.secret)
.update(`${JSON.stringify(value)}${timestamp}`)
.digest('hex');

try {
// actual signature should be equal to expected signature
// timing safe equal is used to prevent timing attacks
if (
!timingSafeEqual(Buffer.from(signature), Buffer.from(expectedSignature))
) {
this.logger.error('Invalid signature');
throw new HttpErrors.Unauthorized();
}

const hh = Math.abs(timestamp - Date.now());
// timestamp should be within 10 seconds
if (hh > TIMESTAMP_TOLERANCE) {
this.logger.error('Timestamp out of tolerance');
throw new HttpErrors.Unauthorized();
}
} catch (e) {
this.logger.error(e);
throw new HttpErrors.Unauthorized();
}

this.setCurrentUser(this.systemUser);
return next();
}
}

export type TempUser = {
userTenantId: string;
tenantType: string;
tenantId?: string;
} & IAuthUser;
Loading

0 comments on commit ff7705e

Please sign in to comment.