typescript jwt.verify cannot access data - node.js

I'm trying to use JWT with nodejs.
My problem is that I can't read the data from the JWT verify function.
I'm using it like this :
//encode when logging in
const token = jwt.sign(
{ user: user },
'secret'
);
// decode when fetching the user from token
const decoded = jwt.verify(req.body.jwtToken, 'secret');
return res.send({
user: decoded.user // <-- error here
});
Here are the typings for the verify method:
export declare function verify(
token: string,
secretOrPublicKey: string | Buffer,
): object | string;
linter Error is :
Property user does not exists on typeof "object|string".
How am I supposed to get the data from the decoded token?
Link to the documentation of the library

When using Typescript, you have to remember everything is typed as in e.g. Java or C#.
object is a superclass that has no knowledge of the property user.
While this code is valid in javascript (you are looking at javascript documentation), it is not in typescript.
To fix this error, cast the decoded token using any.
return res.send({
user: (<any>decoded).user
});

You need to cast the decoded token. Although casting to any will work, you'll also lose type checking on that variable.
A more robust approach is to declare an interface that captures the structure of your decoded token and cast using it.
// token.ts
export interface TokenInterface {
user: {
email: string;
name: string;
userId: number;
};
}
and then you can cast using
decoded as TokenInterface
or more exactly in your case
return res.send({
user: (decoded as TokenInterface).user
});
Notes:
The casting is done at compile time, not runtime
Creating an interface has the added benefit that you keep your type definition in one place. This is especially useful if you want to add a field of that particular type to an existing object. An example of this is adding the token as a field on object of the express.Request type.

Create a user payload interface
interface UserPayload {
id: string;
}
interface JwtExpPayload {
expiresIn: string;
exp: number;
}
Cast to UserPaylod
try {
const jwtPayload = jwt.decode(
req.header('authorization')!
) as JwtExpPayload;
req.jwtPayload = jwtPayload;
const payload = jwt.verify(
req.header('authorization')!,
process.env.JWT_KEY!
) as UserPayload;
req.currentUser = payload;
} catch (err) {
console.log(err);
}
middleware function
export const requireAuth = (
req: Request,
res: Response,
next: NextFunction
) => {
if (req.jwtPayload && Date.now() >= req.jwtPayload!.exp * 1000) {
throw new TokenExpiredError();
}
if (!req.currentUser) {
throw new NotAuthorizedError();
}
next();
};
const userJwt = jwt.sign(
{
id: existingUser.id,
},
process.env.JWT_KEY!,
{ expiresIn: 30 }
);

Related

Property `role` does not exist on type `User | AdapterUser` in NextAuth

I am attempting to build a NextJS application that implements NextAuth. I am encountering the following error in my [...nextauth].ts when configuring my callbacks:
Type error: Property 'role' does not exist on type 'User | AdapterUser'.
Property 'role' does not exist on type 'User'.
56 | jwt: async ({ token, user }) => {
57 | // First time JWT callback is run, user object is available
> 58 | if (user && user.id && user.role) {
| ^
59 | token.id = user.id;
60 | token.role = user.role;
61 | }
The complete callback section of code looks like this:
callbacks: {
jwt: async ({ token, user }) => {
// First time JWT callback is run, user object is available
if (user && user.id && user.role) {
token.id = user.id;
token.role = user.role;
}
return token;
},
session: async ({ session, token }) => {
if (token && token.id && token.role) {
session.id = token.id;
session.role = token.role;
}
return session;
},
},
I am using the CredentialProvider with an email and a password. Here is authorize:
async authorize(credentials) {
if (!credentials || !credentials.email) return null;
const dbCredentials = await executeAccountQuery(
`SELECT password FROM auth WHERE email=?`,
[credentials.email]
);
if (Array.isArray(dbCredentials) && "password" in dbCredentials[0]) {
const isValid = await compare(
credentials.password,
dbCredentials[0].password
);
if (isValid) {
return {
id: "5",
role: 99,
name: "John Smith",
email: credentials.email,
};
}
return null;
}
return null; // login failed
},
Because of the way the authorize function is working, I know for a fact that the User object will have a role appended to it (because I have tested it), but I cannot figure out a way to handle this error and get rid it.
Similarily, I also get an error with the session callback where the session.id and session.role are also not present on Session.
After a lot of digging through the internet and some trial and error, I was able to figure out how to solve this problem.
You will need to manually extend the Session, User and JWT types by doing the following:
Create types/next-auth.d.ts in your root project directory.
Insert the following into the file:
import { Session } from "next-auth";
import { JWT } from "next-auth/jwt";
declare module "next-auth" {
interface Session {
id: string;
role: number;
}
interface User {
id: string;
role: number;
}
}
declare module "next-auth/jwt" {
interface JWT {
id: string;
role: number;
}
}
Run npm run build and verify that it builds correctly.
Type Definition:
types/next-auth.d.ts:
import { DefaultUser } from 'next-auth';
declare module 'next-auth' {
interface Session {
user?: DefaultUser & { id: string; role: string };
}
interface User extends DefaultUser {
role: string;
}
}
Usage:
async session({ session, user }) {
if (session?.user) {
session.user.id = user.id;
session.user.role = user.role;
}
return session;
}

Mongoose/Typescript issue

So, i have to update some data inside my main interface, the problem is that, when i tried to do that, it complains because .save() is not defined
So i create another interface to that data in order to extends Document so i can have access to .save()
But, here's the new error....
const theComment: IComment
Type 'Comment' is missing the following properties from type 'IComment': $getAllSubdocs, $ignore, $isDefault, $isDeleted, and 47 more.
Here's my code
What i want to update ( the problem is theComment )
export const editComment = async (req: Request, res: Response) => {
const {publicationId} = req.params;
const { identifier, body, commentId } = req.body;
// Check id's
if (!mongoose.Types.ObjectId.isValid(identifier!))
return res.status(400).json({ Message: "identifier not valid" });
if (!mongoose.Types.ObjectId.isValid(publicationId!))
return res.status(400).json({ Message: "identifier not valid" });
if (!mongoose.Types.ObjectId.isValid(commentId!))
return res.status(400).json({ Message: "identifier not valid" });
// Find pub
const thePub: Ipub = await Publication.findById(publicationId);
// Find user
const theUser: Iauth = await User.findById(identifier);
// Find comment, make sure that comment is from that user
const theComment: IComment = thePub.comments!.find((f) => f.id === commentId && f.identifier === theUser.id)!;
if(!theComment) return res
.status(405)
.json({ Message: "You are not the owner of the comment || Comment doesn't exist" })
// Make sure body is not empty
if(!body) return res.status(404).json({Message: 'No data provided'})
try {
// Update comment and perfil if it has changed
theComment.body = body;
theComment.perfil = theUser.perfil;
await theComment.save()
return res.json(theComment)
} catch (err) {
console.log(err);
return res.status(500).json({ Error: "the API failed" });
}
};
Main interface
export interface Ipub extends Document {
id?: string;
body: string;
photo: string;
creator: {
name: string;
perfil?: string;
identifier: string;
};
likes?: Likes[];
comments?: Comment[];
createdAt: string;
}
Data's interface that i want to update inside my main interface
export interface IComment extends Document {
id?: string;
body: string;
name: string;
perfil?: string;
identifier: string;
createdAt: string;
likesComments?: Likes[];
}
What can i do ? how can i solve it ?
Thanks for your time comunnity !!
TS Compiler says the object described by Comment interface doesn't have .save() method. And as far as I presume it should not have because it's not a MongoDB document.
The time you inherit all props from Document interface the compiler throws the error saying that types Comment & IComment are not compatible because the second one has Document props, and the first one doesn't. To fix it you should just cast the type directly like this:
const theComment = thePub.comments!.find((f) => f.id === commentId && f.identifier === theUser.id)! as IComment;
But in order to update the comment you have to update 'whole' Publication document(for example, by using aggregate):
Publication.update(
{
"id": publicationId,
"comments.id": commentId,
"comments.identifier": theUser.id,
},
{ $inc: {
"comments.$.body": body,
"comments.$.perfil": theUser.perfil,
}},
false,
true,
);
Or the best option I think is to use relationships between Documents. Create another Document named Comment and save all related comments there. In that case you will have an ability to use .save() and other methods provided.

Nodejs TypeScript: Property 'email' does not exist on type 'string | object'

TypeScript is giving me the below error in the terminal.
static resendEmail = async (req: Request, res: Response) => {
const jwtToken = req.body.jwtToken;
const jwtData = jwt.verify(jwtToken, process.env.JWT_SECRET);
const email = jwtData.email;
const account = await userModel.findOne({email}).exec();
console.log(account)
};
jwtData looks like this..
{ email: 'me#email.com',
id: '5d74fc5d900de015a04c2107',
iat: 1567947869 }
The error is error TS2339: Property 'email' does not exist on type 'string | object'. the place is const email = jwtData.email;
Edit
static resendEmail = async (req: Request, res: Response) => {
interface JWTData {
email: string;
id: string;
}
const jwtToken = req.body.jwtToken;
const jwtData = jwt.verify(jwtToken, process.env.JWT_SECRET);
const isJWTData = (input: object): input is JWTData => {
return 'email' in input;
}
if (isJWTData(jwtData)) {
console.log(jwtData.email)
}
};
the error is now Shadowed name: 'jwtData' (no-shadowed-variable)tslint(1)
The type definitions for the verify method define the return type as string | object. So you're trying to access email on string | object (which it does not exist).
You have a few options:
Cast the value and access email (could lead to a runtime error if the types are correct)
Use a type guard (protects against runtime errors)
Override the types or open a PR to the #types package
const jwtData: string | object = { email: "user#example.com" };
jwtData.email; // Invalid
interface JWTData {
email: string;
}
(jwtData as JWTData).email // Valid
const isJWTData = (input: object | string): input is JWTData => {
return typeof input === "object" && "email" in input;
};
if (isJWTData(jwtData)) {
jwtData.email; // Valid
}
TypeScript Playground
Overriding the types for verify in only your project may look something like this:
declare module "jsonwebtoken" {
export function verify(
token: string,
secretOrPublicKey: string | Buffer,
options?: VerifyOptions
): { email: string };
}
const jwtData = verify("token", "secretOrPublicKey");
const email = jwtData.email;
I would be careful casting or modifying the types (unless the types themselves are wrong in which case opening a PR to DefinitelyTyped would be the best options).
The safest route is to use the type guard because it also protects unexpected results at runtime as well.

#Get DTO with multiple parameters in NestJs

I'm trying to create a controller action in NestJS accessible via GET HTTP request which receives two params but they are undefined for some reason.
How to fix it?
#Get('/login')
login(#Param() params: LoginUserDto) {
console.log(params)
return 'OK'
}
import { ApiModelProperty } from '#nestjs/swagger';
export class LoginUserDto {
#ApiModelProperty()
readonly userName: string;
#ApiModelProperty()
readonly password: string;
}
In Browser
localhost:3001/Products/v1/user2
Controller like this:
#Controller('Products')
export class CrashesController {
constructor(private readonly crashesService: CrashesService) { }
#Get('/:version/:user')
async findVersionUser(#Param('version') version: string, #Param('user') user: string): Promise<Crash[]> {
return this.crashesService.findVersionUser(version, user);
}
}
Nest doesn't support the ability to automatically convert Get query params into an object in this way. It's expected that you would pull out the params individually by passing the name of the param to the #Param decorator.
Try changing your signature to:
login(#Param('userName') userName: string, #Param('password') password: string)
If you want to receive an object instead consider switching to using Post and passing the object in the request body (which makes more sense to me for a login action anyways).
Right now i am using nestJs on 7.0.0 and if you do this:
#Get('/paramsTest3/:number/:name/:age')
getIdTest3(#Param() params:number): string{
console.log(params);
return this.appService.getMultipleParams(params);
}
the console.log(params) result will be(the values are only examples):
{ number:11, name: thiago, age: 23 }
i hope that after all that time i've been helpful to you in some way !
Let's say you need to pass a one required parameter named id you can send it through header params, and your optional parameters can be sent via query params;
#Get('/:id')
findAll(
#Param('id') patientId: string,
#Query() filter: string,
): string {
console.log(id);
console.log(filter);
return 'Get all samples';
}
#Get('/login/:email/:password')
#ApiParam({name:'email',type:'string'})
#ApiParam({name:'password',type:'string'})
login(#Param() params: string[]) {
console.log(params)
return 'OK'
}
Output
{email:<input email >,password:<input password>}
You can get multiple params and map them to your dto in this way:
#Get('/login')
login(#Param() { userName, password }: LoginUserDto) {
console.log({ userName});
console.log({ password });
return 'OK'
}

what would be an extensible structure for node.js with native mongodb driver in Typescript

using node.js with the native mongodb driver is there a way to enforce a schema/schemaless structure by using classes or interfaces with Typescript (ES6). For example, if we have a collection called users. and we do something along the lines of
//Interface Library
interface INewUser {
name: string,
age: number,
location: string
}
//DAL
function insertUser(data: INewUser, cb: MongoCallBack < InsertOneWriteOpResult > ) {
//for best practices we would have an instance connected to db already
//so we do not have to open a new connection on every request
MongoClient.connect(uri, function(err, db) {
//handle err first
db.collection("users", function(err, col) {
//handle err first
col.insertOne(data, function(err, response: InsertOneWriteOpResult) {
//MongoCallBack expects (MongoError, <T>) T = InsertOneWriteOpResult
return cb(null, response)
});
});
});
}
//api endpoint
app.post('/user', function(req, res) {
//do some checks to make sure the req.body is valid -- i.e not empty no xss attacks..
let newUser: INewUser = req.body;
insertUser(newUser, function(err, res) {
if (res.res.ok == 1) {
res.json({
success: true
});
}
});
});
/*example data being posted to /user
{
name: "test",
age: 33,
location: "NA"
} */
I'm looking to find some of the best styles/techniques that could be used to structure data flow throughout an application utilizing typescript.
I have an application which is in production and works in a similar way that you described.
Though It's not mongodb and it uses an ORM sequelize with MySQL. Back in the past I've used mongoose, but this was not the requirement with the application I'm working on right now.
Nevertheless I use something similar :
// models/User.js
export interface UserAttributes {
name: string;
age: number;
location: string;
}
interface UserInstance extends UserAttributes {
id: number;
}
Then in my AJAX end-points I use Joi, for validating the schema
import { UserAttributes } from "../models/User";
const schema = Joi.object( {
name : Joi.string().required(),
age: Joi.string().required(),
location: Joi.string().required()
} );
server.post("/user", function( req, res ) {
const body = req.body;
Joi.validate( body, schema, function( errors ) {
if ( !errors ) {
const user : UserAttributes = req.body;
db.User.create( user ).then( user => res.json( { user } ) );
} else {
// handle error logic
}
});
});
This has the benefit that you validate the model before you assign it to your interface. Making sure that you will always have a valid user after the AJAX call.

Resources