"res.setHeader is not a function" error Google Auth Strategy in NestJS GraphQL - graphql

I've tried to implement an oauth method using GraphQL with Google auth and for some reason I'm getting the following error
"res.setHeader is not a function" from within the authenticate method in Google Strategy
I've used passport-google-oauth20 strategy
this is my google-auth.guard.ts
import { ExecutionContext, Injectable } from '#nestjs/common';
import { GqlExecutionContext } from '#nestjs/graphql';
import { AuthGuard } from '#nestjs/passport';
#Injectable()
export class GoogleAuthGuard extends AuthGuard('google') {
getRequest(context: ExecutionContext) {
const ctx = GqlExecutionContext.create(context);
const gqlReq = ctx.getContext().req;
if (gqlReq) {
const { token } = ctx.getArgs();
gqlReq.body = { token };
return gqlReq;
}
return context.switchToHttp().getRequest();
}
}
this is my google.strategy.ts
import { PassportStrategy } from '#nestjs/passport';
import { Strategy, VerifyCallback } from 'passport-google-oauth20';
import { Injectable, UnauthorizedException } from '#nestjs/common';
import { Profile } from 'passport';
#Injectable()
export class GoogleStrategy extends PassportStrategy(Strategy, 'google') {
constructor() {
super({
clientID: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_SECRET,
callbackURL: process.env.GOOGLE_REDIRECT_URL,
prompt: 'consent',
scope: ['email', 'profile'],
})
}
async validate(
accessToken: string,
refreshToken: string,
profile: Profile,
done: VerifyCallback,
): Promise<any> {
if (!profile) {
return done(new UnauthorizedException(), false);
}
return done(null, profile);
}
}
it's important to point out that since my app is a react SPA, the callbackURL value is the main page of the client and not another path in the server.
and the resolver which I intend to use to generate a jwt token and a refresh token, but the code never gets to this part due to the error in the strategy
#UseGuards(GoogleAuthGuard)
#Query(() => LoginResponseApi)
async googleLogin(
#Args({ name: 'token', type: () => String }) token: string,
#Req() req,
#Context() context
): Promise<LoginResponseApi> {
const res: Response = context.req.res;
const loginResponse: any = await this.authService.googleLogin(req)
const jwtToken = this.authService.createRefreshToken(loginResponse.user)
if (loginResponse.accessToken)
this.authService.sendRefreshToken(res, jwtToken)
return loginResponse;
}

Related

NestJS/GraphQL/Passport - getting unauthorised error from guard

I'm trying to follow along with this tutorial and I'm struggling to convert the implementation to GraphQL.
local.strategy.ts
#Injectable()
export class LocalStrategy extends PassportStrategy(Strategy) {
constructor(private readonly authenticationService: AuthenticationService) {
super();
}
async validate(email: string, password: string): Promise<any> {
const user = await this.authenticationService.getAuthenticatedUser(
email,
password,
);
if (!user) throw new UnauthorizedException();
return user;
}
}
local.guard.ts
#Injectable()
export class LogInWithCredentialsGuard extends AuthGuard('local') {
async canActivate(context: ExecutionContext): Promise<boolean> {
const ctx = GqlExecutionContext.create(context);
const { req } = ctx.getContext();
req.body = ctx.getArgs();
await super.canActivate(new ExecutionContextHost([req]));
await super.logIn(req);
return true;
}
}
authentication.type.ts
#InputType()
export class AuthenticationInput {
#Field()
email: string;
#Field()
password: string;
}
authentication.resolver.ts
#UseGuards(LogInWithCredentialsGuard)
#Mutation(() => User, { nullable: true })
logIn(
#Args('variables')
_authenticationInput: AuthenticationInput,
#Context() req: any,
) {
return req.user;
}
mutation
mutation {
logIn(variables: {
email: "email#email.com",
password: "123123"
} ) {
id
email
}
}
Even the above credentials are correct, I'm receiving an unauthorized error.
The problem is in your LogInWithCredentialsGuard.
You shouldn't override canAcitavte method, all you have to do is update the request with proper GraphQL args because in case of API request, Passport automatically gets your credentials from req.body. With GraphQL, execution context is different, so you have to manually set your args in req.body. For that, getRequest method is used.
As the execution context of GraphQL and REST APIs is not same, you have to make sure your guard works in both cases whether it's controller or mutation.
here is a working code snippet
#Injectable()
export class LogInWithCredentialsGuard extends AuthGuard('local') {
// Override this method so it can be used in graphql
getRequest(context: ExecutionContext) {
const ctx = GqlExecutionContext.create(context);
const gqlReq = ctx.getContext().req;
if (gqlReq) {
const { variables } = ctx.getArgs();
gqlReq.body = variables;
return gqlReq;
}
return context.switchToHttp().getRequest();
}
}
and your mutation will be like
#UseGuards(LogInWithCredentialsGuard)
#Mutation(() => User, { nullable: true })
logIn(
#Args('variables')
_authenticationInput: AuthenticationInput,
#Context() context: any, // <----------- it's not request
) {
return context.req.user;
}
I've been able to get a successful login with a guard like this:
#Injectable()
export class LocalGqlAuthGuard extends AuthGuard('local') {
constructor() {
super();
}
getRequest(context: ExecutionContext) {
const ctx = GqlExecutionContext.create(context);
const req = ctx.getContext().req;
req.body = ctx.getArgs();
return req;
}
async canActivate(context: ExecutionContext) {
await super.canActivate(context);
const ctx = GqlExecutionContext.create(context);
const req = ctx.getContext().req;
await super.logIn(req);
return true;
}
}

Angular, error 500 after sending the request in the header

I have a hard time passing the right angular request to the header. This is my service:
import { Injectable } from '#angular/core';
import { HttpClient, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest, HttpHeaders }
from '#angular/common/http';
import { Utente } from '../model/Utente ';
import { Prodotto } from '../model/Prodotto ';
import { OktaAuthService } from '#okta/okta-angular';
import { Observable, from } from 'rxjs';
import { Carrello } from '../model/Carrello ';
import { userInfo } from 'node:os';
import { getLocaleCurrencyCode } from '#angular/common';
const headers = new HttpHeaders().set('Accept', 'application/json');
#Injectable({
providedIn: 'root'
})
export class HttpClientService {
constructor(
private httpClient:HttpClient, private oktaAuth:OktaAuthService ) {}
getCarr(){
return this.httpClient.get<Carrello[]>('http://localhost:8080/prodotti/utente/vedicarrelloo', {headers} );
}
}
This is my spring method:
#Transactional(readOnly = true)
public List<Carrello> getCarrello(#AuthenticationPrincipal OidcUser utente){
Utente u= utenteRepository.findByEmail(utente.getEmail());
return carrelloRepository.findByUtente(u);
}
In console I get this error (error 500):
https://i.stack.imgur.com/BiONS.png
this error corresponds in my console to "java.lang.NullPointerException: null.
But if I access localhost: 8080, I can see the answer correctly, so I assume there is a problem in passing the request header in angular, can anyone tell me where am I wrong, please? I specify that I get this error only in the methods where the OidcUser is present, the rest works perfectly. Thank you!
You need to send an access token with your request. Like this:
import { Component, OnInit } from '#angular/core';
import { OktaAuthService } from '#okta/okta-angular';
import { HttpClient } from '#angular/common/http';
import sampleConfig from '../app.config';
interface Message {
date: string;
text: string;
}
#Component({
selector: 'app-messages',
templateUrl: './messages.component.html',
styleUrls: ['./messages.component.css']
})
export class MessagesComponent implements OnInit {
failed: Boolean;
messages: Array<Message> [];
constructor(public oktaAuth: OktaAuthService, private http: HttpClient) {
this.messages = [];
}
async ngOnInit() {
const accessToken = await this.oktaAuth.getAccessToken();
this.http.get(sampleConfig.resourceServer.messagesUrl, {
headers: {
Authorization: 'Bearer ' + accessToken,
}
}).subscribe((data: any) => {
let index = 1;
const messages = data.messages.map((message) => {
const date = new Date(message.date);
const day = date.toLocaleDateString();
const time = date.toLocaleTimeString();
return {
date: `${day} ${time}`,
text: message.text,
index: index++
};
});
[].push.apply(this.messages, messages);
}, (err) => {
console.error(err);
this.failed = true;
});
}
}
On the Spring side, if you want it to accept a JWT, you'll need to change to use Jwt instead of OidcUser. Example here.
#GetMapping("/")
public String index(#AuthenticationPrincipal Jwt jwt) {
return String.format("Hello, %s!", jwt.getSubject());
}

TypeGraphql - #inputtype on typeorm

Hello I need to check if there is an email in the database already:
with this:
return User.findOne({ where: { email } }).then((user) => {
if (user) return false;
return true;
});
I have the following inputtypes:
#InputType()
export class RegisterInput {
#Field()
#IsEmail({}, { message: 'Invalid email' })
email: string;
#Field()
#Length(1, 255)
name: string;
#Field()
password: string;
}
I would like to know if there is any way for me to validate the email in the inputtype? or just in my resolve:
#Mutation(() => User)
async register(
#Arg('data')
{ email, name, password }: RegisterInput,
): Promise<User> {
const hashedPassword = await bcrypt.hash(password, 12);
const user = await User.create({
email,
name,
password: hashedPassword,
}).save();
return user;
}
Actually you can register your own decorator for class-validator
For example it can look something like this:
isEmailAlreadyExists.ts
import {
registerDecorator,
ValidationOptions,
ValidatorConstraint,
ValidatorConstraintInterface,
} from 'class-validator';
import { UserRepo } from '../../repositories/UserRepo';
import { InjectRepository } from 'typeorm-typedi-extensions';
#ValidatorConstraint({ async: true })
export class isEmailAlreadyExist
implements ValidatorConstraintInterface {
#InjectRepository()
private readonly userRepo: UserRepo;
async validate(email: string) {
const user = await this.userRepo.findOne({ where: { email } });
if (user) return false;
return true;
}
}
export function IsEmailAlreadyExist(validationOptions?: ValidationOptions) {
return function (object: Object, propertyName: string) {
registerDecorator({
target: object.constructor,
propertyName: propertyName,
options: validationOptions,
constraints: [],
validator: isEmailAlreadyExist,
});
};
}
If you're injecting dependencies than you should in inject it in class-validator too. Simply add to your main file this:
import { Container } from 'typedi';
import * as classValidator from 'class-validator';
classValidator.useContainer(Container);
...
const schema = await buildSchema({
resolvers: [...],
container: Container,
});
Then you can use decorator in your InputType
import { InputType, Field } from 'type-graphql';
import { IsEmailAlreadyExist } from '../../../utils/validators/isEmailAlreadyExist';
#InputType()
export class YourInput {
#Field()
#IsEmailAlreadyExist()
email: string;
}
I actually just figured this out myself for my own project.
You can simply add a validation on the email from RegisterInput argument and throw an error if the email already exists.
import { Repository } from 'typeorm'
import { InjectRepository } from 'typeorm-typedi-extensions'
...
// Use dependency injection in the resolver's constructor
constructor(
#InjectRepository(User) private readonly userRepository: Repository<User>
) {}
...
// Your mutation
#Mutation(() => User)
async register(
#Arg('data')
{ email, name, password }: RegisterInput,
): Promise<User> {
const hashedPassword = await bcrypt.hash(password, 12);
const userWithEmail = this.userRepository.find({ email: email })
// If a user with the email was found
if (userWithEmail) {
throw new Error('A user with that email already exists!')
}
const user = await User.create({
email,
name,
password: hashedPassword,
}).save();
return user;
}
To use the InjectRepository make sure you add a "container" to your buildSchema function:
import { Container } from 'typedi'
...
const schema = await buildSchema({
resolvers: [...],
container: Container
})
Let me know if this works out for you? Thanks!

Connect NestJS to a websocket server

How can NestJS be use as a websocket client? I want to connect to a remote websocket server as a client using NestJS, but I didn't find any information about this implementation in the framework.
As Nestjs is simply a framework for Nodejs, so you need to find an NPM package that supports Websocket. For example, I use ws with #types/ws type definition, and create a Websocket client as a Nestjs service class:
// socket-client.ts
import { Injectable } from "#nestjs/common";
import * as WebSocket from "ws";
#Injectable()
export class WSService {
// wss://echo.websocket.org is a test websocket server
private ws = new WebSocket("wss://echo.websocket.org");
constructor() {
this.ws.on("open", () => {
this.ws.send(Math.random())
});
this.ws.on("message", function(message) {
console.log(message);
});
}
send(data: any) {
this.ws.send(data);
}
onMessage(handler: Function) {
// ...
}
// ...
}
// app.module.ts
import { Module } from "#nestjs/common";
import { WSService } from "./socket-client";
#Module({
providers: [WSService]
})
export class AppModule {}
I try it by another way. I write an adapter with socket.io-client. Then use this adapter in boostrap by method useWebSocketAdapter. After that i can write handle websocket event in gateway like the way working with socket server (use decorator #SubscribeMessage)
My Adapter file
import { WebSocketAdapter, INestApplicationContext } from '#nestjs/common';
import { MessageMappingProperties } from '#nestjs/websockets'
import * as SocketIoClient from 'socket.io-client';
import { isFunction, isNil } from '#nestjs/common/utils/shared.utils';
import { fromEvent, Observable } from 'rxjs';
import { filter, first, map, mergeMap, share, takeUntil } from 'rxjs/operators';
export class IoClientAdapter implements WebSocketAdapter {
private io;
constructor(private app: INestApplicationContext) {
}
create(port: number, options?: SocketIOClient.ConnectOpts) {
const client = SocketIoClient("http://localhost:3000" , options || {})
this.io = client;
return client;
}
bindClientConnect(server: SocketIOClient.Socket, callback: Function) {
this.io.on('connect', callback);
}
bindClientDisconnect(client: SocketIOClient.Socket, callback: Function) {
console.log("it disconnect")
//client.on('disconnect', callback);
}
public bindMessageHandlers(
client: any,
handlers: MessageMappingProperties[],
transform: (data: any) => Observable<any>,
) {
const disconnect$ = fromEvent(this.io, 'disconnect').pipe(
share(),
first(),
);
handlers.forEach(({ message, callback }) => {
const source$ = fromEvent(this.io, message).pipe(
mergeMap((payload: any) => {
const { data, ack } = this.mapPayload(payload);
return transform(callback(data, ack)).pipe(
filter((response: any) => !isNil(response)),
map((response: any) => [response, ack]),
);
}),
takeUntil(disconnect$),
);
source$.subscribe(([response, ack]) => {
if (response.event) {
return client.emit(response.event, response.data);
}
isFunction(ack) && ack(response);
});
});
}
public mapPayload(payload: any): { data: any; ack?: Function } {
if (!Array.isArray(payload)) {
return { data: payload };
}
const lastElement = payload[payload.length - 1];
const isAck = isFunction(lastElement);
if (isAck) {
const size = payload.length - 1;
return {
data: size === 1 ? payload[0] : payload.slice(0, size),
ack: lastElement,
};
}
return { data: payload };
}
close(server: SocketIOClient.Socket) {
this.io.close()
}
}
main.js
import { NestFactory } from '#nestjs/core';
import { AppModule } from './app.module';
import {IoClientAdapter} from './adapters/ioclient.adapter'
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useWebSocketAdapter(new IoClientAdapter(app))
await app.listen(3006);
console.log(`Application is running on: ${await app.getUrl()}`);
}
bootstrap();
then Gateway
import {
MessageBody,
SubscribeMessage,
WebSocketGateway,
WebSocketServer,
WsResponse,
} from '#nestjs/websockets';
import { from, Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { Server } from 'socket.io';
#WebSocketGateway()
export class EventsGateway {
#WebSocketServer()
server: Server;
#SubscribeMessage('hello')
async identity(#MessageBody() data: number): Promise<number> {
console.log(data)
return data;
}
}
It a trick, but look so cool. Message handler can write more like nestjs style.

NESTJS Gateway / Websocket - how to send jwt access_token through socket.emit

I am using the default passport jwt AuthGuard for my project. That works for my post & get routes fine when setting the authentication header.
Now I want to use Nestjs Gateways as well with socket.io on the client-side, but I don't know how to send the access_token to the gateway?
That is basically my Gateway:
#WebSocketGateway()
export class UserGateway {
entityManager = getManager();
#UseGuards(AuthGuard('jwt'))
#SubscribeMessage('getUserList')
async handleMessage(client: any, payload: any) {
const results = await this.entityManager.find(UserEntity);
console.log(results);
return this.entityToClientUser(results);
}
And on the client I'm sending like this:
this.socket.emit('getUserList', users => {
console.log(users);
this.userListSub.next(users);
});
How and where do I add the jwt access_token? The documentation of nestjs misses that point completely for Websockets. All they say is, that the Guards work exactly the same for websockets as they do for post / get etc. See here
While the question is answered, I want to point out the Guard is not usable to prevent unauthorized users from establishing a connection.
It's only usable to guard specific events.
The handleConnection method of a class annotated with #WebSocketGateway is called before canActivate of your Guard.
I end up using something like this in my Gateway class:
async handleConnection(client: Socket) {
const payload = this.authService.verify(
client.handshake.headers.authorization,
);
const user = await this.usersService.findOne(payload.userId);
!user && client.disconnect();
}
For anyone looking for a solution. Here it is:
#UseGuards(WsGuard)
#SubscribeMessage('yourRoute')
async saveUser(socket: Socket, data: any) {
let auth_token = socket.handshake.headers.authorization;
// get the token itself without "Bearer"
auth_token = auth_token.split(' ')[1];
}
On the client side you add the authorization header like this:
this.socketOptions = {
transportOptions: {
polling: {
extraHeaders: {
Authorization: 'your token', // 'Bearer h93t4293t49jt34j9rferek...'
}
}
}
};
// ...
this.socket = io.connect('http://localhost:4200/', this.socketOptions);
// ...
Afterwards you have access to the token on every request serverside like in the example.
Here also the WsGuard I implemented.
#Injectable()
export class WsGuard implements CanActivate {
constructor(private userService: UserService) {
}
canActivate(
context: any,
): boolean | any | Promise<boolean | any> | Observable<boolean | any> {
const bearerToken = context.args[0].handshake.headers.authorization.split(' ')[1];
try {
const decoded = jwt.verify(bearerToken, jwtConstants.secret) as any;
return new Promise((resolve, reject) => {
return this.userService.findByUsername(decoded.username).then(user => {
if (user) {
resolve(user);
} else {
reject(false);
}
});
});
} catch (ex) {
console.log(ex);
return false;
}
}
}
I simply check if I can find a user with the username from the decoded token in my database with my user service. I am sure you could make this implementation cleaner, but it works.
Thanks! At the end i implemented a Guard that like the jwt guard puts the user inside the request. At the end I'm using the query string method from the socket client to pass the auth token This is my implementation:
import { CanActivate, ExecutionContext, Injectable, Logger } from '#nestjs/common';
import { WsException } from '#nestjs/websockets';
import { Socket } from 'socket.io';
import { AuthService } from '../auth/auth.service';
import { User } from '../auth/entity/user.entity';
#Injectable()
export class WsJwtGuard implements CanActivate {
private logger: Logger = new Logger(WsJwtGuard.name);
constructor(private authService: AuthService) { }
async canActivate(context: ExecutionContext): Promise<boolean> {
try {
const client: Socket = context.switchToWs().getClient<Socket>();
const authToken: string = client.handshake?.query?.token;
const user: User = await this.authService.verifyUser(authToken);
client.join(`house_${user?.house?.id}`);
context.switchToHttp().getRequest().user = user
return Boolean(user);
} catch (err) {
throw new WsException(err.message);
}
}
}

Resources