How to modify currentUserStore in mobx-state-tree - mobx

I'm trying to wrap my head around mobx-state-tree and whipped up a simple currentUserStore to hold some data for a logged in user and allow login/logout:
import { types } from "mobx-state-tree";
import { client } from '../../../helpers/client';
const User = types
.model("User", {
name: types.string,
email: types.string,
type: types.string,
token: types.string,
roles: types.array(types.string),
})
export const CurrentUserStore = types
.model("CurrentUserStore", {
user: types.optional(types.maybeNull(User), () => null)
})
.actions((currentUserStore) => ({
async login(login, password) {
const result = await client.post(`auth`, {
email: login,
password,
});
const { name, email, type, token, roles } = result
currentUserStore.user = User.create({ name, email, type, token, roles })
localStorage.setItem('authUser', JSON.stringify(result));
},
logout() {
currentUserStore.user = null
localStorage.removeItem('authUser')
},
}));
When calling the login function, I get the error Cannot modify 'CurrentUserStore#/currentUserStore', the object is protected and can only be modified by using an action.. There's something I'm missing here, but not exactly sure why I shouldn't be able to do something like this after reading through example where the store is modified directly in an action like this.

Once I moved the modification out of an async func, it worked. Just changed to this:
async login(login, password) {
const result = await client.post(`auth`, {
email: login,
password,
});
this.setUser(User.create(result))
localStorage.setItem('authUser', JSON.stringify(result));
},
logout() {
currentUserStore.user = null
localStorage.removeItem('authUser')
},
setUser(user) {
self.user = user
}

Related

authorization header undefined, empty

Can you explain what I'm doing wrong, I don't understand why my header is empty. Login is working but when I trigger the me query it's not working, not authenticated appear and if I console log this it's undefined.
How to keep the user logged, and be able to acces the data ?
Here is my code, if you know what's wrong.
Thanks
import * as dotenv from 'dotenv';
dotenv.config();
import express from 'express';
import { ApolloServer } from 'apollo-server-express';
import { PrismaClient } from '#prisma/client';
import bcrypt from 'bcrypt';
import jwt from 'jsonwebtoken';
const JWT_SECRET = 'abc123';
const typeDefs = `
type User {
id: ID
username: String
}
type Query {
me: User
}
type Mutation {
signup(username: String!, password: String!): User
login(username: String!, password: String!): User
}
`;
const resolvers = {
Query: {
async me(_, __, context) {
// Check if the request has a valid JWT
const auth = context.req.headers.authorization;
if (!auth) {
throw new Error('Not authenticated');
}
// Get the user ID from the JWT
const token = auth.split('Bearer ')[1];
const { userId } = jwt.verify(token, JWT_SECRET);
// Retrieve the user from the database
return context.prisma.user({ id: userId });
},
},
Mutation: {
async signup(_, { username, password }, context) {
// Hash the password
const hashedPassword = await bcrypt.hash(password, 10);
// Create a new user in the database
const user = await context.prisma.user.create({
data: {
username,
password: hashedPassword,
},
});
// Create a JWT for the new user
const token = jwt.sign({ userId: user.id }, JWT_SECRET);
return { token, ...user };
},
async login(_, { username, password }, context) {
// Retrieve the user from the database
const user = await context.prisma.user.findUnique({
where: {
username: username,
},
});
if (!user) {
throw new Error('Invalid login');
}
// Compare the provided password with the hashed password
const valid = await bcrypt.compare(password, user.password);
if (!valid) {
throw new Error('Invalid login');
}
// Create a JWT for the user
const token = jwt.sign({ userId: user.id }, JWT_SECRET);
return { ...user, token };
},
},
};
const main = async () => {
const app = express();
const prisma = new PrismaClient();
const server = new ApolloServer({
typeDefs,
resolvers,
context: ({ req }) => ({ req, prisma }),
});
await server.start();
server.applyMiddleware({ app });
app.listen(4000, () =>
console.log(`GraphQL server running on http://localhost:4000`)
);
};
main().catch((err) => {
console.error(err);
});

[uncaught application error]: TypeError - Cannot read properties of undefined (reading 'name')

I get this error message when I try to test the /api/register end point with Postman and following POST request:
{
"name" : "first",
"email" : "first#one.com",
"password" : "123"
}
[uncaught application error]: TypeError - Cannot read properties of undefined (reading 'name')
request: { url: "http://0.0.0.0:8000/api/register", method: "POST", hasBody: true }
response: { status: 404, type: undefined, hasBody: false, writable: true }
at register (file:///C:/Users/m/app_back/controllers/auth_controller.ts:9:22)
at async dispatch (https://deno.land/x/oak#v9.0.1/middleware.ts:41:7)
at async dispatch (https://deno.land/x/oak#v9.0.1/middleware.ts:41:7)
at async dispatch (https://deno.land/x/oak#v9.0.1/middleware.ts:41:7)
at async EventTarget.#handleRequest (https://deno.land/x/oak#v9.0.1/application.ts:379:9)
TypeError: Cannot read properties of undefined (reading 'name')
at register (file:///C:/Users/m/app_back/controllers/auth_controller.ts:9:22)
at async dispatch (https://deno.land/x/oak#v9.0.1/middleware.ts:41:7)
at async dispatch (https://deno.land/x/oak#v9.0.1/middleware.ts:41:7)
at async dispatch (https://deno.land/x/oak#v9.0.1/middleware.ts:41:7)
at async EventTarget.#handleRequest (https://deno.land/x/oak#v9.0.1/application.ts:379:9)
This is my auth_controller.ts file:
import {
create, verify, decode, getNumericDate, RouterContext, hashSync, compareSync
} from "../deps.ts";
import { userCollection } from "../mongo.ts";
import User from "../models/user.ts";
export class AuthController {
async register(ctx: RouterContext) {
const { value: { name, email, password } } = await ctx.request.body().value;
let user = await User.findOne({ email });
if (user) {
ctx.response.status = 422;
ctx.response.body = { message: "Email is already used" };
return;
}
const hashedPassword = hashSync(password);
user = new User({ name, email, password: hashedPassword });
await user.save();
ctx.response.status = 201;
ctx.response.body = {
id: user.id,
name: user.name,
email: user.email
};
}
async login(ctx: RouterContext) {
const { value: { email, password } } = await ctx.request.body().value;
if (!email || !password) {
ctx.response.status = 422;
ctx.response.body = { message: "Please provide email and password" };
return;
}
let user = await User.findOne({ email });
if (!user) {
ctx.response.status = 422;
ctx.response.body = { message: "Incorrect email" };
return;
}
if (!compareSync(password, user.password)) {
ctx.response.status = 422;
ctx.response.body = { message: "Incorrect password" };
return;
}
const key = await crypto.subtle.generateKey(
{ name: "HMAC", hash: "SHA-512" },
true,
["sign", "verify"],
);
const jwt = create( {
alg: "HS256",
typ: "JWT",
}, {
iss: user.email,
exp: getNumericDate(
Date.now() + parseInt(Deno.env.get("JWT_EXP_DURATION") || "0"))
},
key
);
ctx.response.body = {
id: user.id,
name: user.name,
email: user.email,
jwt,
};
}
}
export default new AuthController();
What is the problem and how can I resolve it?
EDIT: I added this line to the code:
console.log( await ctx.request.body().value );
And this is the result:
{ name: "first", email: "first#one.com", password: "123" }
You are facing this issue because you are trying to access ctx.request.body().value.value.name (notice multiple value porperties). You could change line 9 of your auth_controller.ts to this to fix it:
const { name, email, password } = await ctx.request.body().value;
On a side note, I also noticed few more issues with your current code.
Your JWT algorithm and generated secret key encryption algorithm should match
So either change your hash encryption on line 47 to SHA-256 or your JWT algorithm on line 53 to HS512.
You don't need to pass current date to getNumericDate function
This helper function already does this job for you, all you need to pass here is the time period (in seconds) when you want your token to expire. In your case it would be:
getNumericDate(Deno.env.get("JWT_EXP_DURATION") || 0)}

why setup() doesnt work "Cannot read property 'username' of undefined "

when i use toLogin in methods it works, but use in setup and return(in this code - test) i get error Cannot read property 'username' of undefined doesnt work, why?
export default {
name: "Login",
setup() {
const username = ref("");
const password = ref("");
const router = useRouter();
const test = async () => {
console.log(this.username.value)
}
return {
username,
password,
router,
test
};
},
methods: {
async toLogin() {
const rep = await axios.post("login", {
username: this.username,
password: this.password,
});
console.log(rep)
//await this.router.push("/successlogin");
},
},
};
You don't need this. Instead, just use console.log(username.value). username is already captured in scope for the test function to use.
The docs here https://v3.vuejs.org/guide/composition-api-introduction.html#setup-component-option contain this example:
setup (props) {
const repositories = ref([])
const getUserRepositories = async () => {
repositories.value = await fetchUserRepositories(props.user)
}
return {
repositories,
getUserRepositories
}
}

User authentication error using express not working

So I'm having trouble authenticating a user login using express for the backend. If I do a simple res.send I could get a response in postman. but if I do a check if the user and password check and generate a token if says error 401 invalid usernames and password. mind the tokens work for register and update profiles. I also attached the user schema.
Simple Approach
const authUser = asyncHandler(async (req, res) => {
const { email, password } = req.body
res.send({ email, password })
})
When I try to check and generate a token
const authUser = asyncHandler(async (req, res) => {
const { email, password } = req.body
const user = await User.findOne({ email })
if (user && (await user.matchPassword(password))) {
res.json({
_id: user._id,
name: user.name,
email: user.email,
isAdmin: user.isAdmin,
token: generateToken(user._id),
})
} else {
res.status(401)
throw new Error('Invalid Email or password')
}
})
Also here's my user schema using mongoose and added a function to check password since in the database it's encrypted using bcrypt.js
import mongoose from 'mongoose'
import bcrypt from 'bcryptjs'
const userSchema = mongoose.Schema(
{
name: {
type: String,
required: true,
},
email: {
type: String,
required: true,
unique: true,
},
password: {
type: String,
required: true,
},
isAdmin: {
type: Boolean,
required: true,
default: false,
},
},
{
timestamps: true,
}
)
userSchema.methods.matchPassword = async function (enteredPassword) {
return await bcrypt.compare(enteredPassword, this.password)
}
const User = mongoose.model('User', userSchema)
export default User
You see, your code is absolutely correct, but only if you have saved the user correctly in your model at the first. In addition, the password used during storage is hashed in the same way and from the same module.
Now you have to change your checking terms because there may not really be a user with this email in the database to match your password and return true value.
import mongoose from 'mongoose'
import bcrypt from 'bcryptjs'
const userSchema = mongoose.Schema(
{
name: {
type: String,
required: true,
},
email: {
type: String,
required: true,
unique: true,
},
password: {
type: String,
required: true,
},
isAdmin: {
type: Boolean,
required: true,
default: false,
},
},
{
timestamps: true,
}
)
userSchema.pre('save', function (next) {
if (this.password) {
bcrypt.hash(this.password, bcrypt.genSaltSync(15), (err, hash) => {
if (err) console.log(err);
this.password = hash;
next();
});
}
}
userSchema.methods.Save = async (data) => {
let model=new User(data)
await model.save();
}
userSchema.methods.matchPassword = async function (enteredPassword) {
return await bcrypt.compare(enteredPassword, this.password)
}
const User = mongoose.model('User', userSchema)
export default User
and update this code:
const authUser = asyncHandler(async (req, res) => {
const { email, password } = req.body
const user = await User.findOne({ email })
if (user) {
if(await user.matchPassword(password)) {
res.json({
_id: user._id,
name: user.name,
email: user.email,
isAdmin: user.isAdmin,
token: generateToken(user._id),
})
} else {
res.status(401)
throw new Error('Invalid password')
}
} else {
res.status(401)
throw new Error('Invalid Email')
}
})
Now you know which part of the job is the problem. No user or no wrist password

stateless session api request

I am building a simple app that uses JWT for authentication. But I keeps on getting the error saying the route I GET to require a call back function.
What do I expect?
I should be getting the current user's data back.
What do I actually get?
Error: Route.get() requires a callback function but got a [object Object]
Route:
const authenticate = require("../middlewares/authenticate");
const usersController = require("../controllers").users;
app.get("/users/me", authenticate, usersController.getMe);
Model:
"use strict";
const jwt = require("jsonwebtoken");
module.exports = (sequelize, DataTypes) => {
var User = sequelize.define(
"User",
{
email: DataTypes.STRING,
password: DataTypes.STRING
},
{
classMethods: {
associate: function(models) {
// associations can be defined here
},
findByToken: function(token) {
const User = this;
let decoded;
try {
decoded = jwt.verify(token, "leogoesger");
} catch (e) {
console.log(e);
}
return User.find({ where: { email: decoded.email } });
}
}
}
);
return User;
};
Middleware:
const { User } = require("../models/user");
const authenticate = (req, res, next) => {
console.log("called here");
const token = req.header("x-auth");
User.findByToken(token)
.then(user => {
if (!user) {
}
req.user = user;
req.token = token;
next();
})
.catch(e => {
res.status(401).send(e);
});
};
module.exports = { authenticate };
Controller:
module.exports = {
getMe(req, res) {
res.status(200).send({ message: "hello" });
}
};
Your authenticate module exports an object, yet you do this:
const authenticate = require("../middlewares/authenticate");
which means your const authenticate is an object, not your function. Change that to this:
const authenticate = require("../middlewares/authenticate").authenticate;
Or, change the module to export the function directly instead of exporting an object with the function in it.