What is the best way for approaching dynamic Auth Guards? - nestjs

I'm trying to package my own AuthGuard for use in other projects and need to pass it a string before use.
Because I saw the Passport auth guard use a function that wrapped around a new class I've done the same...
export const AnchorAuthGuard = (rpc?: string): Type<CanActivate> => {
class AuthGuard implements CanActivate {
rpc = rpc || "https://eos.greymass.com";
async canActivate(context: ExecutionContext): Promise<boolean> {
const [req] = context.getArgs();
const { body } = req as { body: ProofPayload };
const proof = IdentityProof.from(body.proof);
const client = new APIClient({
provider: new AxiosAPIProvider(this.rpc),
});
const accountName = proof.signer.actor;
const account = await client.v1.chain.get_account(accountName);
const auth = account.getPermission(proof.signer.permission).required_auth;
const valid = proof.verify(auth, account.head_block_time);
if (valid) {
req.anchor = {
account: {
actor: proof.signer.actor.toString(),
permission: proof.signer.permission.toString(),
},
object: account.toJSON(),
};
return true;
} else {
return false;
}
}
}
return AuthGuard;
};
However, now that I've packaged this up and extending the Guard with extends for some more functionality in a projhect I'm consuming the library in I'm having trouble figuring out how to enter the rpc parameter via configService from the ConfigModule and now feel like I'm not using the best practices here and that there's a better way from the start.
Any ideas?

I am not sure if I understood you correctly but to modify AuthGuard you must extend AuthGuard class and write over canActivate method.
#Injectable()
export class LoginGuard extends AuthGuard('jwt') {
constructor(private reflector: Reflector, private config: ConfigService) {
super();
}
canActivate(context: ExecutionContext) {
return super.canActivate(context); // this is necessary due to possibly returning `boolean | Promise<boolean> | Observable<boolean>
}
}

Related

NestJS v9: implement durable providers

[SOLVED] I'm pretty new to NestJS and trying to get my head around durable providers but i can't get them to work.
My scenario is that i have a service with some logic and two providers that implement the same interface to get some data. Depending on a custom header value i want to use Provider1 or Provider2 and the service itself does not have to know about the existing provider implementations.
Since i'm in a request scoped scenario but i know there are only 2 possible dependency-subtrees i want to use durable providers that the dependencies are not newly initialised for each request but reused instead.
I set up the ContextIdStrategy as described in the official docs and it is executed on each request but i miss the part how to connect my provider implementations with the ContextSubtreeIds created in the ContextIdStrategy.
Interface:
export abstract class ITest {
abstract getData(): string;
}
Implementations:
export class Test1Provider implements ITest {
getData() {
return "TEST1";
}
}
export class Test2Provider implements ITest {
getData() {
return "TEST2";
}
}
Service:
#Injectable()
export class AppService {
constructor(private readonly testProvider: ITest) {}
getHello(): string {
return this.testProvider.getData();
}
}
Controller:
#Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
#Get()
getData(): string {
return this.appService.getData();
}
}
ContextIdStrategy:
const providers = new Map<string, ContextId>([
["provider1", ContextIdFactory.create()],
["provider2", ContextIdFactory.create()],
]);
export class AggregateByProviderContextIdStrategy implements ContextIdStrategy {
attach(contextId: ContextId, request: Request) {
const providerId = request.headers["x-provider-id"] as string;
let providerSubTreeId: ContextId;
if (providerId == "provider1") {
providerSubTreeId = providers["provider1"];
} else if (providerId == "provider2") {
providerSubTreeId = providers["provider2"];
} else {
throw Error(`x-provider-id ${providerId} not supported`);
}
// If tree is not durable, return the original "contextId" object
return (info: HostComponentInfo) =>
info.isTreeDurable ? providerSubTreeId : contextId;
}
}
Main:
async function bootstrap() {
const app = await NestFactory.create(AppModule);
ContextIdFactory.apply(new AggregateByProviderContextIdStrategy());
await app.listen(3000);
}
bootstrap();
Module:
#Module({
imports: [],
controllers: [AppController],
providers: [
{
provide: ITest,
useFactory: () => {
// THIS IS THE MISSING PIECE.
// Return either Test1Provider or Test2Provider based on the ContextSubtreeId
// which is created by the ContextIdStrategy
return new Test1Provider();
},
},
AppService,
],
})
export class AppModule {}
The missing part was a modification of the ContextIdStrategy return statement:
return {
resolve: (info: HostComponentInfo) => {
const context = info.isTreeDurable ? providerSubTreeId : contextId;
return context;
},
payload: { providerId },
}
after that change, the request object can be injected in the module and where it will only contain the providerId property and based on that, the useFactory statement can return different implementations

NestJS with Prisma Transactions

I'm trying to use a Prisma transaction in a NestJS project and I can't figure out a clean way to accomplish the following:
Have a service that will call other services and have all of them bound to a transaction. Eg:
#Injectable()
export class OrdersService {
constructor(private prismaService: PrismaService, ...) {}
async someFn() {
return await this.prismaService.$transaction(async (prismaServiceBoundToTransaction): Promise<any> => {
await this.userService.update() // This will perform an update using prismaService internally
await this.otherService.delete() // Again, it'll use prismaService
}
}
}
In this case, both user and other services will use their own prisma service and won't be bound to the Transaction.
Is there a way to accomplish this without passing the prismaServiceBoundToTx to each method?
The main problem I had when finding a suitable solution was, that the prisma client in the lambda of an interactive transaction is not a fully fledged Client, but just Prisma.TransactionClient, which is missing $on, $connect, $disconnect, $use and the $transaction methods.
If prisma would provide a full Client at this place, all you could do to solve the problem was just doing transactions like this:
**THIS DOES NOT WORK BECAUSE prismaServiceBoundToTransaction IS JUST OF TYPE Prisma.TransactionClient!!!**
return await this.prismaService.$transaction(async (prismaServiceBoundToTransaction): Promise<any> => {
const userService = new UserService(prismaServiceBoundToTransaction)
const otherService = new OtherService(prismaServiceBoundToTransaction)
//Following calls will use prismaServiceBoundToTransaction internally
await userService.update()
await otherService.delete()
}
Of course above only works, if UserService and OtherService are stateless.
So for my solution I created a new Interface that will offer all methods of Prisma.TransactionClient, but also a custom method to create a transaction.
All of the services like your UserService will only retrieve this exact interface, so they can't call $transaction but only my interactiveTransaction method!
export interface PrismaClientWithCustomTransaction
extends Readonly<Prisma.TransactionClient> {
interactiveTransaction<F>(
fn: (prisma: Prisma.TransactionClient) => Promise<F>,
options?: {
maxWait?: number | undefined;
timeout?: number | undefined;
isolationLevel?: Prisma.TransactionIsolationLevel | undefined;
}
): Promise<F>;
}
We then create a concrete class TransactionalPrismaClient that implements the mentioned interface and delivers it, by retrieving a Prisma.TransactionClient in it's constructor and forwarding all of its methods. Additionally we also just implement the interactiveTransaction method by executing the lambda method with the Prisma.TransactionClient
export class TransactionalPrismaClient<
T extends Prisma.PrismaClientOptions = Prisma.PrismaClientOptions,
U = 'log' extends keyof T
? T['log'] extends Array<Prisma.LogLevel | Prisma.LogDefinition>
? Prisma.GetEvents<T['log']>
: never
: never,
GlobalReject extends
| Prisma.RejectOnNotFound
| Prisma.RejectPerOperation
| false
| undefined = 'rejectOnNotFound' extends keyof T
? T['rejectOnNotFound']
: false
> implements PrismaClientWithCustomTransaction
{
constructor(private readonly transactionalClient: Prisma.TransactionClient) {}
$executeRaw<T = unknown>(
query: TemplateStringsArray | Prisma.Sql,
...values: any[]
): PrismaPromise<number> {
return this.transactionalClient.$executeRaw(query, ...values);
}
$executeRawUnsafe<T = unknown>(
query: string,
...values: any[]
): PrismaPromise<number> {
return this.transactionalClient.$executeRawUnsafe(query, ...values);
}
$queryRaw<T = unknown>(
query: TemplateStringsArray | Prisma.Sql,
...values: any[]
): PrismaPromise<T> {
return this.transactionalClient.$queryRaw(query, ...values);
}
$queryRawUnsafe<T = unknown>(
query: string,
...values: any[]
): PrismaPromise<T> {
return this.transactionalClient.$queryRawUnsafe(query, ...values);
}
get otherEntity(): Prisma.OtherEntityDelegate<GlobalReject> {
return this.transactionalClient.otherEntity;
}
get userEntity(): Prisma.UserEntityDelegate<GlobalReject> {
return this.transactionalClient.userEntity;
}
async interactiveTransaction<F>(
fn: (prisma: Prisma.TransactionClient) => Promise<F>,
options?: {
maxWait?: number | undefined;
timeout?: number | undefined;
isolationLevel?: Prisma.TransactionIsolationLevel | undefined;
}
): Promise<F> {
return await fn(this.transactionalClient);
}
}
And in your PrismaService we also need to implement the interactiveTransaction method, so that it satifies our defined interface PrismaClientWithCustomTransaction.
#Injectable()
export class PrismaService
extends PrismaClient
implements OnModuleInit, PrismaClientWithCustomTransaction
{
private readonly logger = new ConsoleLogger(PrismaService.name);
async onModuleInit() {
this.logger.log('Trying to connect to db.');
await this.$connect();
}
async enableShutdownHooks(app: INestApplication) {
this.$on('beforeExit', async () => {
await app.close();
});
}
interactiveTransaction<R>(
fn: (prisma: Prisma.TransactionClient) => Promise<R>,
options?: {
maxWait?: number | undefined;
timeout?: number | undefined;
isolationLevel?: Prisma.TransactionIsolationLevel | undefined;
},
numRetries = 1
): Promise<R> {
let result: Promise<R> | null = null;
for (let i = 0; i < numRetries; i++) {
try {
result = this.$transaction(fn, options);
} catch (e) {
if (e instanceof Prisma.PrismaClientKnownRequestError) {
//TODO?
} else {
throw e;
}
}
if (result != null) {
return result;
}
}
throw new Error(
'No result in transaction after maximum number of retries.'
);
}
}
Because in our services we now expect the PrismaClientWithCustomTransaction interface, the auto injecting of NestJs wont work anymore and we have to provide PrismaService using a token:
providers: [
{
provide: 'PRISMA_SERVICE_TOKEN',
useClass: PrismaService,
},
],
exportt class UserService{
constructor(#Inject('PRISMA_SERVICE_TOKEN') private readonly prisma: PrismaClientWithCustomTransaction){}
}
Alright so now we can do the following:
#Injectable()
export class OrdersService {
constructor( #Inject('PRISMA_SERVICE_TOKEN')
private readonly prisma: PrismaClientWithCustomTransaction, ...) {}
async someFn() {
return await this.prisma.interactiveTransaction(
async (client) => {
//You can still use client directly, if you dont need nested transaction logic
return client.userEntity.create(...)
//Or create services for nested usage
const transactionalClient = new TransactionalPrismaClient(client);
const userService = new UserService(transactionalClient);
return userService.createUser(...);
});
},
{ isolationLevel: Prisma.TransactionIsolationLevel.RepeatableRead }
);
}
}
And if you need the $on, $connect, $disconnect, $use, you can of course still inject the original PrismaService with its regular interface.

How to create common class for third-party API requests in NestJS

I am creating NestJS application where I am making third-party API requests. For that I have to write the same thing inside every function in order to get the data.
To make things non-repeating, how can I write on common class that has API request based on GET or POST request and send the response so that I can use that class in every function.
Below is my code:
subscribe.service.ts
#Injectable()
export class SubscribeService {
constructor(#InjectModel('Subscribe') private readonly model:Model<Subscribe>,
#Inject(CACHE_MANAGER) private cacheManager:Cache,
private httpService: HttpService){}
async addSubscriber(subscriberDto:SubscribeDto){
const url = 'https://track.cxipl.com/api/v2/phone-tracking/subscribe';
const headersRequest = {
'content-Type': 'application/json',
'authkey': process.env.AUTHKEY
};
try{
const resp = await this.httpService.post(url,subscriberDto,{ headers: headersRequest }).pipe(
map((response) => {
if(response.data.success == true){
const data = new this.model(subscriberDto);
// return data.save();
const saved = data.save();
if(saved){
const msgSuccess = {
"success":response.data.success,
"status":response.data.data.status
}
return msgSuccess;
}
}
else{
const msgFail = {"success":response.data.success}
return msgFail;
}
}),
);
return resp;
}
catch(err){
return err;
}
}
async getLocation(phoneNumber:PhoneNumber){
try{
const location = await this.cacheManager.get<Coordinates>(phoneNumber.phoneNumber);
if(location){
return location;
}
else{
const resp = await axios.post('https://track.cxipl.com/api/v2/phone-tracking/location',phoneNumber,{headers:{
'content-Type': 'application/json',
'authkey': process.env.AUTHKEY
}});
const msg:Coordinates = {
"location":resp.data.data.location,
"timestamp":resp.data.data.timestamp
}
await this.cacheManager.set<Coordinates>(phoneNumber.phoneNumber,msg, { ttl: 3600 });
return msg;
}
}
catch(err){
console.log(err);
return err;
}
}
}
As in above code in both function addSubscriber() and getLocation() I need to hit the API repeatedly and add request headers again and again is there any way so that I can create one separate class for request and response and utilize in my service.
How can I achieve desired the result?
To create a common class for making third-party API requests in NestJS, you can follow these steps:
Create a new file in your NestJS project to store the common class.
For example, you could create a file called api.service.ts in the
src/common directory.
In the file, create a new class called ApiService that will be responsible for making the API requests. This class should have a
constructor that injects the necessary dependencies, such as the
HttpService provided by NestJS.
import { HttpService, Injectable } from '#nestjs/common';
#Injectable()
export class ApiService {
constructor(private readonly httpService: HttpService) {}
}
Add methods to the ApiService class for each type of API request you want to make. For example, you might have a get() method for making GET requests, a post() method for making POST requests, and so on. Each method should accept the necessary parameters for making the request (such as the URL and any query parameters or request body), and use the HttpService to make the request.
import { HttpService, Injectable } from '#nestjs/common';
#Injectable()
export class ApiService {
constructor(private readonly httpService: HttpService) {}
async get(url: string, params?: object): Promise<any> {
return this.httpService.get(url, { params }).toPromise();
}
async post(url: string, body: object): Promise<any> {
return this.httpService.post(url, body).toPromise();
}
}
Inject the ApiService wherever you need to make API requests. For example, you might inject it into a service or a controller, and use the methods of the ApiService to make the actual API requests.
import { Injectable } from '#nestjs/common';
import { ApiService } from './api.service';
#Injectable()
export class SomeService {
constructor(private readonly apiService: ApiService) {}
async getData(): Promise<any> {
return this.apiService.get('https://some-api.com/endpoint');
}
}
This is just one way you could create a common class for making third-party API requests in NestJS. You can customize the ApiService class to meet the specific needs of your application

nestjs return undefined in Public Guards

I follow this link https://docs.nestjs.com/security/authentication#enable-authentication-globally
I went ahead and create a public guard
But it does not recognize my requests when I put #Public on top of any method
and returns undefined !
I want to detect usser is logged on public routes
JwtAuthGuard
export class JwtAuthGuard extends AuthGuard('jwt') {
constructor(private reflector: Reflector) {
super();
}
canActivate(context: ExecutionContext) {
const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [
context.getHandler(),
context.getClass(),
]);
if (isPublic) {
return true;
}
return super.canActivate(context);
}
}
Public Decorator
export const IS_PUBLIC_KEY = 'isPublic';
export const Public = () => SetMetadata(IS_PUBLIC_KEY, true);
Sample controller
#Public()
#Get()
async find(#LoginUser() user: User): Promise<any> {
console.log(user);
// the user is undefined even after login
}
You set the route to be public and have a clause in your guard that says "if the route is public return true before attempting a login". If you want to login regardless of if the route is public or not, and then return ture at all times you can do something like
export class JwtAuthGuard extends AuthGuard('jwt') {
constructor(private reflector: Reflector) {
super();
}
canActivate(context: ExecutionContext) {
const canAct = await (super.canActivate(context) as Promise<boolean>)
const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [
context.getHandler(),
context.getClass(),
]);
return isPublic ?? canAct;
}
}
This will now have passport attempt a login from the get-go. So long as you don't throw an error in the valdiate method, or anything the validate method calls, everything should proceed as Passport does the login, check if the route is public, and return true if so, return if the login was successful if the route is not public.
write this:
constructor(#Inject(Reflector) private reflector: Reflector){
super();
}

How to accomplish HTTP Basic Authentication with NestJS

I am wondering how one can accomplish HTTP Basic Authentication with common NestJS Auth practices.
For example if I use an AuthGuard like this, I get the error
(node:336) UnhandledPromiseRejectionWarning: Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
import { CanActivate, ExecutionContext, Injectable } from '#nestjs/common';
import { InjectRepository } from '#nestjs/typeorm';
import { compareSync } from 'bcrypt';
import { User } from 'src/user/user.entity';
import { Repository } from 'typeorm';
#Injectable()
export class AuthGuard implements CanActivate {
constructor(
#InjectRepository(User) private readonly userRepository: Repository<User>,
) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const request = context.switchToHttp().getRequest();
const b64auth = (request.headers.authorization || '').split(' ')[1] || '';
const [username, password] = Buffer.from(b64auth, 'base64')
.toString()
.split(':');
const user = await this.userRepository.findOne({
where: { username },
});
if (user && compareSync(password, user.password) !== false) {
request.user = user;
return true;
}
const response = context.switchToHttp().getResponse();
response.set('WWW-Authenticate', 'Basic realm="Authentication required."'); // change this
response.status(401).send();
return false;
}
}
I suspect, that returning false (and letting Nest handle the reponse) doesn't play with "manually" setting the response status code to 401 and sending the response.
How can I protect certain routes with this ancient http authorization mechanism?
I would suggest implementing an ExceptionFilter that listens for Exception thrown by the guard specifically (UnauthorizedException). Then, in the filter, set the response as you would like to, that way the guard doesn't try to send multiple responses and you can set up the response as you'd like.

Resources