how to pass data from login api to profile api? - node.js

I am a beginner in Nodejs and Mongo dB. I am creating a signup/login system with authentication and also I have to make a profile page. I have built a login API here is the code
router.post('/login',async(req,res)=>{
try {
const email = req.body.email;
const password = req.body.password;
const usermail = await UserSignup.findOne({email:email});
const isMatch = bcrypt.compare(password,usermail.password);
if(isMatch){
const token = generatetoken(usermail._id)
res.setHeader('JWT_TOKEN',token);
// res.status(201).json({
// message:"User loged in",
// userdetail:{
// name:usermail.name,
// email:usermail.email,
// Phone:usermail.mobilenumber,
// Id:usermail._Id
// }
// })
res.redirect('/api/user/welcome',{useremail:email})
}
else{
res.status(400).send("Invalid Credentials")
}
} catch (error) {
console.log(error);
res.status(500).send("Internal server error")
}
})
I have redirected the User after logging in to the welcome page.
so In the welcome page I have to display user name email. how can I do that?
router.get('/welcome',auth,(req,res)=>{
})
what should I write in the welcome API to get information about the logged user?
I have passed information of the user in 2nd argument of redirect is it correct or not because I can not able to access it in the welcome route.

You can use query strings. Modify you login route to redirect to:
res.redirect('/api/user/welcome?email=' + email)
And that you can access the query in the welcome route like so:
let email = req.query.email;

Related

Appwrite authentication - "createJWT is not a function" error

I am trying to build a Node.js server-side signup function for user authentication. The data for the user is being sent via "req.body" and the authentication database is provided by Appwrite.
The signup function should:
Create a user with the credentials provided in the request body.
Return the user details, such as the username and email.
Generate and return a token (cookie/JWT)
I am encountering issues with the Appwrite documentation and would appreciate guidance on building this function.
When trying to POST a new user using the Users API, an error of
createJWT is not a function
is produced, and when using the Account API, an error of
User (role: guests) missing scope (account)
is produced.
Here's the code I have:
const sdk = require('node-appwrite')
const client = sdk.Client()
client
.setEndpoint(endpoint)
.setProject(projectId)
.setKey('...')
const users = sdk.Users(client)
async function signup(req, res) {
try {
const { email, username } = req.body
let { password } = req.body
password = await bcrypt.hash(password, SALT_ROUNDS)
const result = await users.createBcryptUser("unique()", email, password, username)
// Create a token
// Combine data
res.send(userWithToken)
} catch (err) {
error('Failed to signup', err)
throw new Error(err)
}
}
The Users API is intended to be used in an admin perspective rather than as a user. You can use the Account API to execute things on behalf of a user, but the JWT token is typically generated client side and passed to the server, where you can call client.setJWT().

Delete User and Logout that user from all Devices

I wanted to implement a feature in my app. Where an Admin can delete the user. So basically the delete is working fine but somehow i cannot logout the logged in user. Let me explain it more briefly, Suppose there is a User A which is currently using my app and the admin decided to remove that user from the app so they can't no longer access the features of the app. To remove the user i can call an API and delete that user but if i completely delete the user it loses all the access to the API's call coz user with the certain ID isn't available anymore and the app breaks coz the API call will fail for that deleted User. So I was wondering is there anyway to logout the user after admin deletes it.
The Frontend is on ReactJs and Backend is on NodeJs. And i am using JWT for authentication. Any help will be appreciated and if this question isn't clear enough please let me know so i can explain it more.
In backend in every protected route you should verify the token and token should contain user id or email using that you will verify the token. After deleting the user throw error with no user found and in frontend make sure if there are the error no user found then it will delete the JWT token.
What comes into my mind is to put a middleware between your requests and server. By doing so, instead of trying to log out from all devices, we will not allow any action if user does not exist; in this very example, we will prevent the user to delete a place and toast a message on the front end. I will share an example of that, but you need to tweak the code according to your needs.
Http Error Model
class HttpError extends Error {
constructor(message, errorCode) {
super(message);
this.code = errorCode;
}
}
module.exports = HttpError;
Middleware
const HttpError = require('../models/http-error');
module.exports = (req, res, next) => {
try {
// Check user if exists
User.findById(req.userData.userId).exec(function (error, user) {
if (error) {
throw new Error('Authentication failed!');
}
else {
return next();
}
});
}
catch (error) {
return next(new HttpError('Authentication failed!', 403));
}
};
Route
const express = require('express');
const router = express.Router();
const checkAuth = require('../middleware/check-auth');
router.use(checkAuth);
// Put after any routes that you want the user to be logged in
router.delete('/:placeId', placesControllers.deletePlace); //e.x.
...
module.exports = router;
E.x. controller (with MongoDB)
const deletePlace = async (req, res, next) => {
const placeId = req.params.placeId;
let foundPlace;
try {
foundPlace = await Place.findById(placeId).populate('userId').exec();
}
catch (error) {
return next(new HttpError('Could not find the place, please try again', 500));
}
// Delete place
res.status(200).json({message: 'Deleted place'});
};
FRONT END PART
import toastr from 'toastr';
....
try {
const response = await fetch(url, {method, body, headers});
const data = await response.json();
if (!response.ok) {
throw new Error(data.message);
}
}
catch(error) {
// handle the error, user not found
console.log(error.message);
toastr.error(error.message, 'Error', {
closeButton: true,
positionClass: 'toast-top-right',
timeOut: 2000,
extendedTimeOut: 1,
});
}

Node function codes to delete user by admin is not working

I am learning by building. I am building a blog CMS with Nodejs, reactjs, and mongodb.
I have two roles: users and admin. I would like admin to be able to delete any user. I wrote codes that enabled a user to delete his/her own account. How do I go about making the admin to be able to delete any user by clicking a button next to that user?
Here are my codes so far:
code for a user to delete him/her self. Once a user deletes him/her self, everything associated with the user also gets deleted. This is working fine.
//delete logic
router.delete("/:id", async (req, res) =>{
if(req.body.userId === req.params.id){//we checked if the user id matched
try{
const user = await User.findById(req.params.id)//get the user and assign it to user variable
try{
await Post.deleteMany({username: user._id})//deleting user posts once the username matches with the variable user object .username
await Comment.deleteMany({author: user._id})//delete user's comment by checking the comment author's id.
await Reply.deleteMany({author: user._id})//deletes user's replies
await User.findByIdAndDelete(req.params.id)//delete the user
res.status(200).json("User has been deleted")
} catch(err){
res.status(500).json(err) //this handles the error if there is one from the server
}
}catch(err){
res.status(404).json("User not found")
}
} else{
res.status(401).json("You can only update your account!")
}
});
How I tried to write the code for admin to be able to delete a user:
/delete a user by an admin
router.delete("/:id", async (req, res) =>{
if(req.body.userId === req.params.id){
const user = await User.findOne({username: req.body.username})
if(user && user.role === "admin"){
try{
const regUser = await User.findById(req.params.id)//get the user and assign it to user variable
try{
await Post.deleteMany({username: regUser._id})//deleting user posts once the username matches with the variable user object .username
await Comment.deleteMany({author: regUser._id})//delete user's comment by checking the comment author's id.
await Reply.deleteMany({author: regUser._id})//deletes user's replies
await User.findByIdAndDelete(req.params.id)//delete the user
res.status(200).json("User has been deleted")
} catch(err){
res.status(500).json(err) //this handles the error if there is one from the server
}
}catch(err){
res.status(404).json("User not found")
}
}else{
res.status(401).json("You do not have the permission")
}
}
});
When I tried this code on postman, it kept on loading and didn't deliver anything.
I know that I am not writing the function properly. Kindly provide me with any help to enable me achieve this. Thank you
I tried to reverse-engineer your request body of your API.
And I think it's the following:
{
body: {
userId: string
userName: string
}
params: {
id: string
}
}
So, trying to reverse engineer what each value is intended for:
the params-id obviously is just the parameter which is contained in the URL. So, that's the id of the user which you're trying to delete.
So, what are the userId and userName in your body ?
The issue of security
Judging from your code, the userName and/or userId refer to the user who's logged in and who's performing the operation. Surely, that can't be secure.
You are aware that every user can hit F12 in his webbrowser and see all in/out going requests. It's really easy to modify them and put in the ID of a different user. So, surely, you need more security than just that.
What you need is a "context" which keeps track of the logged in user. e.g. sometimes the entire user object of the logged in user is added on req.context.me.
I've searched for a tutorial that illustrates this, and found this one. It's not entirely what I meant, but it's similar. They store the userId on the req object. Making it available as req.userId.
Aside from security
Having written all that, what you were trying to do is probably the following.
router.delete("/:id", async(req, res) => {
const loggedInUser = await User.findById(req.body.userId);
if (loggedInUser && loggedInUser.role === "admin") {
try {
const regUser = await User.findById(req.params.id);
if (regUser == null) {
throw new Error("user not found");
}
await Post.deleteMany({
username: regUser._id
}) //deleting user posts once the username matches with the variable user object .username
await Comment.deleteMany({
author: regUser._id
}) //delete user's comment by checking the comment author's id.
await Reply.deleteMany({
author: regUser._id
}) //deletes user's replies
await User.findByIdAndDelete(req.params.id) //delete the user
res.status(200).json("User has been deleted")
} catch (err) {
res.status(500).json(err) //this handles the error if there is one from the server
}
} else {
res.status(401).json("You do not have the permission")
}
}
As you can see, you don't need the username.
DELETE does not support a body
Whether a DELETE can have a body or not is actually a point of discussion. Some clients/servers support it and some don't. You can find more about this here:
body is empty when parsing DELETE request with express and body-parser
Again, this means that you really shouldn't pass the logged in user through the body.

Firebase : How to send a password reset email backend with NodeJs

I'm trying to impliment the code as found here: https://firebase.google.com/docs/auth/web/manage-users#send_a_password_reset_email
var auth = firebase.auth();
var emailAddress = "user#example.com";
auth.sendPasswordResetEmail(emailAddress).then(function() {
// Email sent.
}).catch(function(error) {
// An error happened.
});
But I can't find the sendPasswordResetEmail method in firebase admin.
Anyway I can do this on the backend?
ORIGINAL JULY 2018 ANSWER:
The sendPasswordResetEmail() method is a method from the client-side auth module, and you're right, the Admin-SDK doesn't have it - or anything comparable. Most folks call this function from the front-end...
With that said, it is possible to accomplish on the backend... but you'll have to create your own functionality. I've done this sort of thing before, I'll paste some of my code from my cloud functions to help you along... should you choose to go down this road. I create my own JWT, append it to a URL, and then use NodeMailer to send them an email with that link... when they visit that link (a password reset page) they enter their new password, and then when they click the submit button I pull the JWT out of the URL and pass it to my 2nd cloud function, which validates it and then resets their password.
const functions = require('firebase-functions');
const admin = require('firebase-admin');
var jwt = require('jsonwebtoken');
admin.initializeApp()
// Email functionality
const nodemailer = require('nodemailer');
// Pull the gmail login info out of the environment variables
const gmailEmail = functions.config().gmail.email;
const gmailPassword = functions.config().gmail.password;
// Configure the nodemailer with our gmail info
const mailTransport = nodemailer.createTransport({
service: 'gmail',
auth: {
user: gmailEmail,
pass: gmailPassword,
},
});
// Called from any login page, from the Forgot Password popup
// Accepts a user ID - finds that user in the database and gets the associated email
// Sends an email to that address containing a link to reset their password
exports.forgotPassword = functions.https.onRequest( (req, res) => {
// Make a query to the database to get the /userBasicInfo table...
admin.database().ref(`userBasicInfo`).once('value').then( dataSnapshot => {
let allUsers = dataSnapshot.val() ? dataSnapshot.val() : {};
let matchingUid = '';
let emailForUser = '';
// Loop over all of the users
Object.keys(allUsers).forEach( eachUid => {
// See if their email matches
allUsers[eachUid]['idFromSis'] = allUsers[eachUid]['idFromSis'] ? allUsers[eachUid]['idFromSis'] : '';
if (allUsers[eachUid]['idFromSis'].toUpperCase() === req.body.userIdToFind.toUpperCase()) {
// console.log(`Found matching user! Uid: ${eachUid} with idFromSis: ${allUsers[eachUid]['idFromSis']}... setting this as the matchingUid`);
matchingUid = eachUid;
emailForUser = allUsers[eachUid]['email'] ? allUsers[eachUid]['email'] : '';
}
})
// After loop, see if we found the matching user, and make sure they have an email address
if (matchingUid === '' || emailForUser == '') {
// Nothing found, send a failure response
res.send(false);
} else {
// Send an email to this email address containing the link to reset their password
// We need to generate a token for this user - expires in 1 hour = 60 minutes = 3600 seconds
jwt.sign({ uid: matchingUid }, functions.config().jwt.secret, { expiresIn: 60 * 60 }, (errorCreatingToken, tokenToSend) => {
if (errorCreatingToken) {
console.log('Error creating user token:');
console.log(errorCreatingToken);
let objToReplyWith = {
message: 'Error creating token for email. Please contact an adminstrator.'
}
res.json(objToReplyWith);
} else {
// Send token to user in email
// Initialize the mailOptions variable
const mailOptions = {
from: gmailEmail,
to: emailForUser,
};
// Building Email message.
mailOptions.subject = 'LMS Password Reset';
mailOptions.text = `
Dear ${req.body.userIdToFind.toUpperCase()},
The <system> at <company> has received a "Forgot Password" request for your account.
Please visit the following site to reset your password:
https://project.firebaseapp.com/home/reset-password-by-token/${tokenToSend}
If you have additional problems logging into LMS, please contact an adminstrator.
Sincerely,
<company>
`;
// Actually send the email, we need to reply with JSON
mailTransport.sendMail(mailOptions).then( () => {
// Successfully sent email
let objToReplyWith = {
message: 'An email has been sent to your email address containing a link to reset your password.'
}
res.json(objToReplyWith);
}).catch( err => {
// Failed to send email
console.log('There was an error while sending the email:');
console.log(err);
let objToReplyWith = {
message: 'Error sending password reset email. Please contact an adminstrator.'
}
res.json(objToReplyWith);
});
}
})
}
}).catch( err => {
console.log('Error finding all users in database:');
console.log(err);
res.send(false);
})
});
// Called when the unauthenticated user tries to reset their password from the reset-password-by-token page
// User received an email with a link to the reset-password-by-token/TOKEN-HERE page, with a valid token
// We need to validate that token, and if valid - reset the password
exports.forgotPasswordReset = functions.https.onRequest( (req, res) => {
// Look at the accessToken provided in the request, and have JWT verify whether it's valid or not
jwt.verify(req.body.accessToken, functions.config().jwt.secret, (errorDecodingToken, decodedToken) => {
if (errorDecodingToken) {
console.error('Error while verifying JWT token:');
console.log(error);
res.send(false);
}
// Token was valid, pull the UID out of the token for the user making this request
let requestorUid = decodedToken.uid;
admin.auth().updateUser(requestorUid, {
password: req.body.newPassword
}).then( userRecord => {
// Successfully updated password
let objToReplyWith = {
message: 'Successfully reset password'
}
res.json(objToReplyWith);
}).catch( error => {
console.log("Error updating password for user:");
console.log(error)
res.send(false);
});
});
});
JANUARY 2019 EDIT:
The Admin SDK now has some methods that allow you to generate a "password reset link" that will direct people to the built-in Firebase password reset page. This isn't exactly the solution OP was looking for, but it's close. You will still have to build and send the email, as my original answer shows, but you don't have to do everything else... i.e.: generate a JWT, build a page in your app to handle the JWT, and another backend function to actually reset the password.
Check out the docs on the email action links, specifically the "Generate password reset email link" section.
// Admin SDK API to generate the password reset link.
const email = 'user#example.com';
admin.auth().generatePasswordResetLink(email, actionCodeSettings)
.then((link) => {
// Do stuff with link here
// Construct password reset email template, embed the link and send
// using custom SMTP server
})
.catch((error) => {
// Some error occurred.
});
Full disclosure - I haven't actually used any of those functions, and I'm a little concerned that the page in question refers a lot to mobile apps - so you might have to pass it the mobile app config.
const actionCodeSettings = {
// URL you want to redirect back to. The domain (www.example.com) for
// this URL must be whitelisted in the Firebase Console.
url: 'https://www.example.com/checkout?cartId=1234',
// This must be true for email link sign-in.
handleCodeInApp: true,
iOS: {
bundleId: 'com.example.ios'
},
android: {
packageName: 'com.example.android',
installApp: true,
minimumVersion: '12'
},
// FDL custom domain.
dynamicLinkDomain: 'coolapp.page.link'
};
On the other hand, the page also says these features provide the ability to:
Ability to customize how the link is to be opened, through a mobile
app or a browser, and how to pass additional state information, etc.
Which sounds promising, allowing it to open in the browser... but if you are developing for web - and the function errors out when not provided iOS/Android information... then I'm afraid you'll have to do it the old fashioned approach and create your own implementation... but I'm leaning towards this .generatePasswordResetLink should work now.
Unlike other client features which work on the user object (e.g. sending a verification mail), sending a password reset email works on the auth module and doesn't require a logged-in user. Therefore, you can simply use the client library from the backend (provided you have the user's email address):
const firebase = require('firebase');
const test_email = "test#test.com";
const config = {} // TODO: fill
const app = firebase.initializeApp(config);
app.auth().sendPasswordResetEmail(test_email).then(() => {
console.log('email sent!');
}).catch(function(error) {
// An error happened.
});
public void resetpasswoord()
{
string emailaddress = resest_email.text;
FirebaseAuth.DefaultInstance.SendPasswordResetEmailAsync(emailaddress).ContinueWith((task =>
{
if (task.IsCompleted)
{
Debug.Log("Email sent.");
}
if (task.IsFaulted)
{
Firebase.FirebaseException e =
task.Exception.Flatten().InnerExceptions[0] as Firebase.FirebaseException;
GetErrorMessage((AuthError)e.ErrorCode);
errorpanal = true;
return;
}
}));
}
void GetErrorMessage(AuthError errorcode)
{
string msg = "";
msg = errorcode.ToString();
print(msg);
errorpanal = true;
ErrorText.text = msg;
}

do something 'before' login in loopback

I am pretty new to loopback and here is what I am doing:
I am using standard login route provided by the loopback to log in the users - extended base Users to my own model say orgadmin.
With prebuilt route /api/orgadmin/login, I can easily login.
Now, I have a flag in orgadmins say 'status' which can be either 'active' or 'inactive' based on which I have to defer user login.
I was thinking something with remote hooks like beforeRemote as below but it doesn't work:
//this file is in the boot directory
module.exports = function(orgadmin) {
orgadmin.beforeRemote('login', function(context, user, next) {
console.log(user)
// context.args.data.date = Date.now();
// context.args.data.publisherId = context.req.accessToken.userId;
next();
});
};
So what is the best way to accomplish this?
The user attribute will only be available if the request is coming with a valid access token. The attribute is unused for unauthenticated requests, which login is.
Here's a possible alternative:
module.exports = (OrgAdmin) => {
OrgAdmin.on('dataSourceAttached', () => {
const { login } = OrgAdmin;
OrgAdmin.login = async (credentials, include) => {
const accessToken = await login.call(OrgAdmin, credentials, include);
const orgAdmin = await OrgAdmin.findById(accessToken.userId);
if (orgAdmin.status !== 'active') {
OrgAdmin.logout(accessToken);
const err = new Error('Your account has not been activated');
err.code = 'NOT_ACTIVE_USER';
err.statusCode = 403;
throw err
}
return accessToken;
};
});
};
The above code overrides the login method and does the following:
Login the user, using loopback's built-in login
Take the response of login, which is an access token, and use it to get the user.
If the user is active, return the access token, satisfying the expected successful response of login.
If the user is not active, remove the access token that was created (which is what logout does), and throw an error.

Resources