switching user module to msgraph module (#15)

This commit was merged in pull request #15.
This commit is contained in:
2025-11-16 09:28:06 -06:00
committed by GitHub
parent aa09a2685c
commit c66cfb3c9e
20 changed files with 194 additions and 107 deletions

View File

@@ -0,0 +1,4 @@
import { Controller } from '@nestjs/common';
@Controller('auth-controller')
export class AuthControllerController {}

View File

@@ -1,11 +1,12 @@
import { ExecutionContext, Injectable } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { AuthGuard as PassportAuthGuard } from '@nestjs/passport';
import { Observable } from 'rxjs';
import { IS_PUBLIC_KEY } from '../decorators/public.decorator';
@Injectable()
export class AuthGuard extends PassportAuthGuard('oidc') {
constructor() {
export class AuthGuard extends PassportAuthGuard('jwt') {
constructor(private readonly reflector: Reflector) {
super();
}

View File

@@ -1,10 +1,5 @@
export interface AuthModuleOptions {
authorizationUrl: string;
issuer: string;
callbackUrl: string;
clientId: string;
clientSecret: string;
scope: string;
tokenUrl: string;
userInfoUrl: string;
audience: string;
issuerUrl: string;
jwksUri: string;
}

View File

@@ -1,16 +1,16 @@
import { Module } from '@nestjs/common';
import { PassportModule } from '@nestjs/passport';
import { OidcStrategy } from './oidc.strategy';
import { AuthStrategy } from './auth.strategy';
import { ConfigurableModuleClass } from './auth.module-definition';
@Module({
imports: [
PassportModule.register({
defaultStrategy: 'oidc'
defaultStrategy: 'jwt'
})
],
providers: [
OidcStrategy
AuthStrategy
]
})
export class AuthModule extends ConfigurableModuleClass {}

View File

@@ -0,0 +1,29 @@
import { Inject, Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';
import * as jwksRsa from 'jwks-rsa';
import { AuthModuleOptions } from './auth.interface';
import { MODULE_OPTIONS_TOKEN } from './auth.module-definition';
@Injectable()
export class AuthStrategy extends PassportStrategy(Strategy, 'jwt') {
constructor(@Inject(MODULE_OPTIONS_TOKEN) authModuleOptions: AuthModuleOptions) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
audience: authModuleOptions.audience,
issuer: authModuleOptions.issuerUrl,
algorithms: ['RS256'],
ignoreExpiration: false,
secretOrKeyProvider: jwksRsa.passportJwtSecret({
cache: true,
rateLimit: true,
jwksRequestsPerMinute: 5,
jwksUri: authModuleOptions.jwksUri,
}),
});
}
validate(payload: any) {
return payload;
}
}

View File

@@ -1,31 +0,0 @@
import { PassportStrategy } from "@nestjs/passport";
import { Strategy, Profile } from 'passport-openidconnect';
import { MODULE_OPTIONS_TOKEN } from "./auth.module-definition";
import { AuthModuleOptions } from "./auth.interface";
import { Inject, Injectable } from "@nestjs/common";
@Injectable()
export class OidcStrategy extends PassportStrategy(Strategy, 'oidc') {
constructor(@Inject(MODULE_OPTIONS_TOKEN) authModuleOptions: AuthModuleOptions) {
super({
authorizationURL: authModuleOptions.authorizationUrl,
callbackURL: authModuleOptions.callbackUrl,
clientID: authModuleOptions.clientId,
clientSecret: authModuleOptions.clientSecret,
issuer: authModuleOptions.issuer,
scope: authModuleOptions.scope,
tokenURL: authModuleOptions.tokenUrl,
userInfoURL: authModuleOptions.userInfoUrl
})
}
async validate(profile: Profile, done: Function): Promise<any> {
const user = {
id: profile.id,
email: profile.emails,
name: profile.displayName
}
done(null, user)
}
}

View File

@@ -1,4 +0,0 @@
import { SetMetadata } from "@nestjs/common";
export const IS_PUBLIC_KEY = 'isPublic'
export const Public = () => SetMetadata(IS_PUBLIC_KEY, true);

View File

@@ -1,8 +1,7 @@
// Auth
export * from './auth/auth.module';
export * from './auth/auth.gaurd';
export * from './auth/public.decorator';
export * from './auth/auth.guard';
// Custom Error
@@ -12,7 +11,9 @@ export * from './util/customError';
export { IS_PUBLIC_KEY, Public } from './decorators/public.decorator'
// User
// MsGraph
export * from './msgraph/msgraph.module';
export * from './msgraph/msgraph.service';
export * from './user/user.module';
export * from './user/user.service';

View File

@@ -0,0 +1 @@
export const MSGRAPH_MODULE_OPTIONS = 'MSGRAPH_MODULE_OPTIONS'

View File

@@ -6,26 +6,26 @@ import {
StreamableFile,
UseGuards,
} from '@nestjs/common';
import { UserService } from './user.service';
import { MsGraphService } from './msgraph.service';
import { Client as MsGraphClient } from '@microsoft/microsoft-graph-client';
import { AuthGuard } from '../auth/auth.gaurd';
import { AuthGuard } from '../auth/auth.guard';
@Controller('user')
export class UserController {
@Controller('msgraph')
export class MsGraphController {
constructor(
private readonly userService: UserService
private readonly msGraphService: MsGraphService
) {}
@Get('photo')
@UseGuards(AuthGuard)
async getProfilePhoto(@Headers() headers: any): Promise<StreamableFile> {
try {
const graphToken: string = await this.userService.getMsGraphAuth(
const graphToken: string = await this.msGraphService.getMsGraphAuth(
headers.authorization.replace('Bearer ', ''),
['user.read']
);
const client: MsGraphClient =
await this.userService.getMsGraphClientDelegated(graphToken);
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);
@@ -43,12 +43,12 @@ export class UserController {
@UseGuards(AuthGuard)
async getUserProfile(@Headers() headers: any): Promise<any> {
try {
const graphToken: string = await this.userService.getMsGraphAuth(
const graphToken: string = await this.msGraphService.getMsGraphAuth(
headers.authorization.replace('Bearer ', ''),
['user.read']
);
const client: MsGraphClient =
await this.userService.getMsGraphClientDelegated(graphToken);
await this.msGraphService.getMsGraphClientDelegated(graphToken);
const userProfile = await client.api(`me`).get();
return userProfile;
@@ -66,7 +66,7 @@ export class UserController {
try {
const accessToken: string = headers.authorization.replace('Bearer ', '');
const personSearchResults: any[] =
await this.userService.searchUsers(accessToken, search);
await this.msGraphService.searchUsers(accessToken, search);
return personSearchResults;
} catch (error) {

View File

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

View File

@@ -1,4 +1,4 @@
import { ConfigurableModuleBuilder } from '@nestjs/common';
import { UserModuleOptions } from './user.interface';
import { MsGraphModuleOptions } from './msgraph.interface';
export const { ConfigurableModuleClass, ASYNC_OPTIONS_TYPE, MODULE_OPTIONS_TOKEN, OPTIONS_TYPE } = new ConfigurableModuleBuilder<UserModuleOptions>().build()
export const { ConfigurableModuleClass, ASYNC_OPTIONS_TYPE, MODULE_OPTIONS_TOKEN, OPTIONS_TYPE } = new ConfigurableModuleBuilder<MsGraphModuleOptions>().build()

View File

@@ -0,0 +1,11 @@
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 {}

View File

@@ -1,12 +1,12 @@
import { Inject, Injectable, StreamableFile } from '@nestjs/common';
import { UserModuleOptions } from './user.interface';
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 './user.module-definition';
import { MODULE_OPTIONS_TOKEN } from './msgraph.module-definition';
@Injectable()
export class UserService {
constructor(@Inject(MODULE_OPTIONS_TOKEN) private userModuleOptions: UserModuleOptions) {}
export class MsGraphService {
constructor(@Inject(MODULE_OPTIONS_TOKEN) private msGraphModuleOptions: MsGraphModuleOptions) {}
async getMsGraphAuth(accessToken: string, scopes: string[]): Promise<string> {
try {
@@ -16,9 +16,9 @@ export class UserService {
}
const cca = new ConfidentialClientApplication({
auth: {
clientId: this.userModuleOptions.clientId,
clientSecret: this.userModuleOptions.clientSecret,
authority: `https://login.microsoftonline.com/${this.userModuleOptions.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

@@ -1 +0,0 @@
export const USER_MODULE_OPTIONS = 'USER_MODULE_OPTIONS'

View File

@@ -1,11 +0,0 @@
import { Module } from '@nestjs/common';
import { UserService } from './user.service';
import { ConfigurableModuleClass } from './user.module-definition';
import { UserController } from './user.controller';
@Module({
controllers: [UserController],
providers: [UserService],
exports: [UserService]
})
export class UserModule extends ConfigurableModuleClass {}

View File

@@ -5,5 +5,5 @@
"outDir": "../../dist/libs/modules"
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist", "test", "**/*spec.ts"]
"exclude": ["node_modules", "src/**/*.spec.ts"]
}