Skip to content

Commit

Permalink
Feat/smtp notification (#47)
Browse files Browse the repository at this point in the history
* feat: smtp notification

* chore: update dependencies
  • Loading branch information
mbystedt authored Oct 25, 2024
1 parent 5a828e1 commit bd73eff
Show file tree
Hide file tree
Showing 19 changed files with 2,424 additions and 1,560 deletions.
3,490 changes: 2,009 additions & 1,481 deletions package-lock.json

Large diffs are not rendered by default.

36 changes: 19 additions & 17 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,35 +7,37 @@
"authtool": "./bin/run"
},
"dependencies": {
"@oclif/core": "^4.0.17",
"@oclif/plugin-help": "^6.2.8",
"@oclif/plugin-plugins": "^5.4.4",
"axios": "^1.7.4",
"inversify": "^6.0.2",
"@oclif/core": "^4.0.30",
"@oclif/plugin-help": "^6.2.16",
"@oclif/plugin-plugins": "^5.4.15",
"axios": "^1.7.7",
"inversify": "^6.0.3",
"nodemailer": "^6.9.15",
"reflect-metadata": "^0.2.2",
"rxjs": "^7.8.1"
},
"devDependencies": {
"@oclif/test": "^4.0.8",
"@types/chai": "^4.3.17",
"@oclif/test": "^4.1.0",
"@types/chai": "file:@types/chai",
"@types/ejs": "^3.1.5",
"@types/eslint__js": "^8.42.3",
"@types/mocha": "^10.0.7",
"@types/node": "^22.4.1",
"@typescript-eslint/eslint-plugin": "^8.2.0",
"@typescript-eslint/parser": "^8.2.0",
"chai": "^5.1.1",
"eslint": "^9.9.0",
"@types/mocha": "^10.0.9",
"@types/node": "^22.8.0",
"@types/nodemailer": "^6.4.16",
"@typescript-eslint/eslint-plugin": "^8.11.0",
"@typescript-eslint/parser": "^8.11.0",
"chai": "^5.1.2",
"eslint": "^9.13.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-prettier": "^5.2.1",
"jest": "^29.7.0",
"mocha": "^10.7.3",
"oclif": "^4.14.22",
"oclif": "^4.15.12",
"shx": "^0.3.3",
"ts-node": "^10.9.2",
"tslib": "^2.6.3",
"typescript": "^5.5.4",
"typescript-eslint": "^8.2.0"
"tslib": "^2.8.0",
"typescript": "^5.6.3",
"typescript-eslint": "^8.11.0"
},
"engines": {
"node": ">=8.0.0"
Expand Down
28 changes: 28 additions & 0 deletions src/commands/member-sync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,19 @@ import {
brokerToken,
brokerApiUrl,
sourceBrokerIdp,
notificationOptionFrom,
notificationOptionSubject,
notificationOptionTemplateHtml,
notificationOptionTemplateText,
notificationSmtpHost,
notificationSmtpPort,
notificationSmtpSecure,
} from '../flags';
import { TYPES } from '../inversify.types';
import {
bindBroker,
bindConstants,
bindNotification,
bindTarget,
vsContainer,
} from '../inversify.config';
Expand All @@ -37,6 +45,13 @@ export default class MemberSync extends Command {
...cssTokenUrl,
...cssClientId,
...cssClientSecret,
...notificationSmtpHost,
...notificationSmtpPort,
...notificationSmtpSecure,
...notificationOptionFrom,
...notificationOptionSubject,
...notificationOptionTemplateText,
...notificationOptionTemplateHtml,
...sourceBrokerIdp,
};

Expand All @@ -48,6 +63,19 @@ export default class MemberSync extends Command {

bindConstants(flags['config-path'], flags['source-broker-idp']);
bindBroker(flags['broker-api-url'], flags['broker-token']);
bindNotification(
{
host: flags['notification-smtp-host'],
port: flags['notification-smtp-port'],
secure: flags['notification-smtp-secure'],
},
{
from: flags['notification-option-from'],
subject: flags['notification-option-subject'],
text: flags['notification-option-template-text'],
html: flags['notification-option-template-html'],
},
);
await bindTarget(
flags['css-token-url'],
flags['css-client-id'],
Expand Down
28 changes: 28 additions & 0 deletions src/commands/monitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,19 @@ import {
cssClientSecret,
cssTokenUrl,
help,
notificationOptionFrom,
notificationOptionSubject,
notificationOptionTemplateHtml,
notificationOptionTemplateText,
notificationSmtpHost,
notificationSmtpPort,
notificationSmtpSecure,
sourceBrokerIdp,
} from '../flags';
import {
bindBroker,
bindConstants,
bindNotification,
bindTarget,
vsContainer,
} from '../inversify.config';
Expand All @@ -33,6 +41,13 @@ export default class Monitor extends Command {
...cssTokenUrl,
...cssClientId,
...cssClientSecret,
...notificationSmtpHost,
...notificationSmtpPort,
...notificationSmtpSecure,
...notificationOptionFrom,
...notificationOptionSubject,
...notificationOptionTemplateText,
...notificationOptionTemplateHtml,
...sourceBrokerIdp,
};

Expand All @@ -46,6 +61,19 @@ export default class Monitor extends Command {

bindConstants(flags['config-path'], flags['source-broker-idp']);
bindBroker(flags['broker-api-url'], flags['broker-token']);
bindNotification(
{
host: notificationSmtpHost,
port: notificationSmtpPort,
secure: notificationSmtpSecure,
},
{
from: notificationOptionFrom,
subject: notificationOptionSubject,
text: notificationOptionTemplateText,
html: notificationOptionTemplateHtml,
},
);
await bindTarget(
flags['css-token-url'],
flags['css-client-id'],
Expand Down
91 changes: 60 additions & 31 deletions src/controller/auth-member-sync.controller.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { inject, injectable, multiInject } from 'inversify';
import { getLogger } from '@oclif/core';

import { TYPES } from '../inversify.types';
import { SourceService, SourceUser } from '../services/source.service';
import { IntegrationConfig, RoleConfig } from '../types';
import { IntegrationConfig, RoleConfig, UserSummary } from '../types';
import { TargetService } from '../services/target.service';
import { SmtpNotificationService } from '../notification/smtp-notification.service';
import { roleFromConfig } from '../util/role.util';

type OutletMap = Map<string, Map<string, SourceUser>>;

Expand All @@ -12,43 +15,67 @@ type OutletMap = Map<string, Map<string, SourceUser>>;
* Css sync controller
*/
export class AuthMemberSyncController {
private readonly console = getLogger('AuthMemberSyncController');
/**
* Constructor
*/
constructor(
@multiInject(TYPES.SourceService) private sourceServices: SourceService[],
@inject(TYPES.TargetService) private targetService: TargetService,
@inject(TYPES.SmtpNotificationService)
private notificationService: SmtpNotificationService,
) {}

public async sync(integrationConfigs: IntegrationConfig[]) {
const sdate = new Date();
const userMap: { [key in string]: OutletMap } = {};
for (const integrationConfig of integrationConfigs) {
const idp = integrationConfig.idp ?? 'idir';
console.log(`>>> ${integrationConfig.name} : Get users`);
this.console.info(`>>> ${integrationConfig.name} : Get users`);
userMap[integrationConfig.name] = await this.integrationMemberSync(
idp,
integrationConfig.roles,
);

for (const environment of integrationConfig.environments) {
const sEnvDate = new Date();
console.log(`>>> ${integrationConfig.name} - ${environment}: start`);
await this.syncIntegrationRoleUsers(
integrationConfig,
environment,
userMap[integrationConfig.name],
idp,
this.console.info(
`>>> ${integrationConfig.name} - ${environment}: start`,
);
// const summaryMap = await this.syncIntegrationRoleUsers(
// integrationConfig,
// environment,
// userMap[integrationConfig.name],
// idp,
// );
const summaryMap = new Map<string, UserSummary>();
summaryMap.set(
'483CFF50E3E94A22BDB082B56DE564B6',
new UserSummary({
guid: '483CFF50E3E94A22BDB082B56DE564B6',
domain: 'azureidir',
email: 'matthew.bystedt@gov.bc.ca',
name: 'Bystedt, Matthew WLRS:EX',
}),
);
summaryMap
.get('483CFF50E3E94A22BDB082B56DE564B6')
?.addRoles.push('group_vault-user');
console.log(summaryMap);

this.notificationService.notifyUsers(integrationConfig, [
...summaryMap.values(),
]);

const eEnvDate = new Date();
console.log(
this.console.info(
`>>> ${integrationConfig.name} - ${environment}: done - ${eEnvDate.getTime() - sEnvDate.getTime()} ms`,
);
}
}
const edate = new Date();

console.log(`Done - ${edate.getTime() - sdate.getTime()} ms`);
this.console.info(`Done - ${edate.getTime() - sdate.getTime()} ms`);
}

private async syncIntegrationRoleUsers(
Expand All @@ -57,8 +84,9 @@ export class AuthMemberSyncController {
userRoles: OutletMap,
idp: string,
) {
const userSummary = new Map<string, UserSummary>();
for (const [roleName, roleUserGuidMap] of userRoles.entries()) {
console.log(`${integrationConfig.id} ${environment} ${roleName}`);
this.console.info(`${integrationConfig.id} ${environment} ${roleName}`);
const existingUserGuidMap = await this.targetService.getRoleUsers(
integrationConfig.id,
environment,
Expand All @@ -74,11 +102,11 @@ export class AuthMemberSyncController {
.filter((guid) => !existingUserGuidMap.has(guid))
.map((guid) => roleUserGuidMap.get(guid))
.filter((user) => !!user);
// console.log(`remove:`);
// console.log(usersToRemove);
// console.log(`add:`);
// console.log(usersToAdd);
await Promise.all([
// this.console.info(`remove:`);
// this.console.info(usersToRemove);
// this.console.info(`add:`);
// this.console.info(usersToAdd);
const [finalizedAdd, finalizedDel] = await Promise.all([
this.targetService.alterIntegrationRoleUser(
integrationConfig,
environment,
Expand All @@ -94,12 +122,25 @@ export class AuthMemberSyncController {
usersToRemove,
),
]);
for (const finalize of finalizedAdd) {
if (!userSummary.has(finalize.guid)) {
userSummary.set(finalize.guid, new UserSummary(finalize));
}
userSummary.get(finalize.guid)?.addRoles.push(roleName);
}
for (const finalize of finalizedDel) {
if (!userSummary.has(finalize.guid)) {
userSummary.set(finalize.guid, new UserSummary(finalize));
}
userSummary.get(finalize.guid)?.delRoles.push(roleName);
}
}
return userSummary;
}

private async integrationMemberSync(idp: string, roleConfigs: RoleConfig[]) {
const roleConfigNames = roleConfigs.map((roleConfig) =>
this.roleFromConfig(roleConfig),
roleFromConfig(roleConfig),
);

const outletMap = await this.addUserToRoleWithServices(roleConfigs);
Expand Down Expand Up @@ -174,19 +215,15 @@ export class AuthMemberSyncController {
if (!outletMap.has(target)) {
continue;
}
callback(
this.roleFromConfig(roleConfig),
outletMap,
outletMap.get(target),
);
callback(roleFromConfig(roleConfig), outletMap, outletMap.get(target));
}
}
}

private async addUserToRoleWithServices(roleConfigs: RoleConfig[]) {
const outletMap = new Map<string, Map<string, SourceUser>>();
for (const roleConfig of roleConfigs) {
const role = this.roleFromConfig(roleConfig);
const role = roleFromConfig(roleConfig);
const users = await this.getUserMapFromServices(roleConfig);
if (users.size > 0) {
outletMap.set(role, users);
Expand All @@ -203,12 +240,4 @@ export class AuthMemberSyncController {
}
return userMap;
}

private roleFromConfig(roleConfig: RoleConfig) {
if (roleConfig.group) {
return `${roleConfig.group}_${roleConfig.name}`;
} else {
return roleConfig.name;
}
}
}
13 changes: 8 additions & 5 deletions src/controller/auth-monitor.controller.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
/* eslint-disable @typescript-eslint/no-empty-function */
import { inject, injectable } from 'inversify';
import { getLogger } from '@oclif/core';

import { TYPES } from '../inversify.types';
import { delay, exhaustMap, filter, interval, timer } from 'rxjs';
import { GenerateController } from './generate.contoller';
Expand All @@ -17,6 +19,7 @@ const MONITOR_CACHE_RESET_FULL_NTH = 4;
* Auth monitor controller
*/
export class AuthMonitorController {
private readonly console = getLogger('AuthMonitorController');
/**
* Constructor
*/
Expand All @@ -38,17 +41,17 @@ export class AuthMonitorController {
const resetAllCacheInterval$ = interval(
MONITOR_CACHE_RESET_INTERVAL_MS * MONITOR_CACHE_RESET_FULL_NTH,
);
console.log(`>>> Monitor - start`);
this.console.info(`>>> Monitor - start`);

// Skip every MONITOR_CACHE_RESET_FULL_NTH because it is a full reset
resetCacheInterval$
.pipe(filter((cnt) => cnt % MONITOR_CACHE_RESET_FULL_NTH === 0))
.subscribe(() => {
console.log(`---- Reset user cache`);
this.console.info(`---- Reset user cache`);
this.targetService.resetUserCache(false);
});
resetAllCacheInterval$.subscribe(() => {
console.log(`---- Reset user cache (all)`);
this.console.info(`---- Reset user cache (all)`);
this.targetService.resetUserCache(true);
});

Expand All @@ -64,9 +67,9 @@ export class AuthMonitorController {
await this.role.sync(integrationConfigs);
await this.member.sync(integrationConfigs);
}
console.log(`---- sync end [${Date.now() - startMs}]`);
this.console.info(`---- sync end [${Date.now() - startMs}]`);
} catch (e) {
console.log(`---- sync fail [Check authentication]`);
this.console.info(`---- sync fail [Check authentication]`);
}
}),
)
Expand Down
Loading

0 comments on commit bd73eff

Please sign in to comment.