How to apply authorization to whole controller except one method - node.js

I have added an AuthGuard on the UsersController class but I want to exclude one method in this controller from authorization.
Is it possible to exclude a single method from authorization?
I have added my code below:
#UseGuards(AuthGuard('jwt'))
#ApiUseTags('Users')
#Controller('users')
export class UsersController {
constructor(public service: UsersService) {}
get base(): CrudController<UsersService, UserEntity> {
return this;
}
#Override()
async createOne(#ParsedParams() params, #ParsedBody() body: UserEntity) {
const username = body.username, email = body.email;
const qb = await getRepository(UserEntity)
.createQueryBuilder('users')
.where('users.username = :username', { username })
.orWhere('users.email = :email', { email });
const _user = await qb.getOne();
if (_user) {
const error = { username: 'username already exists' };
throw new HttpException({ message: 'Input data validation error', error }, HttpStatus.BAD_REQUEST);
}
return this.base.createOneBase(params, body);
}
#Post(':id/avatar')
#UseInterceptors(FileInterceptor('file', {
storage: multerDiskStorageConfig('users', 'avatar'),
}))
async uploadAvatarFile(#Param() params, #UploadedFile() file: any) {
const userObject = await this.base.service.findOne();
userObject.avatar = file.path.replace(PUBLIC_ROOT_PATH, '');
return this.base.updateOneBase(params, userObject);
}
}

Related

Error on function Create at Controller it can't find the arguments on the service controller using Typescript, Node.js and express

I'm getting an error on the file transactionController, where it is not allowing me to use the function "create" because of the arguments inside of the serviceController file. I'll post the error shown by V.S.Code, right when I put the mouse over the "result". Error Message bellow in bold:
" const result: "User not found!" | "Error: Not enough balance" | "User account not found!" | Transaction
Argument of type '"User not found!" | "Error: Not enough balance" | "User account not found!" | Transaction' is not assignable to parameter of type 'ITransaction | undefined'.
Type 'string' is not assignable to type 'ITransaction'.ts(2345) ".
Thanks in advance for all your help.
Interface ITransaction:
export default interface ITransaction {
id: number;
debitedAccountId: number;
creditedAccountId: number;
value: number;
createdAt: Date;
};
import { Request, Response } from 'express';
import TransactionService from '../services/transactionService';
import ITransaction from '../interfaces/transactionInterface';
export class transactionController {
constructor(
private transactionService = new TransactionService()) {}
public CreateTransaction = async (req: Request, res: Response<ITransaction>) => {
try {
const { username , value, targetUser } = req.body;
// const transaction = { username, targetUserName, value };
const result = await this.transactionService.transfer(username, value, targetUser);
return res.status(201).json(result);
} catch (error) {
throw new Error ('Não foi possível realizar a transação');
}
};
public getAll = async (req: Request, res: Response<ITransaction[]>) => {
try {
const userId = req.body;
const transaction = await this.transactionService.getAll(userId);
return res.status(201).json(transaction);
} catch (error) {
throw new Error ('Não foi possível realizar a transação');
}
};
}
export default transactionController;
import { Op } from 'sequelize';
import Account from '../models/account';
import Transaction from '../models/transaction';
import User from '../models/user';
class transactionServices {
public async getAll(username: string, date?: Date): Promise<Transaction[]> {
const user = await User.findOne({
// selecionar a conta do usuário tabela users
where: {username: username}
});
const transaction = await Transaction.findAll({
where: {
[Op.or]:[
{debitedAccountId: user?.accountId},
{creditedAccountId: user?.accountId},
{date}
]
}
});
return transaction;
}
public async transfer(username: string, targetUsername:string, value:number ) {
const userOrigin = await User.findOne({
where: {username: username}
});
const userAccount = await Account.findByPk(userOrigin?.accountId);
if (!userAccount) return 'User not found!';
if( userAccount?.balance < value){
return 'Error: Not enough balance';
}
userAccount.balance = userAccount.balance - value;
userAccount.save();
const targetUser = await User.findOne({
where: {username: targetUsername}
});
const targetAccount = await Account.findByPk(targetUser?.accountId);
if(!targetAccount) return 'User account not found!';
targetAccount.balance = targetAccount.balance + value;
targetAccount.save();
const newTransaction = await Transaction.create({
debitedAccountId:userAccount.id, creditedAccountId:targetAccount.id, value, createdAt: Date.now() });
return newTransaction;
}
}
export default transactionServices;

How to move responses from service to controller?

I have login service and login controller.
Service:
class LoginService {
private tokenService: TokenService;
constructor() {
this.tokenService = new TokenService();
this.login = this.login.bind(this);
this.getClient = this.getClient.bind(this);
this.getUserProfile = this.getUserProfile.bind(this);
}
public async login(req: Request, res: Response) {
try {
const { password } = req.body;
const client = await this.getClient(req, res);
const userProfile = await this.getUserProfile(client);
if (!comparePassword(password, userProfile.password))
return res.status(StatusCodes.UNAUTHORIZED).json({ error: 'Incorrect password' });
const tokens = this.tokenService.generateTokens(client);
this.tokenService.setToken(res, tokens);
return res.status(StatusCodes.OK).json(tokens);
} catch (error) {
return res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({ msg: error.message });
}
}
}
Controller:
class LoginController {
private loginService: LoginService;
constructor(private router: IRouter) {
this.router = router;
this.loginService = new LoginService();
this.routes();
}
public routes() {
this.router.route('/auth').post(loginMiddleware, this.loginService.login);
}
}
So, my question is, how to move responses logic to controller correctly, cuz i'm just started to learn nestjs.

Node.js server gets stuck after a few API calls

I have a node.js application where I'm using the sign in functionality.
I'm facing an issue; that is, when I try to sign in, sometimes the request gets stuck. It won't return anything until I refresh the node.js server. Can anybody take a look at the code and help me out what am I doing wrong here?
Here is my Controller function
export const authenticate = async (
req: Request,
res: Response
): Promise<void> => {
try {
console.log("Login")
const result: DataObject = await model.authenticate(
req.body.username,
req.body.password
)
const { status, data } = result
res.status(status)
if(status==200) {
console.log(status)
const u : User = data as User;
const token = jwt.sign({ id: u.id }, process.env.JWT_SECRET as string)
res.json({token: token})
}
else {
res.json(data)
}
} catch (error) {
res.status(NOT_FOUND)
res.json(error)
}
}
And my Model method.
async authenticate(username: string, password: string): Promise<DataObject> {
try {
const sql =
"SELECT * FROM users WHERE username=$1"
const conn: PoolClient = await Client.connect()
const result: QueryResult<User> = await conn.query(sql, [username])
const { rows } = result
if (rows.length > 0) {
const user: User = rows[0]
const pepper: string = process.env.PASSWORD_HASH as string
const pepperedPass = password + pepper;
const validPass : boolean = bcrypt.compareSync(pepperedPass, user.password_digest);
if (validPass) {
const result: DataObject = {
status: OK,
data: user,
}
return result
}
else {
const passErr: DataObject = {
status: NOT_FOUND,
data: "Incorrect Password",
}
return passErr
}
}
const userErr: DataObject = {
status: NOT_FOUND,
data: "No User found with this Username",
}
return userErr
} catch (error) {
const userErr: DataObject = {
status: NOT_FOUND,
data: "No User found with this Username",
}
return userErr
throw new Error(`Not found, ${error}`)
}
}
I have tried res._destroy, res.set("Connection", "close"); but the issue persists.
Any suggestion/solution is appreciated.
Thank you.

User is not available in request with NestJS passport strategy (other than documentation exemples)

I'm trying to implement a passport strategy (passport-headerapikey), I was able to make it work and I can secure my routes.
But the request is empty and cannot access the logged in user ?
import { HeaderAPIKeyStrategy } from "passport-headerapikey";
import { PassportStrategy } from "#nestjs/passport";
import { Injectable, NotFoundException } from "#nestjs/common";
import { CompanyService } from "../../companies/companies.service";
#Injectable()
export class ApiKeyStrategy extends PassportStrategy(HeaderAPIKeyStrategy, "api-key") {
constructor(private readonly companyService: CompanyService) {
super(
{
header: "Authorization",
prefix: "Api-Key "
},
true,
async (apiKey, done) => {
return this.validate(apiKey, done);
}
);
}
public async validate(apiKey: string, done: (error: Error, data) => {}) {
const company = await this.companyService.findByApiKey(apiKey);
if (company === null) {
throw new NotFoundException("Company not found");
}
return company;
}
}
#UseGuards(AuthGuard("api-key"))
export class CompaniesController {
constructor(private companyService: CompanyService) {}
#Get()
#ApiOperation({ title: "Get company information" })
public getCompany(#Request() req) {
// here request is empty, so i cannot access the user..
console.log("request", req);
return [];
}
}
Thanks for your help !
To access the logged user, you can inject the object in the request. To do that, in your ApiKeyStrategy constructor, change the third parameter to something like this:
async (apiKey, verified, req) => {
const user = await this.findUser(apiKey);
// inject the user in the request
req.user = user || null;
return verified(null, user || false);
}
Now, you can access the logged user:
getCompany(#Request() req) {
console.log(req.user);
}
I hope that could help you.
As show in the documentation you should do some works to get the current user : here the documetation
First of all in the app.module make sure that the context is set :
context: ({ req }) => ({ req })
Then you can add this in the controller/resolver, this example use the Gql (GraphQL):
export const CurrentUser = createParamDecorator(
(data: unknown, context: ExecutionContext) => {
const ctx = GqlExecutionContext.create(context);
return ctx.getContext().req.user;
},
);
if this one doesnt work for you try this one instead :
export const CurrentUser = createParamDecorator(
(data: unknown, context: ExecutionContext) => {
const ctx = GqlExecutionContext.create(context);
const request = ctx.getContext();
request.body = ctx.getArgs();
return request.user;
},
);
Modify your validate method like so:
public async validate(apiKey: string, done: (error: Error, data) => {}) {
const company = await this.companyService.findByApiKey(apiKey);
if (company === null) {
return done(new NotFoundException("Company not found"), null);
}
return done(null, company);
}

Angular: JWT Token works fine on Postman but not in my app

I'm a bit of a beginner with Angular so please bear with me.
I have a simple app which allows people to register, login and retrieve their own user data (which is the part I am stuck at).
Backend user.routes.js :
const auth = require('./middlewares/auth')
module.exports = (app) => {
const user = require('./user.controller.js');
app.post('/login', user.login);
app.post('/register', user.register);
app.get('/getuser', auth, user.getuser);
}
Backend user.controller.js:
exports.getuser = async (req, res, next) => {
let user
try {
user = await User.findById(req.payload._id)
} catch (err) {
next(new InternalServerError('Could not fetch user', err))
return
}
if (!user) {
next(new NotFoundError('User not found'))
return
}
res.json(
pick(user, [
'email',
'firstName',
'lastName',
'accountType'
])
)
}
Backend user.service.ts :
#Injectable()
export class UserService {
private _isLoggedIn: BehaviorSubject<boolean> = new BehaviorSubject(false);
public readonly isLoggedIn$ = this._isLoggedIn.asObservable();
constructor(private http: HttpClient) {
this._isLoggedIn.next(this.isLoggedIn());
}
login(
email: string,
password: string,
rememberMe = false
): Observable<boolean | any> {
return this.http
.post<LoginResponse>('http://localhost:3001/login', { email, password })
.map(res => {
setToken(res.token, rememberMe);
this._isLoggedIn.next(true);
return true;
})
.catch(this.handleError);
}
register(
email: string,
password: string,
lastName: string,
firstName: string
): Observable<boolean | any> {
return this.http
.post<LoginResponse>('http://localhost:3001/register', {
email,
password,
lastName,
firstName
})
.map(res => {
setToken(res.token);
return true;
})
.catch(this.handleError);
}
logout() {
removeToken();
}
isLoggedIn() {
return tokenNotExpired();
}
getProfile() {
return this.http.get<Profile>('http://localhost:3001/getuser');
}
And finally, my backend auth.js :
// Dependencies
import { JwtHelperService } from '#auth0/angular-jwt';
// Angular
import {
HttpEvent,
HttpHandler,
HttpInterceptor,
HttpRequest
} from '#angular/common/http';
import { Injectable } from '#angular/core';
// RXJS
import { Observable } from 'rxjs/Observable';
// Environment
import { DecodedToken } from './decoded-token';
// Services
const helper = new JwtHelperService();
// Constants
export const TOKEN_NAME = 'access_token';
// Exports
export function getToken(storage = null) {
if (storage) {
const token = storage.getItem(TOKEN_NAME);
if (token && !helper.isTokenExpired(token)) {
return token;
}
removeToken(storage);
return null;
}
return getToken(localStorage) || getToken(sessionStorage);
}
export function setToken(token: string, rememberMe = false) {
const storage = rememberMe ? localStorage : sessionStorage;
storage.setItem(TOKEN_NAME, token);
}
export function removeToken(storage = null) {
if (storage) {
storage.removeItem(TOKEN_NAME);
} else {
localStorage.removeItem(TOKEN_NAME);
sessionStorage.removeItem(TOKEN_NAME);
}
}
export function tokenNotExpired() {
return !helper.isTokenExpired(getToken());
}
export function decodeToken(): DecodedToken {
return helper.decodeToken(getToken());
}
#Injectable()
export class JwtHttpInterceptor implements HttpInterceptor {
constructor() {}
intercept(
request: HttpRequest<any>,
next: HttpHandler
): Observable<HttpEvent<any>> {
const token = getToken();
let clone: HttpRequest<any>;
if (token) {
clone = request.clone({
setHeaders: {
Accept: `application/json`,
'Content-Type': `application/json`,
Authorization: `Bearer ${token}`
}
});
} else {
clone = request.clone({
setHeaders: {
Accept: `application/json`,
'Content-Type': `application/json`
}
});
}
return next.handle(clone);
}
}
On my dashboard, I do a very simple request:
this.userService.getProfile().subscribe(data => (this.profile = data));
Now, my problem is the following:
Using Postman, if I do a POST request to /login, I get a token back. Everything fine so far. And if I use this token (in Postman) in my next GET request to /getuser, I also get the results I want (email, firstName, lastName, accountType of the user).
However, the problem is on the front-end. I login and arrive to the main page (no issues there), but once getProfile() is called, I get a GET http://localhost:3001/getuser 401 (Unauthorized) . I've been stuck on this for hours and not sure where the problem is from.
I appreciate any help I can get.
Thanks!
I found my issue. I had forgotten to add the Interceptor I had created to my providers in app.module.ts.
// Auth
{
provide: HTTP_INTERCEPTORS,
useClass: JwtHttpInterceptor,
multi: true
}

Resources