NestJS | Using OAuth along with Session - session

I'm using NestJS as a Service then I've got this problem. I don't usually deal with sessions and tokens.
I've been able to implement the session via passport-local, but I'm having a problem regarding Facebook and Google OAuth login it's not saving the session in the database, while the local guard does.
Below are the codes for the strategies and guards.
File: local.strategy.ts
import { Strategy } from 'passport-local';
import { PassportStrategy } from '#nestjs/passport';
import { Injectable } from '#nestjs/common';
import { AuthService } from '../auth.service';
import { VerifyUserDto } from 'src/user/dto/VerifyUser.dto';
#Injectable()
export class LocalStrategy extends PassportStrategy(Strategy) {
constructor(private authService: AuthService) {
super({
usernameField: 'UserID',
passwordField: 'UserPass',
});
}
async validate(UserID: string, UserPass: string): Promise<any> {
const ToBeVerified = new VerifyUserDto();
ToBeVerified.UserID = UserID;
ToBeVerified.UserPass = UserPass;
return await this.authService.ValidateUser(ToBeVerified);
}
}
File: google.strategy.ts
import { Strategy, VerifyCallback } from 'passport-google-oauth20';
import { Injectable } from '#nestjs/common';
import { PassportStrategy } from '#nestjs/passport';
#Injectable()
export class GoogleStrategy extends PassportStrategy(Strategy, 'google') {
constructor() {
super({
clientID: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_SECRET,
callbackURL: '/api/v1/google/redirect',
scope: ['email', 'profile'],
});
}
async validate(
accessToken: string,
refreshToken: string,
profile: any,
done: VerifyCallback,
): Promise<any> {
const data = {
accessToken,
profile,
// refreshToken,
};
done(null, data);
}
}
File: facebook.strategy.ts
import { Injectable } from '#nestjs/common';
import { PassportStrategy } from '#nestjs/passport';
import { Profile, Strategy } from 'passport-facebook';
#Injectable()
export class FacebookStrategy extends PassportStrategy(Strategy, 'facebook') {
constructor() {
super({
clientID: process.env.FACEBOOK_CLIENT_ID,
clientSecret: process.env.FACEBOOK_SECRET,
callbackURL: '/api/v1/facebook/redirect',
scope: ['email', 'profile'],
// profileFields: ['emails', 'name'],
});
}
async validate(
accessToken: string,
refreshToken: string,
profile: Profile,
done: (err: any, user: any, info?: any) => void,
): Promise<any> {
const data = {
accessToken,
profile,
};
done(null, data);
}
}
File: Local Guard
import { CanActivate, ExecutionContext, Injectable } from '#nestjs/common';
import { AuthGuard } from '#nestjs/passport';
#Injectable()
export class LocalGuard extends AuthGuard('local') {
async canActivate(context: ExecutionContext) {
const result = (await super.canActivate(context)) as boolean;
const request = context.switchToHttp().getRequest();
await super.logIn(request);
return result;
}
}
export class Authenticated implements CanActivate {
async canActivate(context: ExecutionContext) {
const request = context.switchToHttp().getRequest();
return request.isAuthenticated();
}
}
File: Google Guard
import { CanActivate, ExecutionContext, Injectable } from '#nestjs/common';
import { AuthGuard } from '#nestjs/passport';
#Injectable()
export class GoogleGuard extends AuthGuard('google') {
async canActivate(context: ExecutionContext) {
const result = (await super.canActivate(context)) as boolean;
const request = context.switchToHttp().getRequest();
await super.logIn(request);
return result;
}
}
export class Authenticated implements CanActivate {
async canActivate(context: ExecutionContext) {
const request = context.switchToHttp().getRequest();
return request.isAuthenticated();
}
}
File: Facebook Guard
import { CanActivate, ExecutionContext, Injectable } from '#nestjs/common';
import { AuthGuard } from '#nestjs/passport';
#Injectable()
export class FacebookGuard extends AuthGuard('facebook') {
async canActivate(context: ExecutionContext) {
const result = (await super.canActivate(context)) as boolean;
const request = context.switchToHttp().getRequest();
await super.logIn(request);
return result;
}
}
export class Authenticated implements CanActivate {
async canActivate(context: ExecutionContext) {
const request = context.switchToHttp().getRequest();
return request.isAuthenticated();
}
}

I will share with You my code for google autorization
import { Injectable } from '#nestjs/common';
import { ModuleRef } from '#nestjs/core';
import { PassportStrategy } from '#nestjs/passport';
import { Strategy, VerifyCallback } from 'passport-google-oauth20';
import { AuthService } from '../services/auth.service';
export interface StrategyProfile<STRATEGY extends string> {
id: string;
displayName: string;
name: {
familyName: string; givenName: string;
};
emails: { value: string; verified: boolean; }[];
photos: { value: string; }[];
provider: STRATEGY;
}
export interface UserExternalAuthorization {
providerId: string;
providerName: string;
eMail: string;
name: string;
avatar: string;
}
#Injectable()
export class GoogleStrategy extends PassportStrategy(Strategy, 'google') {
constructor(private authService: AuthService, private moduleRef: ModuleRef) {
super({
clientID: process.env.GOOGLE_CLIENT_ID || '',
clientSecret: process.env.GOOGLE_CLIENT_SECRET || '',
callbackURL: 'http://localhost:3000/auth/redirect/google',
scope: [ 'email', 'profile', ],
// scope: [ 'email', 'profile', 'https://www.googleapis.com/auth/user.birthday.read' ], // not reading the date
});
}
async validate(
accessToken: string,
refreshToken: string,
profile: StrategyProfile<'google'>,
done: VerifyCallback,
): Promise<any> {
const email = profile.emails.find((x) => x.verified);
if (!email) {
throw new Error('No verified email returned from Google Authorization!');
}
const user: UserExternalAuthorization = {
providerId: profile.id,
providerName: profile.provider,
eMail: email.value,
name: profile.displayName,
avatar: profile?.photos[0]?.value,
};
const u = await this.authService.externalUser(user);
done(null, u);
}
}
Function return await this.authService.ValidateUser(ToBeVerified); from local strategy, check against password (if its correct) and then returns user object im using for jwt (userId, loginType(google, local etc), userType(admin or not) and some other simple data - not many).
Function await this.authService.externalUser(user); from google-strategy returns same object, but also
creates user in the database (by email) if there was no such user before
do not check password (just is user was already there and was removed/banned)
So in every part of the system tokens im using have the same structure and i dont care from where user came).
As for Guard, im using one for each user (as i dont want to distinguish them on this level yet). AuthService is service for logging users and for changing password (even if you are not from local, your account is created and you would be able to log in using password unless you check different option in settings)
import { ExecutionContext, Injectable } from '#nestjs/common';
import { AuthGuard } from '#nestjs/passport';
import { Observable } from 'rxjs';
import { AuthService } from '../services/auth.service';
#Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {
constructor(
protected readonly dbService: AuthService,
) {super();}
canActivate(context: ExecutionContext): boolean | Promise<boolean> | Observable<boolean> {
const can = super.canActivate(context);
if (can instanceof Promise) {
return new Promise(async (resolve, reject) => {
can.then((c) => {
// c !== true -> log notLogged
resolve(c);
}).catch((e) => {
// logged denied, e.message
reject(e);
});
});
} else {
return can;
}
}
}
Also i made decorator for using in controllers
import { applyDecorators, UseGuards } from '#nestjs/common';
import { ApiBearerAuth, ApiUnauthorizedResponse } from '#nestjs/swagger';
import { JwtAuthGuard } from '../guards/jwt-auth.guard';
export function IsLogged() {
return applyDecorators(
UseGuards(JwtAuthGuard),
ApiBearerAuth(),
ApiUnauthorizedResponse({description: 'Unauthorized'}),
);
}
So in controllers you can have something like that
everything in controller locked for logged users
#IsLogged()
#Controller()
export class name {}
just end point locked only for logged users
#Controller()
export class name {
#IsLogged()
#Get('/')
async fName() {}
}
I wish this helps and leverage some overburden of using many AuthGuard types

Related

Login request is always 401 Unauthorized with AuthGuard

I have been following this tutorial and trying to convert it to GraphQL. I am trying to implement a NestJS + GraphQL + Passport + Express-Session solution but I am having problems with the AuthGuard. Sadly there is only little information on this available online. My AuthGuard looks like this
import { ExecutionContext, Injectable } from '#nestjs/common';
import { ExecutionContextHost } from '#nestjs/core/helpers/execution-context-host';
import { GqlExecutionContext } from '#nestjs/graphql';
import { AuthGuard } from '#nestjs/passport';
#Injectable()
export class LocalGuard extends AuthGuard('local') {
async canActivate(context: ExecutionContext): Promise<boolean> {
const ctx = GqlExecutionContext.create(context);
const { req } = ctx.getContext();
const result = (await super.canActivate(
new ExecutionContextHost([req]),
)) as boolean;
console.log(result);
await super.logIn(req);
return result;
}
}
Unfortunately, I always receive a Unauthorized response when trying to do a login request. I think the problem comes from my super.canActivate call.
Update: This is my AuthGuard Implementation that finally worked. Overwriting getRequest and adding my input to the body fixes the problem.
import { ExecutionContext, Injectable } from '#nestjs/common';
import { GqlExecutionContext } from '#nestjs/graphql';
import { AuthGuard } from '#nestjs/passport';
#Injectable()
export class LocalGuard extends AuthGuard('local') {
async canActivate(context: ExecutionContext): Promise<boolean> {
const result = (await super.canActivate(context)) as boolean;
const ctx = GqlExecutionContext.create(context);
const { req } = ctx.getContext();
await super.logIn(req);
return result;
}
getRequest(context: ExecutionContext) {
const ctx = GqlExecutionContext.create(context);
const gqlReq = ctx.getContext().req;
if (gqlReq) {
const { authCredentialsInput } = ctx.getArgs();
gqlReq.body = authCredentialsInput;
return gqlReq;
}
}
}

How to resolve GOOGLE_APPLICATION_CREDENTIALS when running app in test, Spring Boot?

I have a Spring Boot application dependent on Google PubSub. I want to run it with a Google Cloud PubSub emulator. How can I resolve GOOGLE_APPLICATION_CREDENTIALS, so the app will start and consume messages from the local emulator, not an external project?
At the moment, if I set GOOGLE_APPLICATION_CREDENTIALS to dev.json, PubSub doesn't get invoked if I don't set the variable, test crashes. How can I overcome it? I cannot put puzzles together.
NOTE: I am writing an integration test with a full Spring boot startup.
My PubSub implementation:
import com.github.dockerjava.api.exception.DockerClientException
import com.google.api.gax.core.NoCredentialsProvider
import com.google.api.gax.grpc.GrpcTransportChannel
import com.google.api.gax.rpc.FixedTransportChannelProvider
import com.google.api.gax.rpc.TransportChannelProvider
import com.google.cloud.pubsub.v1.*
import com.google.cloud.pubsub.v1.stub.GrpcSubscriberStub
import com.google.cloud.pubsub.v1.stub.SubscriberStubSettings
import com.google.protobuf.ByteString
import com.google.pubsub.v1.*
import com.greenbird.server.contracts.TestServer
import io.grpc.ManagedChannel
import io.grpc.ManagedChannelBuilder
import org.testcontainers.containers.PubSubEmulatorContainer
import org.testcontainers.utility.DockerImageName
import java.util.concurrent.TimeUnit
class PubSubTestServer(private val projectName: ProjectName, private val ports: Array<Int> = arrayOf(8085)) :
TestServer {
constructor(projectId: String): this(ProjectName.of(projectId))
private val projectId = projectName.project
var emulator: PubSubEmulatorContainer = PubSubEmulatorContainer(
DockerImageName.parse("gcr.io/google.com/cloudsdktool/cloud-sdk:latest")
)
private var channels: MutableList<ManagedChannel> = mutableListOf()
private fun channel(): ManagedChannel {
return if (channels.isEmpty()) {
val endpoint = emulator.emulatorEndpoint
val channel = ManagedChannelBuilder
.forTarget(endpoint)
.usePlaintext()
.build()
channels.add(channel)
channel
} else {
channels.first()
}
}
private val channelProvider: TransportChannelProvider
get() {
return FixedTransportChannelProvider
.create(
GrpcTransportChannel.create(channel())
)
}
private val credentialsProvider: NoCredentialsProvider = NoCredentialsProvider.create()
private val topicAdminSettings: TopicAdminSettings
get() {
when {
emulator.isRunning -> {
return buildTopicAdminSettings()
}
else -> {
throw DockerClientException("Topic admin settings attempted to initialize before starting PubSub emulator")
}
}
}
private fun buildTopicAdminSettings(): TopicAdminSettings {
return TopicAdminSettings.newBuilder()
.setTransportChannelProvider(channelProvider)
.setCredentialsProvider(credentialsProvider)
.build()
}
private val subscriptionAdminSettings: SubscriptionAdminSettings
get() {
when {
emulator.isRunning -> {
return buildSubscriptionAdminSettings()
}
else -> {
throw DockerClientException("Subscription admin settings attempted to initialize before starting PubSub emulator")
}
}
}
private fun buildSubscriptionAdminSettings(): SubscriptionAdminSettings {
return SubscriptionAdminSettings.newBuilder()
.setTransportChannelProvider(channelProvider)
.setCredentialsProvider(credentialsProvider)
.build()
}
override fun start() {
emulator.withExposedPorts(*ports).start()
}
override fun stop() {
terminate()
emulator.stop()
}
private fun terminate() {
for (channel in channels) {
channel.shutdownNow()
channel.awaitTermination(5, TimeUnit.SECONDS)
}
}
fun createTopic(topicId: String) {
TopicAdminClient.create(topicAdminSettings).use { topicAdminClient ->
val topicName = TopicName.of(projectId, topicId)
topicAdminClient.createTopic(topicName)
}
}
fun listTopics(): List<String> {
return TopicAdminClient.create(topicAdminSettings)
.listTopics(projectName)
.iterateAll()
.map { it.name }
.toList()
}
fun createSubscription(subscriptionId: String, topicId: String) {
val subscriptionName = ProjectSubscriptionName.of(projectId, subscriptionId)
SubscriptionAdminClient.create(subscriptionAdminSettings).createSubscription(
subscriptionName,
TopicName.of(projectId, topicId),
PushConfig.getDefaultInstance(),
10
)
}
fun listSubscriptions(): List<String> {
return SubscriptionAdminClient.create(subscriptionAdminSettings)
.listSubscriptions(projectName)
.iterateAll()
.map { it.name }
.toList()
}
fun push(topicId: String, message: String) {
val publisher: Publisher = Publisher.newBuilder(TopicName.of(projectId, topicId))
.setChannelProvider(channelProvider)
.setCredentialsProvider(credentialsProvider)
.build()
val pubsubMessage: PubsubMessage = PubsubMessage.newBuilder().setData(ByteString.copyFromUtf8(message)).build()
publisher.publish(pubsubMessage).get()
}
fun poll(size: Int, subscriptionId: String): List<String> {
val subscriberStubSettings: SubscriberStubSettings = SubscriberStubSettings.newBuilder()
.setTransportChannelProvider(channelProvider)
.setCredentialsProvider(credentialsProvider)
.build()
GrpcSubscriberStub.create(subscriberStubSettings).use { subscriber ->
val pullRequest: PullRequest = PullRequest.newBuilder()
.setMaxMessages(size)
.setSubscription(ProjectSubscriptionName.format(projectId, subscriptionId))
.build()
val pullResponse: PullResponse = subscriber.pullCallable().call(pullRequest)
return pullResponse.receivedMessagesList
.map { it.message.data.toStringUtf8() }
.toList()
}
}
}
I couldn't find the answer to my question as it was asked.
I found a workaround for Junit5 with junit-pioneer it's possible to set the env variable to something before the actual test run.
Therefore, the code will be the same as before but annotated with #SetEnvironmentVariable
#SetEnvironmentVariable(key="GOOGLE_APPLICATION_CREDENTIALS", value="dev.json")
class PubSubTestServer {
...
}
JUnit-pioneer: Maven central.

Nest js : Use I18n service in Exception Filter

I have a nestjs-graphql project. I use class-validator and nestjs-i18n module.
I can use i18nService when injected in a service as intended. What I'm struggling to do however is to use i18n in my ExceptionFilter to return translated message from the ValidationPipe handled by class-validator
What I currently have
//app.module.ts
import { Module } from '#nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { ItemsModule } from './items/items.module';
import { GraphQLModule } from '#nestjs/graphql';
import { MongooseModule } from '#nestjs/mongoose';
import { ConfigModule } from '#nestjs/config';
import { I18nModule, I18nJsonParser } from 'nestjs-i18n';
import configuration from './config/configuration';
import * as path from 'path';
#Module({
imports: [
ConfigModule.forRoot({ isGlobal: true, load: [configuration] }),
MongooseModule.forRoot(process.env.MONGO_URI),
I18nModule.forRoot({
fallbackLanguage: 'en',
parser: I18nJsonParser,
parserOptions: { path: path.join(__dirname, '/i18n/') },
}),
GraphQLModule.forRoot({
autoSchemaFile: 'schema.gql',
playground: true,
introspection: true,
context: ({ req, connection }) =>
connection ? { req: connection.context } : { req },
}),
ItemsModule,
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
//main.ts
import { NestFactory } from '#nestjs/core';
import { AppModule } from './app.module';
import { ValidationPipe } from '#nestjs/common';
import { AllExceptionsFilter } from './utils/exceptions.filters';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(new ValidationPipe({ transform: true }));
app.useGlobalFilters(new AllExceptionsFilter(new Logger('Exceptions')));
app.enableCors();
const port = process.env.PORT || 3000;
await app.listen(port);
}
bootstrap();
//AllExceptionFilters.ts
import {
ExceptionFilter,
Catch,
HttpException,
HttpStatus,
Logger,
} from '#nestjs/common';
import { ApolloError } from 'apollo-server-errors';
import { MongoError } from 'mongodb';
#Catch(HttpException)
export class AllExceptionsFilter implements ExceptionFilter {
constructor(private logger: Logger) {}
async catch(exception: HttpException) {
const status =
exception instanceof HttpException
? exception.getStatus()
: HttpStatus.INTERNAL_SERVER_ERROR;
const exceptionMessage = (exception) =>
exception instanceof MongoError
? exception?.message
: exception?.response?.message;
this.logger.error(exceptionMessage(exception), exception.stack);
throw new ApolloError(exceptionMessage(exception), status.toString());
}
}
My idea was to pass the i18n key to the class to validate :
import { Field, InputType, } from '#nestjs/graphql';
import { Length } from 'class-validator';
#InputType()
export class ItemToValidate {
#Length(5, 30, { message: 'global.length' }) //i18n Key
#Field()
readonly title: string;
}
... to use it in AllExceptionsFilter as I would in a service:
#Catch(HttpException)
export class AllExceptionsFilter implements ExceptionFilter {
constructor(private logger: Logger, private i18n: I18nService) {}
async catch(exception: HttpException) {
const status =
exception instanceof HttpException
? exception.getStatus()
: HttpStatus.INTERNAL_SERVER_ERROR;
const exceptionMessage = (exception) =>
exception instanceof MongoError
? exception?.message
: exception?.response?.message;
const translatedMessage = await this.i18n.translate(
exceptionMessage(exception),
);
...
}
}
However, I have a logical error instantiating the Filter class in the boostrap function as I don't know how to access I18nService and inject it from there :
async function bootstrap() {
const logger = new Logger('bootstrap');
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(new ValidationPipe({ transform: true }));
app.useGlobalFilters(new AllExceptionsFilter(new Logger('Exceptions'), /*I18nService ?*/ ));
}
bootstrap();
What's the best way to go to achieve this?
As shown in the docs you cannot have dependency injection if you register your filter with useGlobalFilters().
Instead you have to do something like this:
import { Module } from '#nestjs/common';
import { APP_FILTER } from '#nestjs/core';
#Module({
providers: [
{
provide: APP_FILTER,
useClass: AllExceptionsFilter,
},
],
})
export class AppModule {}

query string format input UserId and password

I need a this format Url:
http://localhost:8089/api/Logins/CheckPassword?ID=[UserID]&Password=[Password]
My code does not work:
loginComponent.ts
import { Component, OnInit } from '#angular/core';
import { Router, ActivatedRoute } from '#angular/router';
import { FormBuilder, FormGroup, Validators } from '#angular/forms';
import { Observable } from 'rxjs';
import { LoginService } from '../login.service';
import { Login } from '../login';
#Component({
selector: 'app-login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.css']
})
export class LoginComponent implements OnInit {
loginForm: FormGroup;
loading=false;
submitted=false;
url: string;
constructor(
private formbuilder: FormBuilder,
private loginService: LoginService,
private router:Router,
private route: ActivatedRoute
) {
if(this.loginService.getLoginById)
{
this.router.navigate(['/']);
}
}
ngOnInit() {
this.loginForm = this.formbuilder.group({
Id: ['', [Validators.required]],
password: ['',Validators.required]
});
}
get f()
{
return this.loginForm.controls;
}
onFormSubmit() {
this.submitted=true;
if(this.loginForm.invalid)
{
return;
}
this.loading=true;
this.loginService.getLoginById()
//.pipe(first())
.subscribe(
data =>{
this.router.navigate([this.url]);
},
error =>
{
this.loading=false;
});
}
}
loginService.ts
import { Injectable } from '#angular/core';
import { HttpClient, HttpParams } from '#angular/common/http';
import { HttpHeaders } from '#angular/common/http';
import { Observable } from 'rxjs';
import { Login } from './login';
#Injectable({
providedIn: 'root'
})
export class LoginService {
constructor(private http: HttpClient) { }
getLoginById(): Observable<any>{
const _params=new HttpParams();
_params: _params.set('ID=','UserID');
_params: _params.set('Password=','Password');
return
this.http.get('http://localhost:8089/api/Logins/CheckPassword?',
{
params:_params
});
console.log(_params);
}
}
Here 'UserID' and 'password' should be replaced with the actual value.I do not see it in your code where you are getting these values.you need to call a method to populate your Userid and password
const _params=new HttpParams();
_params: _params.set('ID=','raju');
_params: _params.set('Password=','password123');
return
this.http.get('http://localhost:8089/api/Logins/CheckPassword?',
{
params:_params
});

Passing ajax request data from my service to a component (Angular 4)

I have a service that retrieves data from a GET request and stores this data into two variables that I want to pass to my component - I can see all the data within the service I am just unable to retrieve this data once in my component.
When I call this.sidebarService.getMenuItems() in the console.log() it returns undefined - any ideas
// sidebar service
import { Injectable } from '#angular/core';
import { HttpClient, HttpParams } from '#angular/common/http';
import { UserService } from '../../services/user.service';
#Injectable()
export class SidebarService {
menu: any;
menuItems: any;
menuCategories: any;
constructor(private http: HttpClient, private userService: UserService) {
getMenuItems() {
if (this.userService.getAuth() != null) {
this.http.get('https://dev.mysite.com/api/calendar/auth',
{ params: new HttpParams()
.set('token', this.userService.getAuth().__token)})
.subscribe(data => {
console.log('data', data);
if (data !== undefined) {
this.menuItems = (data.menuItems !== undefined) ? data.menuItems : [];
this.menuCategories = (data.menuCategories !== undefined) ? data.menuCategories : [];
}
});
}
}
}
// sidebar component
import { Component, OnInit } from '#angular/core';
import { HttpClient, HttpParams } from '#angular/common/http';
import { UserService } from '../../services/user.service';
import { SidebarService } from './sidebar.service';
#Component({
selector: 'app-sidebar',
templateUrl: './sidebar.component.html',
styleUrls: ['./sidebar.component.scss'],
})
export class SidebarComponent implements OnInit {
menu: any;
menuItems: any;
menuCategories: any;
constructor(private http: HttpClient, private userService: UserService, private sidebarService: SidebarService) {
console.log('menuService', this.sidebarService.getMenuItems());
}
ngOnInit() {
}
objectKeys = function(obj) {
return Object.keys(obj);
}
}
You are not returning anything from thegetMenuItems() method
You should return an Observable :
...
return this.http.get('...

Resources