Firebase Admin SDK roles - node.js

I am setting up the Firebase Admin SDK in a NodeJS, Express API.
I have added an endpoint that allows me to create a user -
route.post('/create', (req, res) => {
const { email, password } = req.body
fbseAdmin.auth().createUser({ email, password })
.then((userRecord) => {
res.status(200).json({ userRecord })
})
})
What I would like to do however is ensure a user has roles so I can provide Authorisation checks on some services.
I do not understand how I can achieve this though? I was thinking perhaps I add an entry to the realtime database, something like -
users/uid/roles/<role name>: true
However I am not sure if I missing something. Also, if this is the case and I do need to do this, would I do this something like -
route.post('/create', (req, res) => {
const { email, password } = req.body
fbseAdmin.auth().createUser({ email, password })
.then((userRecord) => {
fbseAdmin.database()
.ref('users')
.child(`${userRecord.uid}/roles`)
.set({
admin: true
})
res.status(200).json({ userRecord })
})
})
This seems a little brittle to say the least.
Also, as this entry isn't part of the user object, I would need to look up in the realtime db everytime I want to verify this? Is that correct?

You should look at how to set a custom claim against a user.
route.post('/create', (req, res) => {
const { email, password } = req.body
fbseAdmin.auth().createUser({ email, password })
.then((userRecord) => {
fbseAdmin.auth().setCustomUserClaims(userRecord.uid, { admin: true })
.then(() => {
res.status(200).json({ userRecord })
})
})
})

Related

Cannot save User in session data

I am creating a MERN-dashboard, with a login, registration & a dashboard where only logged in Users have access to. Now I've managed to get the user registration and login working, however I seem to be missing something when it comes to saving the User in the express-session.
This is what I use for the login
app.post('/login', async (req, res) => {
const username = req.body.username;
const password = req.body.password;
const newUser = await User.findOne( {
username: username,
})
if (newUser) {
bcrypt.compare(password, newUser.get("passwd"), (error, result) => {
if (result) {
console.log(newUser)
req.session.user = newUser
req.session.loggedIn = true
res.send({newUser, message: 'Successful Login!'})
} else {
res.send({message: 'Wrong password!'})
}
})
} else {
res.send({message: 'User not found.'})
}
})
And this is how my frontend is checking if a User is logged in
app.get('/login', (req, res) => {
if (req.session.user) {
res.send({loggedIn: true, user: req.session.user})
} else {
res.send({loggedIn: false})
}
})
Now, if I log myself in the POST request signals me, that all is fine. However if I reload the page, the GET request tells me I am not logged in.
I have tried reading several articles about express-session but did not manage to find the solution to my problem.
Thank you in advance.

add basic auth to express rest api

so I have rest api where i store user data in mongoDB, I want to add basic auth to my api but I'm stuck, I want to check if user is authorised on some paths, for example on /update, if user is auth perfom request, if not send that user is not authorized
my code where I store user is db
const addUser = async (req, res) => {
const checknick = await User.find({ nickname: req.body.nickname }) //checks if user exists with nickname
if (checknick.length !== 0) {
return res.send({
message: 'user already exists, please use another nickname',
})
}
const secretInfo = await hash(req.body.password).catch((err) =>
res.send('password is required!')
)
const user = new User({
name: req.body.name,
surname: req.body.surname,
nickname: req.body.nickname,
password: secretInfo.password,
salt: secretInfo.salt,
})
user.save((err, result) => {
if (err) {
return res.send(err)
}
res.send('user added sucesessfully')
})
}
and where I verify user
const verify = async (req, res) => {
const user = await User.findOne({ nickname: req.body.nickname })
if (!user) {
return
}
const { password } = await hash(req.body.password, user.salt).catch((err) =>
res.send('password is required')
)
const verifiedUser = await User.findOne({
nickname: req.body.nickname,
password: password,
})
if (!verifiedUser) {
return false
}
return true
}
and finally login logic
const login = async (req, res) => {
const access = await verify(req, res)
// console.log(req.headers)
if (access) {
res.send('logged in')
console.log(req.headers)
return
}
return res.status(401).send('failed to login')
}
everything works but I want to use authorizatuon header to send user and password information
This is how to restrict a route add this middleware function before the
route you want to restrict like this:
app.post("/update", restrictTo("admin"));
Every user must have a role to authorize. here I am handling error with a global error handler but you can handle error another way:
exports.restrictTo = (...roles) => {
return (req, res, next) => {
if (!roles.includes(req.user.role))
return next(
new AppError('You dont have permission to do this action', 403)
);
next();
};
};

api call getting affected by another api call's validation

not really sure if my title is correct but my problem is that I have this reset password token checker in my api that seems to get affected by another api that finds a specific user, this api has user validation.
Here is what they look like:
//get specific user
router.get('/:id', validateToken, async (req, res) => {
const id = req.params.id
const user = await User.findByPk(id);
res.json(user);
});
//reset-password token check
router.get('/reset-pass', async (req, res) => {
await User.findOne({
where: {
resetPasswordToken: req.body.resetPasswordToken,
resetPasswordExpires: {
[Op.gt]: Date.now()
}
}
}).then(user => {
if(!user) {
res.status(401).json({ error: 'Password reset link is invalid or has expired.'})
} else {
res.status(200).send({
username: user.username,
message: 'Password reset link Ok!'
});
}
});
});
then here is the validateToken
const validateToken = (req, res, next) => {
const accessToken = req.cookies['access-token'];
if (!accessToken)
return res.status(401).json({error: 'User not authenticated!'});
try {
const validToken = verify(accessToken, JWT_SECRET)
req.user = validToken;
if(validToken) {
req.authenticated = true;
return next();
}
} catch(err) {
res.clearCookie('access-token')
return res.status(400).json({error: err}).redirect('/');
}
};
when I comment out the get specific user api the reset password token check works. If I remove validateToken it returns null instead of giving me the username and message.
One of the things I notice is the route param "/:id", that means that literally everything would be processed by get specific user because all routes start with "/", only use params in routes with a prefix like "/user/:id" that way only the routes that starts with "/user" will execute that code.
Change your code to:
//get specific user
router.get('/user/:id', validateToken, async (req, res) => {
const id = req.params.id
const user = await User.findByPk(id);
res.json(user);
});

req.users undefined react express

so im getting an undefined response when fetching my api and i really dont know why
this is the function calls in the component
const init = usersId => {
getUser(usersId).then(data => {
if (data.error) {
setValues({ ...values, error: data.error });
} else {
// populate the state
setValues({
...values,
username: data.username,
email: data.email,
formData: new FormData()
});
}
});
};
this is the api call in react
export const getUser = usersId => {
console.log('ok')
console.log(usersId)
return fetch(`${API}/users/${usersId}`, {
method: 'GET'
})
.then(response => {
return response.json();
})
.catch(err => console.log(err));
};
at this point im getting the user id correctly but when the fetch is running i get an error that i cant read property of undefined so, there is the express server endpoint
router.get('/users/:usersId',
read_);
and here is the controller
userCtrl.read_ = (req, res) => {
console.log(req.users)
console.log('test')
return res.json(req.users);
};
i really dont know what im doing wrong at this point
You can't get req.user, cos you're not sending req.user.
You are only sending userId and you can only get it via req.params
like this
req.params.userId
What you want to do is use the userId to get the associated user from your DB
if you want req.user you'll have to find user from id.
you can get id by req.params.userId
after getting userdata from database assign user object to req.user
like this: req.user = user;
then you can access req.user

Delete item from mongoDB from client side

I've created a React app where you can post vinyls you have in your collection. Now I've implemented a button that is able to remove the selected item from the DOM but I also want the specific item to beremoved from the database. I'm using node with mongoose and that's (for now) my delete route:
vinylsRouter.delete('/:id', (req, res) => {
const id = req.params.id
Vinyl.findByIdAndDelete(id)
.then((deletedVinyl) => {
console.log(deletedVinyl)
})
.catch((error) => {
res.status(500).send(error);
})
});
I also tried to store the id of the specific vinyl _id into a variable and then delete it. So I also created a get route to try to get the _id of the vinyl.
vinylsRouter.get('/:id', authenticateJWT, (req, res) => {
const id = req.params.id;
Vinyl.findById(id, { __v: 0, updatedAt: 0, createdAt: 0 })
.then((user) => {
res.send(user)
})
.catch((error) => {
res.status(500).send(error)
})
});
But now I don't know how to code in the client side to make that when an user clicks in the delete button, it sends something to get the id of the vinyl and then delete it.
First put some response when the delete works:
vinylsRouter.delete('/:id', (req, res) => {
const id = req.params.id
Vinyl.findByIdAndDelete(id)
.then((deletedVinyl) => {
res.status(200).send(deletedVinyl);
})
.catch((error) => {
res.status(500).send(error);
})
});
If are you trying to do a API You can use express/nodejs, and do res.status(200).json({message: "my message"}).
Second you can use a library like axios:
axios.delete(`http://localhost:xyz/vynils/`, { id })
.then(res => {
console.log(res);
console.log(res.data);
})
https://www.digitalocean.com/community/tutorials/react-axios-react
And send for the server when the users click in the delete button.
You can use postman to test your delete endpoint before you use this on client-side (frontend), remember select delete in the dropbox and put a auth token (JWT) in the Authorization tab, or you can remove, only for test the auth middleware:
Say me if is this what you want to do.
app.delete('/product/:id', async (req, res) => {
const id = req.params.id;
const query = { _id: ObjectId(id) };
const result = await productCollection.deleteOne(query);
res.send(result);
})

Resources