Compare commits
8 Commits
main
...
feature/3-
| Author | SHA1 | Date | |
|---|---|---|---|
| b07b63add2 | |||
| f62c8a0c13 | |||
| 0cfb2edd83 | |||
| 0cef71e053 | |||
| db96953443 | |||
| a0a3a83cae | |||
| dbe79d5e1b | |||
| cf1da606d4 |
@@ -1,4 +0,0 @@
|
|||||||
import { Controller } from '@nestjs/common';
|
|
||||||
|
|
||||||
@Controller('auth-controller')
|
|
||||||
export class AuthControllerController {}
|
|
||||||
@@ -1,22 +1,24 @@
|
|||||||
|
import { CanActivate } from '@nestjs/common';
|
||||||
import { ExecutionContext, Injectable } from '@nestjs/common';
|
import { ExecutionContext, Injectable } from '@nestjs/common';
|
||||||
import { Reflector } from '@nestjs/core';
|
import { Reflector } from '@nestjs/core';
|
||||||
import { AuthGuard as PassportAuthGuard } from '@nestjs/passport';
|
import { AuthGuard as PassportAuthGuard } from '@nestjs/passport';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
import { IS_PUBLIC_KEY } from '../decorators/public.decorator';
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AuthGuard extends PassportAuthGuard('jwt') {
|
export class AuthGuard extends PassportAuthGuard('azure-ad') implements CanActivate {
|
||||||
constructor(private readonly reflector: Reflector) {
|
constructor(private readonly reflector: Reflector) {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
canActivate(context: ExecutionContext): boolean | Promise<boolean> | Observable<boolean> {
|
canActivate(context: ExecutionContext): boolean | Promise<boolean> | Observable<boolean> {
|
||||||
const isPublic = this.reflector.get<boolean>(
|
const isPublic = this.reflector.get<boolean>(
|
||||||
IS_PUBLIC_KEY,
|
'isPublic',
|
||||||
context.getHandler()
|
context.getHandler()
|
||||||
)
|
);
|
||||||
|
|
||||||
if (isPublic) return true;
|
if (isPublic) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
return super.canActivate(context);
|
return super.canActivate(context);
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
export interface AuthModuleOptions {
|
export interface AuthOptions {
|
||||||
audience: string;
|
tenantId: string;
|
||||||
issuerUrl: string;
|
clientId: string;
|
||||||
jwksUri: string;
|
clientSecret: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
import { ConfigurableModuleBuilder } from '@nestjs/common';
|
|
||||||
import { AuthModuleOptions } from './auth.interface';
|
|
||||||
|
|
||||||
export const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN } = new ConfigurableModuleBuilder<AuthModuleOptions>().build()
|
|
||||||
@@ -1,16 +1,26 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { Module, DynamicModule } from '@nestjs/common';
|
||||||
|
import { AuthOptions } from './auth.interface';
|
||||||
|
import { AUTH_OPTIONS } from './auth.constants';
|
||||||
import { PassportModule } from '@nestjs/passport';
|
import { PassportModule } from '@nestjs/passport';
|
||||||
import { AuthStrategy } from './auth.strategy';
|
import { AzureAdStrategy } from './auth.strategy';
|
||||||
import { ConfigurableModuleClass } from './auth.module-definition';
|
|
||||||
|
|
||||||
@Module({
|
@Module({})
|
||||||
|
export class AuthModule {
|
||||||
|
static register(options: AuthOptions): DynamicModule {
|
||||||
|
return {
|
||||||
|
module: AuthModule,
|
||||||
imports: [
|
imports: [
|
||||||
PassportModule.register({
|
PassportModule.register({
|
||||||
defaultStrategy: 'jwt'
|
defaultStrategy: 'azure-ad'
|
||||||
})
|
})
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
AuthStrategy
|
{
|
||||||
|
provide: AUTH_OPTIONS,
|
||||||
|
useValue: options
|
||||||
|
},
|
||||||
|
AzureAdStrategy
|
||||||
]
|
]
|
||||||
})
|
};
|
||||||
export class AuthModule extends ConfigurableModuleClass {}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,29 +1,25 @@
|
|||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from "@nestjs/common";
|
||||||
import { PassportStrategy } from '@nestjs/passport';
|
import { PassportStrategy } from "@nestjs/passport";
|
||||||
import { ExtractJwt, Strategy } from 'passport-jwt';
|
import { BearerStrategy } from 'passport-azure-ad';
|
||||||
import * as jwksRsa from 'jwks-rsa';
|
import { AuthOptions } from './auth.interface'
|
||||||
import { AuthModuleOptions } from './auth.interface';
|
import { AUTH_OPTIONS } from "./auth.constants";
|
||||||
import { MODULE_OPTIONS_TOKEN } from './auth.module-definition';
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AuthStrategy extends PassportStrategy(Strategy, 'jwt') {
|
export class AzureAdStrategy extends PassportStrategy(
|
||||||
constructor(@Inject(MODULE_OPTIONS_TOKEN) authModuleOptions: AuthModuleOptions) {
|
BearerStrategy,
|
||||||
|
'azure-ad'
|
||||||
|
) {
|
||||||
|
constructor(@Inject(AUTH_OPTIONS) authOptions: AuthOptions) {
|
||||||
super({
|
super({
|
||||||
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
|
identityMetadata: `https://login.microsoftonline.com/${authOptions.tenantId}/.well-known/openid-configuration`,
|
||||||
audience: authModuleOptions.audience,
|
clientID: authOptions.clientId,
|
||||||
issuer: authModuleOptions.issuerUrl,
|
audience: `api://${authOptions.clientId}`,
|
||||||
algorithms: ['RS256'],
|
loggingLevel: 'info',
|
||||||
ignoreExpiration: false,
|
loggingNoPII: false
|
||||||
secretOrKeyProvider: jwksRsa.passportJwtSecret({
|
})
|
||||||
cache: true,
|
|
||||||
rateLimit: true,
|
|
||||||
jwksRequestsPerMinute: 5,
|
|
||||||
jwksUri: authModuleOptions.jwksUri,
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
validate(payload: any) {
|
async validate(data: any): Promise<any> {
|
||||||
return payload;
|
return data;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
3
libs/modules/src/auth/public.decorator.ts
Normal file
3
libs/modules/src/auth/public.decorator.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
import { SetMetadata } from "@nestjs/common";
|
||||||
|
|
||||||
|
export const Public = () => SetMetadata('isPublic', true);
|
||||||
1
libs/modules/src/azAppConfig/az-app-config.constants.ts
Normal file
1
libs/modules/src/azAppConfig/az-app-config.constants.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export const APP_CONFIG_OPTIONS = 'APP_CONFIG_OPTIONS';
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
export interface MsGraphModuleOptions {
|
export interface AppConfigOptions {
|
||||||
authority: string;
|
url: string;
|
||||||
|
tenantId: string;
|
||||||
clientId: string;
|
clientId: string;
|
||||||
clientSecret: string;
|
clientSecret: string;
|
||||||
tenantId: string;
|
|
||||||
}
|
}
|
||||||
21
libs/modules/src/azAppConfig/az-app-config.module.ts
Normal file
21
libs/modules/src/azAppConfig/az-app-config.module.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
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]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
38
libs/modules/src/azAppConfig/az-app-config.service.ts
Normal file
38
libs/modules/src/azAppConfig/az-app-config.service.ts
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
1
libs/modules/src/azTable/az-table.constants.ts
Normal file
1
libs/modules/src/azTable/az-table.constants.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export const TABLE_OPTIONS = 'TABLE_OPTIONS';
|
||||||
4
libs/modules/src/azTable/az-table.interface.ts
Normal file
4
libs/modules/src/azTable/az-table.interface.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
export interface TableOptions {
|
||||||
|
accountName: string;
|
||||||
|
accountKey: string;
|
||||||
|
}
|
||||||
21
libs/modules/src/azTable/az-table.module.ts
Normal file
21
libs/modules/src/azTable/az-table.module.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
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]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
19
libs/modules/src/azTable/az-table.service.ts
Normal file
19
libs/modules/src/azTable/az-table.service.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
|
import { TableOptions } from './az-table.interface';
|
||||||
|
import { TABLE_OPTIONS } from './az-table.constants';
|
||||||
|
import { TableClient, AzureNamedKeyCredential } from '@azure/data-tables';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class TableService {
|
||||||
|
constructor(
|
||||||
|
@Inject(TABLE_OPTIONS)
|
||||||
|
private tablesOptions: TableOptions
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async getTableClient(tableName: string): Promise<TableClient> {
|
||||||
|
const credential: AzureNamedKeyCredential = new AzureNamedKeyCredential(this.tablesOptions.accountName, this.tablesOptions.accountKey);
|
||||||
|
const tableClient: TableClient = new TableClient(`https://${this.tablesOptions.accountName}.table.core.windows.net`, tableName, credential);
|
||||||
|
|
||||||
|
return tableClient;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
import { SetMetadata } from "@nestjs/common";
|
|
||||||
|
|
||||||
export const IS_PUBLIC_KEY = "isPublic";
|
|
||||||
export const Public = () => SetMetadata(IS_PUBLIC_KEY, true);
|
|
||||||
@@ -1,19 +1,22 @@
|
|||||||
|
// 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
|
// Auth
|
||||||
|
|
||||||
export * from './auth/auth.module';
|
export * from './auth/auth.module';
|
||||||
export * from './auth/auth.guard';
|
export * from './auth/auth.gaurd';
|
||||||
|
export * from './auth/public.decorator';
|
||||||
// Custom Error
|
|
||||||
|
|
||||||
export * from './util/customError';
|
|
||||||
|
|
||||||
// Decorators
|
|
||||||
|
|
||||||
export { IS_PUBLIC_KEY, Public } from './decorators/public.decorator'
|
|
||||||
|
|
||||||
// MsGraph
|
|
||||||
|
|
||||||
export * from './msgraph/msgraph.module';
|
|
||||||
export * from './msgraph/msgraph.service';
|
|
||||||
|
|
||||||
|
// MS Graph
|
||||||
|
|
||||||
|
export * from './msGraph/ms-graph.module';
|
||||||
|
export * from './msGraph/ms-graph.service';
|
||||||
|
export { Client as MsGraphClient } from '@microsoft/microsoft-graph-client';
|
||||||
|
|||||||
1
libs/modules/src/msGraph/ms-graph.constants.ts
Normal file
1
libs/modules/src/msGraph/ms-graph.constants.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export const MS_GRAPH_OPTIONS = 'MS_GRAPH_OPTIONS';
|
||||||
5
libs/modules/src/msGraph/ms-graph.interface.ts
Normal file
5
libs/modules/src/msGraph/ms-graph.interface.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
export interface MsGraphOptions {
|
||||||
|
tenantId: string;
|
||||||
|
clientId: string;
|
||||||
|
clientSecret: string;
|
||||||
|
}
|
||||||
21
libs/modules/src/msGraph/ms-graph.module.ts
Normal file
21
libs/modules/src/msGraph/ms-graph.module.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { Module, DynamicModule } from '@nestjs/common';
|
||||||
|
import { MsGraphService } from './ms-graph.service';
|
||||||
|
import { MsGraphOptions } from './ms-graph.interface';
|
||||||
|
import { MS_GRAPH_OPTIONS } from './ms-graph.constants';
|
||||||
|
|
||||||
|
@Module({})
|
||||||
|
export class MsGraphModule {
|
||||||
|
static register(options: MsGraphOptions): DynamicModule {
|
||||||
|
return {
|
||||||
|
module: MsGraphModule,
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
provide: MS_GRAPH_OPTIONS,
|
||||||
|
useValue: options
|
||||||
|
},
|
||||||
|
MsGraphService
|
||||||
|
],
|
||||||
|
exports: [MsGraphService]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
41
libs/modules/src/msGraph/ms-graph.service.ts
Normal file
41
libs/modules/src/msGraph/ms-graph.service.ts
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
|
import { MsGraphOptions } from './ms-graph.interface';
|
||||||
|
import { MS_GRAPH_OPTIONS } from './ms-graph.constants';
|
||||||
|
import { Client } from '@microsoft/microsoft-graph-client';
|
||||||
|
import { AuthenticationResult, ConfidentialClientApplication, OnBehalfOfRequest } from '@azure/msal-node';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class MsGraphService {
|
||||||
|
constructor(@Inject(MS_GRAPH_OPTIONS) private msGraphOptions: MsGraphOptions) {}
|
||||||
|
|
||||||
|
async getMsGraphAuth(accessToken: string, scopes: string[]): Promise<string> {
|
||||||
|
try {
|
||||||
|
const oboRequest: OnBehalfOfRequest = {
|
||||||
|
oboAssertion: accessToken,
|
||||||
|
scopes: scopes
|
||||||
|
}
|
||||||
|
const cca = new ConfidentialClientApplication({
|
||||||
|
auth: {
|
||||||
|
clientId: this.msGraphOptions.clientId,
|
||||||
|
clientSecret: this.msGraphOptions.clientSecret,
|
||||||
|
authority: `https://login.microsoftonline.com/${this.msGraphOptions.tenantId}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const authenticationResult: AuthenticationResult = await cca.acquireTokenOnBehalfOf(oboRequest);
|
||||||
|
|
||||||
|
return authenticationResult.accessToken
|
||||||
|
} catch (error) {
|
||||||
|
return error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getMsGraphClientDelegated(accessToken): Promise<Client> {
|
||||||
|
const client = await Client.init({
|
||||||
|
authProvider: (done) => {
|
||||||
|
done(null, accessToken);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return client;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1 +0,0 @@
|
|||||||
export const MSGRAPH_MODULE_OPTIONS = 'MSGRAPH_MODULE_OPTIONS'
|
|
||||||
@@ -1,76 +0,0 @@
|
|||||||
import {
|
|
||||||
Controller,
|
|
||||||
Get,
|
|
||||||
Headers,
|
|
||||||
Query,
|
|
||||||
StreamableFile,
|
|
||||||
UseGuards,
|
|
||||||
} from '@nestjs/common';
|
|
||||||
import { MsGraphService } from './msgraph.service';
|
|
||||||
import { Client as MsGraphClient } from '@microsoft/microsoft-graph-client';
|
|
||||||
import { AuthGuard } from '../auth/auth.guard';
|
|
||||||
|
|
||||||
@Controller('msgraph')
|
|
||||||
export class MsGraphController {
|
|
||||||
constructor(
|
|
||||||
private readonly msGraphService: MsGraphService
|
|
||||||
) {}
|
|
||||||
|
|
||||||
@Get('photo')
|
|
||||||
@UseGuards(AuthGuard)
|
|
||||||
async getProfilePhoto(@Headers() headers: any): Promise<StreamableFile> {
|
|
||||||
try {
|
|
||||||
const graphToken: string = await this.msGraphService.getMsGraphAuth(
|
|
||||||
headers.authorization.replace('Bearer ', ''),
|
|
||||||
['user.read']
|
|
||||||
);
|
|
||||||
const client: MsGraphClient =
|
|
||||||
await this.msGraphService.getMsGraphClientDelegated(graphToken);
|
|
||||||
const blob: Blob = await client.api(`me/photos('48x48')/$value`).get();
|
|
||||||
const arrayBuffer: ArrayBuffer = await blob.arrayBuffer();
|
|
||||||
const buffer: Buffer = Buffer.from(arrayBuffer);
|
|
||||||
|
|
||||||
return new StreamableFile(buffer, {
|
|
||||||
type: 'application/json',
|
|
||||||
disposition: `attachment; filename="user_photo.png"`
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
return error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Get('profile')
|
|
||||||
@UseGuards(AuthGuard)
|
|
||||||
async getUserProfile(@Headers() headers: any): Promise<any> {
|
|
||||||
try {
|
|
||||||
const graphToken: string = await this.msGraphService.getMsGraphAuth(
|
|
||||||
headers.authorization.replace('Bearer ', ''),
|
|
||||||
['user.read']
|
|
||||||
);
|
|
||||||
const client: MsGraphClient =
|
|
||||||
await this.msGraphService.getMsGraphClientDelegated(graphToken);
|
|
||||||
const userProfile = await client.api(`me`).get();
|
|
||||||
|
|
||||||
return userProfile;
|
|
||||||
} catch (error) {
|
|
||||||
return error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Get('search')
|
|
||||||
@UseGuards(AuthGuard)
|
|
||||||
async searchUsers(
|
|
||||||
@Headers() headers: any,
|
|
||||||
@Query('search') search: any
|
|
||||||
): Promise<any> {
|
|
||||||
try {
|
|
||||||
const accessToken: string = headers.authorization.replace('Bearer ', '');
|
|
||||||
const personSearchResults: any[] =
|
|
||||||
await this.msGraphService.searchUsers(accessToken, search);
|
|
||||||
|
|
||||||
return personSearchResults;
|
|
||||||
} catch (error) {
|
|
||||||
return error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
import { ConfigurableModuleBuilder } from '@nestjs/common';
|
|
||||||
import { MsGraphModuleOptions } from './msgraph.interface';
|
|
||||||
|
|
||||||
export const { ConfigurableModuleClass, ASYNC_OPTIONS_TYPE, MODULE_OPTIONS_TOKEN, OPTIONS_TYPE } = new ConfigurableModuleBuilder<MsGraphModuleOptions>().build()
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
import { Module } from '@nestjs/common';
|
|
||||||
import { MsGraphService } from './msgraph.service';
|
|
||||||
import { ConfigurableModuleClass } from './msgraph.module-definition';
|
|
||||||
import { MsGraphController } from './msgraph.controller';
|
|
||||||
|
|
||||||
@Module({
|
|
||||||
controllers: [MsGraphController],
|
|
||||||
providers: [MsGraphService],
|
|
||||||
exports: [MsGraphService]
|
|
||||||
})
|
|
||||||
export class MsGraphModule extends ConfigurableModuleClass {}
|
|
||||||
@@ -1,96 +0,0 @@
|
|||||||
import { Inject, Injectable, StreamableFile } from '@nestjs/common';
|
|
||||||
import { MsGraphModuleOptions } from './msgraph.interface';
|
|
||||||
import { Client } from '@microsoft/microsoft-graph-client';
|
|
||||||
import { AuthenticationResult, ConfidentialClientApplication, OnBehalfOfRequest } from '@azure/msal-node';
|
|
||||||
import { MODULE_OPTIONS_TOKEN } from './msgraph.module-definition';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class MsGraphService {
|
|
||||||
constructor(@Inject(MODULE_OPTIONS_TOKEN) private msGraphModuleOptions: MsGraphModuleOptions) {}
|
|
||||||
|
|
||||||
async getMsGraphAuth(accessToken: string, scopes: string[]): Promise<string> {
|
|
||||||
try {
|
|
||||||
const oboRequest: OnBehalfOfRequest = {
|
|
||||||
oboAssertion: accessToken,
|
|
||||||
scopes: scopes
|
|
||||||
}
|
|
||||||
const cca = new ConfidentialClientApplication({
|
|
||||||
auth: {
|
|
||||||
clientId: this.msGraphModuleOptions.clientId,
|
|
||||||
clientSecret: this.msGraphModuleOptions.clientSecret,
|
|
||||||
authority: this.msGraphModuleOptions.authority
|
|
||||||
}
|
|
||||||
});
|
|
||||||
const authenticationResult: AuthenticationResult = await cca.acquireTokenOnBehalfOf(oboRequest);
|
|
||||||
|
|
||||||
return authenticationResult.accessToken
|
|
||||||
} catch (error) {
|
|
||||||
return error
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async getMsGraphClientDelegated(accessToken): Promise<Client> {
|
|
||||||
const client = await Client.init({
|
|
||||||
authProvider: (done) => {
|
|
||||||
done(null, accessToken);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return client;
|
|
||||||
}
|
|
||||||
|
|
||||||
async getProfilePhoto(accessToken: string, scopes: string[]): Promise<Buffer> {
|
|
||||||
try {
|
|
||||||
const graphToken: string = await this.getMsGraphAuth(accessToken, scopes);
|
|
||||||
const client: Client = await this.getMsGraphClientDelegated(graphToken);
|
|
||||||
const blob: Blob = await client.api(`me/photos('48x48)/$value`).get();
|
|
||||||
const arrayBuffer: ArrayBuffer = await blob.arrayBuffer();
|
|
||||||
const buffer: Buffer = Buffer.from(arrayBuffer);
|
|
||||||
|
|
||||||
return buffer;
|
|
||||||
} catch (error) {
|
|
||||||
return error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async getUserProfile(accessToken): Promise<any> {
|
|
||||||
try {
|
|
||||||
const graphToken: string = await this.getMsGraphAuth(accessToken, ['user.read']);
|
|
||||||
const client: Client = await this.getMsGraphClientDelegated(graphToken);
|
|
||||||
const userProfile = await client.api(`me`).get();
|
|
||||||
|
|
||||||
return userProfile
|
|
||||||
} catch (error) {
|
|
||||||
return error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async searchUsers(accessToken, searchText: string): Promise<any> {
|
|
||||||
try {
|
|
||||||
const graphToken: string = await this.getMsGraphAuth(accessToken, ['user.read.all']);
|
|
||||||
const client: Client = await this.getMsGraphClientDelegated(graphToken);
|
|
||||||
const results: any = await client
|
|
||||||
.api('users')
|
|
||||||
.header('ConsistencyLevel', 'eventual')
|
|
||||||
.search(`"displayName:${searchText}"`)
|
|
||||||
.orderby('displayName')
|
|
||||||
.select(['displayName', 'userPrincipalName'])
|
|
||||||
.get();
|
|
||||||
let personResults: any[];
|
|
||||||
|
|
||||||
if (results.value) {
|
|
||||||
personResults = results.value.filter((result: any) => {
|
|
||||||
if (result.userPrincipalName !== null) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
personResults = []
|
|
||||||
}
|
|
||||||
|
|
||||||
return personResults;
|
|
||||||
} catch (error) {
|
|
||||||
throw new Error(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
export class CustomError extends Error {
|
|
||||||
statusCode: number;
|
|
||||||
|
|
||||||
constructor(message, name, statusCode) {
|
|
||||||
super(message);
|
|
||||||
this.name = name;
|
|
||||||
this.statusCode = statusCode;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -5,5 +5,5 @@
|
|||||||
"outDir": "../../dist/libs/modules"
|
"outDir": "../../dist/libs/modules"
|
||||||
},
|
},
|
||||||
"include": ["src/**/*"],
|
"include": ["src/**/*"],
|
||||||
"exclude": ["node_modules", "src/**/*.spec.ts"]
|
"exclude": ["node_modules", "dist", "test", "**/*spec.ts"]
|
||||||
}
|
}
|
||||||
|
|||||||
3177
package-lock.json
generated
3177
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
28
package.json
28
package.json
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@noahspan/noahspan-modules",
|
"name": "@noahspan/noahspan-modules",
|
||||||
"version": "1.2.11",
|
"version": "0.3.9",
|
||||||
"description": "",
|
"description": "",
|
||||||
"author": "",
|
"author": "",
|
||||||
"license": "UNLICENSED",
|
"license": "UNLICENSED",
|
||||||
@@ -24,32 +24,28 @@
|
|||||||
"test:e2e": "jest --config ./test/jest-e2e.json"
|
"test:e2e": "jest --config ./test/jest-e2e.json"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@azure/app-configuration": "^1.6.0",
|
||||||
|
"@azure/data-tables": "^13.2.2",
|
||||||
"@azure/identity": "^4.2.0",
|
"@azure/identity": "^4.2.0",
|
||||||
"@azure/msal-node": "^2.9.2",
|
"@azure/msal-node": "^2.9.2",
|
||||||
"@microsoft/microsoft-graph-client": "^3.0.7",
|
"@microsoft/microsoft-graph-client": "^3.0.7",
|
||||||
"@nestjs/common": "^11.0.11",
|
"@nestjs/common": "^10.0.0",
|
||||||
"@nestjs/core": "^11.0.11",
|
"@nestjs/core": "^10.0.0",
|
||||||
"@nestjs/jwt": "^11.0.0",
|
"@nestjs/passport": "^10.0.3",
|
||||||
"@nestjs/mapped-types": "*",
|
"@nestjs/platform-express": "^10.0.0",
|
||||||
"@nestjs/passport": "^11.0.5",
|
|
||||||
"@nestjs/platform-express": "^11.0.11",
|
|
||||||
"axios": "^1.7.2",
|
"axios": "^1.7.2",
|
||||||
"jwks-rsa": "^3.2.0",
|
"passport-azure-ad": "^4.3.5",
|
||||||
"passport": "^0.7.0",
|
"reflect-metadata": "^0.2.0",
|
||||||
"passport-jwt": "^4.0.1",
|
|
||||||
"passport-openidconnect": "^0.1.2",
|
|
||||||
"reflect-metadata": "^0.2.2",
|
|
||||||
"rxjs": "^7.8.1"
|
"rxjs": "^7.8.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@microsoft/microsoft-graph-types": "^2.40.0",
|
"@microsoft/microsoft-graph-types": "^2.40.0",
|
||||||
"@nestjs/cli": "^11.0.5",
|
"@nestjs/cli": "^10.0.0",
|
||||||
"@nestjs/schematics": "^11.0.2",
|
"@nestjs/schematics": "^10.0.0",
|
||||||
"@nestjs/testing": "^11.0.11",
|
"@nestjs/testing": "^10.0.0",
|
||||||
"@types/express": "^4.17.17",
|
"@types/express": "^4.17.17",
|
||||||
"@types/jest": "^29.5.2",
|
"@types/jest": "^29.5.2",
|
||||||
"@types/node": "^20.3.1",
|
"@types/node": "^20.3.1",
|
||||||
"@types/passport-openidconnect": "^0.1.3",
|
|
||||||
"@types/supertest": "^6.0.0",
|
"@types/supertest": "^6.0.0",
|
||||||
"@typescript-eslint/eslint-plugin": "^6.0.0",
|
"@typescript-eslint/eslint-plugin": "^6.0.0",
|
||||||
"@typescript-eslint/parser": "^6.0.0",
|
"@typescript-eslint/parser": "^6.0.0",
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
{
|
{
|
||||||
"extends": "./tsconfig.json",
|
"extends": "./tsconfig.json",
|
||||||
"exclude": ["node_modules", "**/*.spec.ts"]
|
"exclude": ["node_modules", "test", "dist", "**/*spec.ts"]
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user