updating dynamic module to configurable module builder

This commit is contained in:
2025-01-30 12:46:33 -06:00
parent 41a1b9f807
commit 533d5d166b
24 changed files with 497 additions and 243 deletions

View File

@@ -1,4 +1,4 @@
export interface AuthOptions {
export interface AuthModuleOptions {
tenantId: string;
clientId: string;
clientSecret: string;

View File

@@ -0,0 +1,4 @@
import { ConfigurableModuleBuilder } from '@nestjs/common';
import { AuthModuleOptions } from './auth.interface';
export const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN } = new ConfigurableModuleBuilder<AuthModuleOptions>().build()

View File

@@ -1,26 +1,14 @@
import { Module, DynamicModule } from '@nestjs/common';
import { AuthOptions } from './auth.interface';
import { AUTH_OPTIONS } from './auth.constants';
import { Module } from '@nestjs/common';
import { PassportModule } from '@nestjs/passport';
import { AzureAdStrategy } from './auth.strategy';
import { ConfigurableModuleClass } from './auth.module-definition';
@Module({})
export class AuthModule {
static register(options: AuthOptions): DynamicModule {
return {
module: AuthModule,
imports: [
PassportModule.register({
defaultStrategy: 'azure-ad'
})
],
providers: [
{
provide: AUTH_OPTIONS,
useValue: options
},
AzureAdStrategy
]
};
}
}
@Module({
imports: [
PassportModule.register({
defaultStrategy: 'azure-ad'
})
],
providers: [AzureAdStrategy]
})
export class AuthModule extends ConfigurableModuleClass {}

View File

@@ -1,19 +1,20 @@
import { Inject, Injectable } from "@nestjs/common";
import { PassportStrategy } from "@nestjs/passport";
import { BearerStrategy } from 'passport-azure-ad';
import { AuthOptions } from './auth.interface'
import { AUTH_OPTIONS } from "./auth.constants";
import { AuthModuleOptions } from './auth.interface'
import { MODULE_OPTIONS_TOKEN } from "./auth.module-definition";
@Injectable()
export class AzureAdStrategy extends PassportStrategy(
BearerStrategy,
'azure-ad'
) {
constructor(@Inject(AUTH_OPTIONS) authOptions: AuthOptions) {
constructor(@Inject(MODULE_OPTIONS_TOKEN) authModuleOptions: AuthModuleOptions) {
console.log(authModuleOptions)
super({
identityMetadata: `https://login.microsoftonline.com/${authOptions.tenantId}/.well-known/openid-configuration`,
clientID: authOptions.clientId,
audience: `api://${authOptions.clientId}`,
identityMetadata: `https://login.microsoftonline.com/${authModuleOptions.tenantId}/.well-known/openid-configuration`,
clientID: authModuleOptions.clientId,
audience: `api://${authModuleOptions.clientId}`,
loggingLevel: 'info',
loggingNoPII: false
})

View File

@@ -1 +0,0 @@
export const APP_CONFIG_OPTIONS = 'APP_CONFIG_OPTIONS';

View File

@@ -1,6 +0,0 @@
export interface AppConfigOptions {
url: string;
tenantId: string;
clientId: string;
clientSecret: string;
}

View File

@@ -1,21 +0,0 @@
import { Module, DynamicModule } from '@nestjs/common';
import { AppConfigService } from './az-app-config.service';
import { AppConfigOptions } from './az-app-config.interface';
import { APP_CONFIG_OPTIONS } from './az-app-config.constants';
@Module({})
export class AppConfigModule {
static register(options: AppConfigOptions): DynamicModule {
return {
module: AppConfigModule,
providers: [
{
provide: APP_CONFIG_OPTIONS,
useValue: options
},
AppConfigService
],
exports: [AppConfigService]
};
}
}

View File

@@ -1,38 +0,0 @@
import { Inject, Injectable } from '@nestjs/common';
import { AppConfigOptions } from './az-app-config.interface';
import { APP_CONFIG_OPTIONS } from './az-app-config.constants';
import axios, { AxiosResponse } from 'axios';
import { AppConfigurationClient, ConfigurationSetting, FeatureFlagValue, GetConfigurationSettingResponse, featureFlagPrefix, isFeatureFlag, parseFeatureFlag } from '@azure/app-configuration';
import { ClientSecretCredential } from '@azure/identity';
@Injectable()
export class AppConfigService {
constructor(
@Inject(APP_CONFIG_OPTIONS)
private appConfigOptions: AppConfigOptions
) { }
async getFeatureFlags(keys: string[], label: string): Promise<{ key: string, enabled: boolean }[]> {
const credential: ClientSecretCredential = new ClientSecretCredential(this.appConfigOptions.tenantId, this.appConfigOptions.clientId, this.appConfigOptions.clientSecret);
const client: AppConfigurationClient = new AppConfigurationClient(this.appConfigOptions.url, credential);
const featureFlags: { key: string, enabled: boolean }[] = await Promise.all(
keys.map(async (key: string) => {
const configSetting: GetConfigurationSettingResponse = await client.getConfigurationSetting({
key: `${featureFlagPrefix}${key}`,
label: label
});
if (isFeatureFlag(configSetting)) {
const parsedFeatureFlag: ConfigurationSetting<FeatureFlagValue> = parseFeatureFlag(configSetting);
return {
key: parsedFeatureFlag.value.id,
enabled: parsedFeatureFlag.value.enabled
}
}
})
);
return featureFlags;
}
}

View File

@@ -1 +0,0 @@
export const TABLE_OPTIONS = 'TABLE_OPTIONS';

View File

@@ -1,6 +0,0 @@
export interface TableOptions {
accountName: string;
accountKey: string;
accountUrl: string;
allowInsecureConnection: boolean;
}

View File

@@ -1,21 +0,0 @@
import { Module, DynamicModule } from '@nestjs/common';
import { TableService } from './az-table.service';
import { TableOptions } from './az-table.interface';
import { TABLE_OPTIONS } from './az-table.constants';
@Module({})
export class TableModule {
static register(options: TableOptions): DynamicModule {
return {
module: TableModule,
providers: [
{
provide: TABLE_OPTIONS,
useValue: options
},
TableService
],
exports: [TableService]
}
}
}

View File

@@ -1,47 +0,0 @@
import { Inject, Injectable } from '@nestjs/common';
import { TableOptions } from './az-table.interface';
import { TABLE_OPTIONS } from './az-table.constants';
import { TableClient, AzureNamedKeyCredential, TableServiceClient, TableServiceClientOptions } from '@azure/data-tables';
@Injectable()
export class TableService {
constructor(
@Inject(TABLE_OPTIONS)
private tablesOptions: TableOptions
) {}
async checkTableExists(tableName: string, tableServiceClient: TableServiceClient): Promise<boolean> {
const tables = tableServiceClient.listTables();
let tableExists: boolean = false;
for await (const table of tables) {
if (table.name === tableName) {
tableExists = true;
break;
}
}
return tableExists;
}
async getTableClient(tableName: string): Promise<TableClient> {
try {
const credential: AzureNamedKeyCredential = new AzureNamedKeyCredential(this.tablesOptions.accountName, this.tablesOptions.accountKey);
const options: TableServiceClientOptions = { allowInsecureConnection: this.tablesOptions.allowInsecureConnection };
const tableServiceClient: TableServiceClient = new TableServiceClient(this.tablesOptions.accountUrl, credential, options);
const tableExists: boolean = await this.checkTableExists(tableName, tableServiceClient);
if (!tableExists) {
await tableServiceClient.createTable(tableName);
}
const tableClient: TableClient = new TableClient(this.tablesOptions.accountUrl, tableName, credential, options);
return tableClient;
} catch (error) {
console.log(error);
return error;
}
}
}

View File

@@ -0,0 +1,28 @@
// import { Logger } from '@nestjs/common';
// import { LoggerService } from '@dapr/dapr';
// export class DaprLogger implements LoggerService {
// private readonly logger = new Logger(DaprLogger.name);
// constructor() {}
// error(message: any, ...optionalParams: any[]): void {
// this.logger.error(message, ...optionalParams);
// }
// warn(message: any, ...optionalParams: any[]): void {
// this.logger.warn(message, ...optionalParams);
// }
// info(message: any, ...optionalParams: any[]): void {
// this.logger.log(message, ...optionalParams);
// }
// verbose(message: any, ...optionalParams: any[]): void {
// this.logger.verbose(message, ...optionalParams);
// }
// debug(message: any, ...optionalParams: any[]): void {
// this.logger.debug(message, ...optionalParams);
// }
// }

View File

@@ -0,0 +1,8 @@
import { Module } from "@nestjs/common";
import { DaprService } from "./dapr.service";
@Module({
providers: [ DaprService ],
exports: [ DaprService ]
})
export class DaprModule {}

View File

@@ -0,0 +1,13 @@
import { Injectable, Logger } from '@nestjs/common';
import { DaprClient } from '@dapr/dapr';
@Injectable()
export class DaprService {
daprClient: DaprClient;
private readonly logger = new Logger(DaprService.name);
constructor() {
this.logger.log('Initializing DaprClient')
this.daprClient = new DaprClient();
}
}

View File

@@ -1,22 +1,20 @@
// Azure App Configuration
export * from './azAppConfig/az-app-config.module';
export * from './azAppConfig/az-app-config.service';
// Azure Data Tables
export * from './azTable/az-table.module';
export * from './azTable/az-table.service';
export { TableClient } from '@azure/data-tables';
// Auth
export * from './auth/auth.module';
export * from './auth/auth.gaurd';
export * from './auth/public.decorator';
// Dapr
export * from './dapr/dapr.module'
export * from './dapr/dapr.service'
// MS Graph
export * from './msGraph/ms-graph.module';
export * from './msGraph/ms-graph.service';
export { Client as MsGraphClient } from '@microsoft/microsoft-graph-client';
// Custom Error
export * from './util/customError';

View File

@@ -1 +0,0 @@
export const MS_GRAPH_OPTIONS = 'MS_GRAPH_OPTIONS';

View File

@@ -1,4 +1,4 @@
export interface MsGraphOptions {
export interface MsGraphModuleOptions {
tenantId: string;
clientId: string;
clientSecret: string;

View File

@@ -0,0 +1,4 @@
import { ConfigurableModuleBuilder } from '@nestjs/common';
import { MsGraphModuleOptions } from './ms-graph.interface';
export const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN } = new ConfigurableModuleBuilder<MsGraphModuleOptions>().build()

View File

@@ -1,21 +1,9 @@
import { Module, DynamicModule } from '@nestjs/common';
import { Module } from '@nestjs/common';
import { MsGraphService } from './ms-graph.service';
import { MsGraphOptions } from './ms-graph.interface';
import { MS_GRAPH_OPTIONS } from './ms-graph.constants';
import { ConfigurableModuleClass } from './ms-graph.module-definition';
@Module({})
export class MsGraphModule {
static register(options: MsGraphOptions): DynamicModule {
return {
module: MsGraphModule,
providers: [
{
provide: MS_GRAPH_OPTIONS,
useValue: options
},
MsGraphService
],
exports: [MsGraphService]
}
}
}
@Module({
providers: [MsGraphService],
exports: [MsGraphService]
})
export class MsGraphModule extends ConfigurableModuleClass {}

View File

@@ -1,12 +1,12 @@
import { Inject, Injectable } from '@nestjs/common';
import { MsGraphOptions } from './ms-graph.interface';
import { MS_GRAPH_OPTIONS } from './ms-graph.constants';
import { MsGraphModuleOptions } from './ms-graph.interface';
import { Client } from '@microsoft/microsoft-graph-client';
import { AuthenticationResult, ConfidentialClientApplication, OnBehalfOfRequest } from '@azure/msal-node';
import { MODULE_OPTIONS_TOKEN } from './ms-graph.module-definition';
@Injectable()
export class MsGraphService {
constructor(@Inject(MS_GRAPH_OPTIONS) private msGraphOptions: MsGraphOptions) {}
constructor(@Inject(MODULE_OPTIONS_TOKEN) private msGraphModuleOptions: MsGraphModuleOptions) {}
async getMsGraphAuth(accessToken: string, scopes: string[]): Promise<string> {
try {
@@ -16,9 +16,9 @@ export class MsGraphService {
}
const cca = new ConfidentialClientApplication({
auth: {
clientId: this.msGraphOptions.clientId,
clientSecret: this.msGraphOptions.clientSecret,
authority: `https://login.microsoftonline.com/${this.msGraphOptions.tenantId}`
clientId: this.msGraphModuleOptions.clientId,
clientSecret: this.msGraphModuleOptions.clientSecret,
authority: `https://login.microsoftonline.com/${this.msGraphModuleOptions.tenantId}`
}
});
const authenticationResult: AuthenticationResult = await cca.acquireTokenOnBehalfOf(oboRequest);

View File

@@ -0,0 +1,9 @@
export class CustomError extends Error {
statusCode: number;
constructor(message, name, statusCode) {
super(message);
this.name = name;
this.statusCode = statusCode;
}
}