nestjs/crud: How use TypeOrmCrudService in no crud controller? - nestjs

I have entity, crud service and crud controller like this
Now I need add new user in another controller or service. For example, in another no CRUD controller:
#Post('sign-up')
async signUp(
#ParsedRequest() req: CrudRequest,
#Res() res: Response,
#Body() body: any,
) {
const user = await this.usersService.createOne(req, body);
console.log('user here', user);
res.send(user);
}
in this case req is undefined, and I got error.
Ok, I know that I can move sign-up endpoint to User controller, it will resolve my problem.
But, how I can use TypeOrmCrudService in another controllers and services, not only in CRUD controller?

Related

Passportjs send different data types to req.user and serializeUser

I'm implementing a basic local authentication flow with passportjs and cookie-session middlewares in my node server.
TLDR:
Inside verifyCallback I query the database for the user data and in response I receive an object like this:
{
_id: '...',
email: '...',
password: '...',
//some other props
}
I want to send only _id to the cookie session but for req.user I want it to contain the entire object. In javascript this is not an issue but adding typescript and complying with the type definitions make this task more complicated because:
//serializeUser type:
serializeUser<TID>(fn: (user: Express.User, done: (err: any, id?: TID) => void) => void): void;
//deserializeUser type:
deserializeUser<TID>(fn: (id: TID, done: (err: any, user?: Express.User | false | null) => void) => void): void;
//req.user type:
Express.user
serializeUser, deserializeUser and req.user share the same type when it comes to the user data and it cannot be customized through generics.
My auth flow is as follows:
user sends the signin data to the server /signin endpoint, which has this shape:
{
email: string;
password: string;
}
this is the express route that will handle that endpoint:
authRouter.post('/signin', passport.authenticate('local'), httpSignin);
This means:
the verifyCallback will be triggered by passport.authenticate('local').
inside verifyCallback I make a query to the database to check for the existence of the user and then compare the passwords, finally I call the done callback passing only the _id property I've got from the database response.
async function verifyCallback (username: string, password: string, done: DoneCallback, next: NextFunction) {
const res = await findOne({ username: req.body.username });
// check if user exists
// check password hashes
const userData = { _id: res._id }
return done(null, userData);
}
req.user will then be populated with userData.
userData will be also passed to the serializeUser function.
the httpSignin controller function will be called and it will respond to the client with also the userData from req.user.
function httpSignin (req: Request<any, any, UserCredentials>, res: Response, next: NextFunction) {
res.status(200).json({ userData: req.user })
}
Now because I'm using typescript I understand (according to this question) that I have to extend the Express.User interface with the data I want to serialize into the session so passport can recognize it in serializeUser and deserializeUser functions. So I put this in global.d.ts:
declare namespace Express {
export interface User {
_id: string;
}
}
Until here all is fine, but then here comes the issue. As I mentioned before at the end of this auth flow I will call my httpSignin controller and respond to the client with the user data which right now is an object that only contains the _id property, but what I really need is to respond to the client not only with the _id property but with the entire user data I received from the database (except of course the password) because I wan't to populate my Vuex store with that data in the client.
So since I'm making a query to the database in the verifyCallback function and thus getting the user data which then will be passsed to req.user I would like to take advantage of this data and send it in the response to the client, this way I don't have to make a second query to the database in httpSignin to get the very same data that I already got in the verifyCallback.
However to do this I will have to pass the entire user data object which I received from the database to the done(null, userData) callback inside verifyCallback, this means that I will have to extend Express.User not just with the _id property (which is the only one I want to assign to the cookie) but with the entire user data object and this in turn will force me (at least if I want to respect the type definitions) to serialize the entire user object too which needless to say is a bad idea because the cookie doesn't need all that information.
Right now I'm just avoiding to extend Express.User and casting or assigning the types manually:
passport.serializeUser((userData, done) => {
done(null, (userData as User)._id.toString());
});
passport.deserializeUser<string>((userId, done) => {
done(null, userId);
});
function httpSignin (req: Request<any, any, UserCredentials>, res: Response, next: NextFunction) {
const { password, ...userData } = req.user as User;
res.status(200).json({ userData: req.user })
}
But I would like to avoid type casting if possible.

Pass Information from NestJS middlewares to controllers/services

I have applied authentication in NestJS middlewares and decoding all the information from user token by the third party API.
Now I have got the information in my middleware, how can I send that information to controllers and services?
It is normal for middlewares to add information to the request object (eg. adding a user object).
For a clean way to extract information from the request object and inject it into the controller you can make a custom route decorator
For example, extracting the user:
import { createParamDecorator, ExecutionContext } from '#nestjs/common';
export const User = createParamDecorator(
(data: unknown, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest();
return request.user;
},
);
And then in your controller
#Get()
async findOne(#User() user: UserEntity) {
console.log(user);
}
From there you can just ensure that your service methods receive the User as a regular method parameter

Login with Facebook Flow in REST (Express.js with TypeScript + Passport.js) - few questions

i have few questions about login with facebook flow in REST application. I use Passport.js, Express.js, Typeorm and TypeScript. I don't implement client site yet.
First i created facebook strategy and i get a profile information in this place in code, but i want to process profile (check, save and generate JWT) in controller by use service layer.
passport.ts
const facebookStrategyCallback = async(accessToken: any, refreshToken: any, profile: any, done: any) => {
// I can get and process profile here, but i want to do it in controller use only
// service layer to check, save and generate JWT token.
done(null, profile);
};
This is my controller with facebook route and callback route:
facebookAuth = (request: Request, response: Response, next: NextFunction) => {
passport.authenticate('facebook')(request, response, next);
};
facebookAuthCallback = (request: Request, response: Response, next: NextFunction) => {
passport.authenticate('facebook', {
session: false,
failureRedirect: '/authenticate/fail'
}, (error, profile) => {
// process profile here using service functions
// But what do next?
})(request, response, next);
};
My flow for now is:
In client someone click to Login with Facebook (redirect to localhost:8080/oauth2/facebook)
User see facebook login form and fill it
User profile with query params CODE back to server site application localhost:8080/oauth2/facebook/callback?Code={somecode} (Is this correct way?)
...
And now what's next? What i should do with CODE get from (/ouath2/facebook/callback?CODE={somecodehere}). I can process profile save it into database, but how to back token to client? With redirect and query params? it's less security i guess.
Maybe i should set facebook callback url to localhost:3000/facebook/callback - but what i should do with this code from query params when i pass it back to server site?
Can someone write me a correct flow in REST application?

Social Login in NestJS using #AuthGuard, Passport

So, I have almost finished attempt to implement social login in NestJS powered app. I have some problems though:
First things first. I have AuthModule and in there is provider TwitterGuard:
const twitterOptions: IStrategyOptionWithRequest = {
consumerKey: process.env[ENV.SOCIAL.TWITTER_CONSUMER_KEY],
consumerSecret: process.env[ENV.SOCIAL.TWITTER_CONSUMER_SECRET],
callbackURL: process.env[ENV.SOCIAL.TWITTER_CALLBACK_URL],
passReqToCallback: true,
includeEmail: true,
skipExtendedUserProfile: false,
};
export class TwitterGuard extends PassportStrategy(Strategy, 'twitter') {
constructor() {
super(twitterOptions);
}
// Magical nest implementation, eq to passport.authenticate
validate(req: Request, accessToken: string, refreshToken: string, profile: Profile, done: (error: any, user?: any) => void) {
const user: SocialAuthUser = {
id: profile.id,
nick: profile.username,
name: profile.displayName,
};
if (profile.emails) {
user.email = profile.emails.shift().value;
}
if (profile.photos) {
user.avatar = profile.photos.shift().value;
}
done(null, user);
}
}
as well as AuthController:
#Controller('auth')
#ApiUseTags('auth')
export class SocialAuthController {
constructor(private us: UserService) {
}
#Get('twitter')
#UseGuards(AuthGuard('twitter'))
twitter() {
throw new UnauthorizedException();
}
#Get('twitter/callback')
#UseGuards(AuthGuard('twitter'))
async twitterCallback(#ReqUser() socialUser: SocialAuthUser, #Res() response) {
const user = await this.us.registerSocialUser(socialUser);
if (user) {
// console.log('Redirect', '/some-client-route/token');
response.redirect(`${SITE_URL}/activate/${user.token}`);
}
response.sendStatus(401);
}
}
When I am calling URL /auth/twitter the guard kicks in and reroutes to Twitter page asking user to grant access to Twitter app.
If the user grants access, everything is fine, on the callback route (/auth/twitter/callback) the TwitterGuard kicks in again and processes user in validate, stores to request and I can access that further in controller. So far so good.
However if user denies access to Twitter app, the guard returns 401 on the callback route even before any of my methods are hit.
I tried to play with authenticate method that is called (now commented out in the code) where I could somehow maybe tweak this but have no idea what to return or do. If that is a way to go, how do I redirect from there to twitter auth page like passport strategy does? What to return on callback to keep going and set some flag that access was denied?
Is there any other way to do it? What am I missing?
Thanks in advance.
Edit: If you have questions what does #ReqUser() do, here it is:
export const ReqUser = createParamDecorator((data, req): any => {
return req.user;
});
Nevermind, I found a solution, this answer helped a lot. Posting here in case someone else would get into the same trouble.
I created TwitterAuthGuard:
export class TwitterAuthGuard extends AuthGuard('twitter') {
handleRequest(err, user, info, context) {
return user;
}
}
and used it at callback route:
#Get('twitter/callback')
#UseGuards(TwitterAuthGuard)
async twitterCallback(#ReqUser() socialUser: SocialAuthUser, #Res() response) {
if (socialUser) {
const user = await this.us.registerSocialUser(socialUser);
if (user) {
response.redirect(`...url`);
return;
}
}
response.redirect(SocialAuthController.authFailedUrl(LoginMethod.TWITTER));
}
Now, when Twitter calls the callback route, it gets into TwitterAuthGuard handleRequest method.
If the access was granted, user parameter contains data from user profile and is passed further down the chain to TwitterGuard validate method (see above in the question).
If the access was denied then user parameter is false.
Therefore in the controller callback route method I get either normalized user data or false in user parameter therefore I can check whether it failed or not and act accordingly.

Inject HttpContext into InversifyJS middleware

I have the following controller.
#controller('/users')
class UsersController {
#httpGet('/', authMiddleware({ role: 'ADMIN' }))
public get() { ... }
}
I have implemented a custom AuthenticationProvider, which returns a principal containing details about the currently authenticated user, including the user's roles.
....
return new Principal({
firstName: "John",
lastName: "Smit",
roles: ["ADMIN"]
});
...
This all works fine, but I am wondering how I can retrieve the principal from the authMiddleware which is used by the above GET route.
For now I have an ugly hack which uses internals of InversifyJS.
function authMiddlewareFactory() {
return (config: { role: string }) => {
return (
req: express.Request,
res: express.Response,
next: express.NextFunction
): void => {
const httpContext: interfaces.HttpContext =
Reflect.getMetadata(
"inversify-express-utils:httpcontext",
req
);
const principal: interfaces.Principal = httpContext.user;
if (!principal.isInRole(config.role)) {
res.sendStatus(HttpStatus.UNAUTHORIZED);
return;
}
next();
};
};
}
The custom authentication provider uses the authorization header to authenticate the user and returns a principal. I don't want to do this work again in the middleware, I just want to retrieve the principal.
This hack works, but I was wondering if someone knows a cleaner way of obtaining the HttpContext in this middleware.
I know you can access the HttpContext and thus the principal (user) if you extend from the BaseMiddleware, but then it's not clear to me how you pass configuration (parameters) to it, such as the desired role. Related to the following issue on InversifyJS.
https://github.com/inversify/InversifyJS/issues/673
This is not supported, but I can see why it is needed. We cannot pass the httpContext to the middleware as an argument because we want to keep the standard Express middleware compatible. This means that the only option is doing something like what you have done but ideally we should encapsulate it using some helper.
We need to implement something like the following getHttpContext function:
import * as express from "express";
import { getHttpContext } from "inversify-express-utils";
function authMiddlewareFactory() {
return (config: { role: string }) => {
return (
req: express.Request,
res: express.Response,
next: express.NextFunction
): void => {
const httpContext = getHttpContext(req);
const principal: interfaces.Principal = httpContext.user;
if (!principal.isInRole(config.role)) {
res.sendStatus(HttpStatus.UNAUTHORIZED);
return;
}
next();
};
};
}
Until this is implemented I don't see any problems with your implementation other than the information leakage of the inversify internals.

Resources