Firebase function
I am trying to set my user role to admin using a callable function:
export const addAdminRole = functions.https.onCall(async (data, context) => {
admin.auth().setCustomUserClaims(data.uid, {
admin: true,
seller: false,
});
});
Cient
And here is how I am calling the function on the client:
const register = (email: string, password: string) => {
createUserWithEmailAndPassword(auth, email, password)
.then((userCredential) => {
// Signed in
const user = userCredential.user;
const addAdminRole = httpsCallable(functions, "addAdminRole");
addAdminRole({ email: user.email, uid: user.uid })
.then((result) => {
console.log(result);
})
.catch((error) => console.log(error));
history.push(`/home/${user.uid}`);
})
.catch((error) => {
const errorCode = error.code;
const errorMessage = error.message;
// ..
});
};
The user is created but, my Admin role is not added
The problem may come from the fact that you don't correctly handle the promise returned by the setCustomUserClaims() method in your Cloud Function and therefore the Cloud Function platform may clean up you CF before it reaches its terminating state. Correctly managing the life-cycle of your Cloud Function is key, as explained here in the doc.
The following should solve the problem:
export const addAdminRole = functions.https.onCall(async (data, context) => {
try {
await admin.auth().setCustomUserClaims(data.uid, {
admin: true,
seller: false,
});
return {result: "Success"}
} catch (error) {
// See https://firebase.google.com/docs/functions/callable#handle_errors
}
});
In addition, you can refactor your front-end code as follows to correctly chain the promises:
const register = (email: string, password: string) => {
createUserWithEmailAndPassword(auth, email, password)
.then((userCredential) => {
// Signed in
const user = userCredential.user;
const addAdminRole = httpsCallable(functions, "addAdminRole");
return addAdminRole({ email: user.email, uid: user.uid });
})
.then((result) => {
console.log(result);
history.push(`/home/${user.uid}`);
})
.catch((error) => {
const errorCode = error.code;
const errorMessage = error.message;
// ..
});
};
Related
I am building a Node.js project which has an advanced folder structure for future purposes.
user.register controller:
exports.register = async (req, res) => {
try {
var isValidated = await userService.validateInDatabase(req);
if (!isValidated)
return res
.status(409)
.json({ error: "Phone number or email is already registered" });
var user = await userService.create(req);
return user
} catch (e) {
console.trace(e);
return res.status(400).json({ message: e.message });
}
};
The services file code:
exports.create = async (user) => {
const hashedPassword = passwordHash.generate(user.password);
let new_user = new User({
phoneNumber,
email,
password: hashedPassword,
});
const payload = {
id: new_user._id,
};
let token = jwt.sign(payload, keys.JWToken, { expiresIn: 31556926 });
const userData = await new_user.save();
return userData;
};
exports.validateInDatabase = async (req) => {
let check_user = await User.findOne({
$or: [{ email: req.body.email }, { phoneNumber: req.body.phoneNumber }],
});
if (check_user) return false;
return true;
};
Now, whenever I send the request from the postman it says invalid password Why is that?
I'm testing out a route in a firebase app I'm building. The route in question recieves 'shouts' which are akin to status updates. Anyway, I just integrated auth to protect this route using FBuath, but I keep getting the following error:
Firebase ID token has expired. Get a fresh ID token from your client app and try again
I've tried relogging in using valid credentials, and then instantly trying to post something via the route, but keep getting this error. Any thoughts as to why? Code as follows, and the route in question has the endpoint '/shout'. Cheers
const functions = require('firebase-functions');
const admin = require('firebase-admin')
admin.initializeApp()
const config = {
apiKey: "AIzaSyBZjz9BNwj4UDwWLoQ1SOD5hB5QcNw3qqs",
authDomain: "social-ape-21874.firebaseapp.com",
databaseURL: "https://social-ape-21874.firebaseio.com",
projectId: "social-ape-21874",
storageBucket: "social-ape-21874.appspot.com",
messagingSenderId: "323044904203",
appId: "1:323044904203:web:edcbc619169a2087f8e60e",
measurementId: "G-T34PXDM1X7"
}
admin.initializeApp
const express = require('express')
const app = express()
const firebase = require('firebase')
firebase.initializeApp(config)
const db = admin.firestore()
app.get('/shouts', (req,res) => {
db
.collection('shouts')
.orderBy('createdAt', 'desc') //returns shouts in order in which they were made
.get()
.then((data) => {
let shouts = []
data.forEach((doc) => {
shouts.push({
shoutId: doc.id,
body: doc.data().body,
userHandle: doc.data().userHandle,
createdAt: doc.data().createdAt
})
})
return res.json(shouts)
})
.catch((err) => console.error(err))
})
const FBauth = (req,res,next) => {
let idToken
if(req.headers.authorization && req.headers.authorization.startsWith('Bearer ')){
idToken = req.headers.authorization.split('Bearer ')[1]
}else{
console.error('No token found')
return res.status(403).json({error: 'Unauthorized'})
}
//verify that this token was issued by our application
admin.auth().verifyIdToken(idToken)
.then(decodedToken => {
req.user = decodedToken
return db.collection('users')
.where('userId', '==', req.user.uid)
.limit(1) //limits results to one document
.get()
})
.then(data => {
req.user.handle = data.docs[0].data().handle //data() is a function that extracts data from document
return next() //next() is a function that allows request to proceed to shout post route
})
.catch(err => {
console.error('Error while verifying token', err)
return res.status(403).json(err)
})
}
app.post('/shout', FBauth, (req,res) => {
const newShout = {
body: req.body.body,
userHandle: req.body.userHandle, //userhandle identifies who is owner of shout
createdAt: new Date().toISOString()
}
db
.collection('shouts')
.add(newShout)
.then((doc) => {
res.json({message: `document ${doc.id} created successfully`})
})
.catch((err) =>{
res.status(500).json({error: 'something went wrong'})
console.error(err)
})
})
//helper function to determine if string is empty or not
//note: .trim() removes whitespace from email field
const isEmpty = (string) => {
if (string.trim()=== '') {
return true
} else {
return false
}
}
//helper function to determine if valid email
const isEmail = (email) => {
const regEx = /^(([^<>()\[\]\\.,;:\s#"]+(\.[^<>()\[\]\\.,;:\s#"]+)*)|(".+"))#((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
if (email.match(regEx)) {
return true
} else {
return false
}
}
//Sign up route
app.post('/signup', (req,res) => {
//here we need to extract form data from request body
const newUser = {
email: req.body.email,
password: req.body.password,
confirmPassword: req.body.confirmPassword,
handle: req.body.handle,
}
let errors = {}
if(isEmpty(newUser.email)){
errors.email = 'Email must not be empty'
} else if(!isEmail(newUser.email)) {
errors.email = 'Must be a valid email address'
} //if not empty, need to check if valid email
if(isEmpty(newUser.password)){
errors.password = 'Must not be empty'
}
if(newUser.password !== newUser.confirmPassword) {
errors.confirmPassword = 'Passwords must match'
}
if(isEmpty(newUser.handle)){
errors.handle = 'Must not be empty'
}
if(Object.keys(errors).length>0) {
return res.status(400).json(errors)
}
//sign up user
let token
db.doc(`/users/${newUser.handle}`).get()
.then((doc)=> {
if(doc.exists){
return res.status(400).json({handle: 'this handle is already taken'})
} else {
return firebase
.auth()
.createUserWithEmailAndPassword(newUser.email, newUser.password)
}
})
.then(data => {
userId = data.user.uid
return data.user.getIdToken()
})
.then(token => {
token=token
const userCredentials = {
handle: newUser.handle,
email: newUser.email,
createdAt: new Date().toISOString(),
userId:userId
}
db.doc(`/users/${newUser.handle}`).set(userCredentials)
return res.status(201).json({token})
})
.then(() => {
return res.status(201).json({token})
})
.catch(err => {
console.error(err)
return res.status(500).json({error:err.code})
})
})
//token is used to access route that is protected
//login route
app.post('/login', (req,res) => {
const user = {
email: req.body.email,
password: req.body.password
}
let errors = {}
if(isEmpty(user.email)){
errors.email = 'Must not be empty'
}
if(isEmpty(user.password)){
errors.password = 'Must not be empty'
}
if(Object.keys(errors).length >0) {
return res.status(400).json(errors)
}
firebase.auth().signInWithEmailAndPassword(user.email, user.password)
.then(data => {
return data.user.getIdToken()
})
.then(token => {
return res.json({token})
})
.catch(err => {
console.error(err)
if(err.code ==="auth/wrong-password" ){
return res.status(403).json({general: 'Wrong credentials, please try again'})
} else
return res.status(500).json({error: err.code})
})
})
exports.api = functions.https.onRequest(app)
Hello I'm working on writing a test for my node.js API and I'm running into an issue. I'm validating if an email exists inside of my code using "const = emailCount". If it does exists it returns an error JSON. If it does NOT exist it returns a success JSON. However I'm not sure how to mock the internal constant I declared in my code.
Here is the code:
async function registerUser(req, res) {
// Request
const email = req.body.email;
const password = req.body.password;
const firstName = req.body.firstName;
const lastName = req.body.lastName;
const inviteCode = req.body.inviteCode;
let errMessage = [];
if (!firstName) {
errMessage.push("first Name Required")
}
if (!lastName) {
errMessage.push("last Name Required")
}
if (!inviteCode) {
errMessage.push("inviteCode Required")
}
if (!email) {
errMessage.push("email Required")
}
if (!password) {
errMessage.push("password Required")
}
if (errMessage.length > 0) {
res.json({ code: "422", message: errMessage })
}
const accessToken = jwt.sign({
email: email,
firstName: firstName,
lastName: lastName
}, config.jwtSecret);
const emailCount = await db.doesEmailExists(email)
if (emailCount.doesEmailExists > 0) {
res.json({ Errors: "Account already exists" })
} else {
db.createUser({
username: email,
hashedPassword: password,
firstName: firstName,
lastName: lastName,
}).then(data => {
res.json({
id: data.insertId,
firstName: firstName,
lastName: lastName,
token: accessToken,
role: 'user'
})
}).catch(err => res.json({ Error: err }))
}
}
Here is my test code
test('POST /user/register', async () => {
//use super test to send post method with json payload of newUser
const res = await agent.post('/user/register').send(newUser);
expect(res.statusCode).toEqual(200)
expect(res.body).toHaveProperty('Errors') || expect(res.body).toHaveProperty('token');
})
Ultimately I want to change the value of emailCount within my test if possible to test for different responses if there is a user and if there is NOT a user.
You should not mock your code, but rather your dependencies and db is exactly that.
For example you can write your test scenario like this:
const db = require('./path/to/db.js');
// auto-create mock
jest.mock('./path/to/db.js')
describe('POST /user/register', () => {
describe('when email Exists'), () => {
// defining the "res" object here
// will allow you to execute the request one
// and separate the expectations in different
// test cases, which will provide better visibility
// on what exactly have failed (in the future)
let res;
beforeAll(async () => {
db.doesEmailExists.mockResolvedValue({
doesEmailExists: 789
});
res = await agent.post('/user/register').send(newUser);
});
it('should probably return something more than 200', () => {
expect(res.statusCode).toBeGreaterThanOrEqual(200)
});
it('should return Error in response Body', () => {
expect(res.body).toHaveProperty('Errors')
});
});
describe('when email DOES NOT Exists'), () => {
let res;
beforeAll(async () => {
db.doesEmailExists.mockResolvedValue({
doesEmailExists: 0
});
res = await agent.post('/user/register').send(newUser);
});
it('should probably return statusCode 200', () => {
expect(res.statusCode).toEqual(200)
});
it('should return token', () => {
expect(res.body).toHaveProperty('token')
});
});
});
Note: you'll also need to mock the return value of db.createUser as the auto-mock will generate a jest.fn() which returns undefined
I'm trying to run a test using Jest for this Nodejs API. Although everything works when I run the app, it's failing and returning the error
TypeError: Cannot read property 'store' of undefined
The test code:
const request = require('supertest')
const server = require('../../server')
const { User } = require('../../app/models/User')
describe('User', () => {
test('should create user', async () => {
const user = await User.store({
name: 'Marcelo',
email: 'marcelo#vuttr.com',
password: '123456'
})
const response = await request(server)
.post('/user')
.send({
name: user.name,
email: user.email,
password: user.password
})
expect(response.status).toBe(200)
})
})
The controller:
const User = require('../models/User')
class UserController {
async store (req, res) {
const { email } = req.body
if (await User.findOne({ email })) {
return res.status(400).json({ error: 'User already exists' })
}
const user = await User.create(req.body)
return res.json(user)
}
}
module.exports = new UserController()
For more details, I share this project in github.
try this code
the Test Code
const request = require('supertest')
const server = require('../../server')
const { User } = require('../../app/models/User')
const { UserController } = require('../../UserController')
describe('User', () => {
test('should create user', async () => {
const user = await UserController({
name: 'Marcelo',
email: 'marcelo#vuttr.com',
password: '123456'
})
const response = await request(server)
.post('/user')
.send({
name: user.name,
email: user.email,
password: user.password
})
expect(response.status).toBe(200)
})
})
The controller:
const User = require('../models/User')
const UserController = async(req, res) => {
const { email } = req.body
if (await User.findOne({ email })) {
return res.status(400).json({ error: 'User already exists' })
}
const user = await User.create(req.body);
return res.status(201).json(user);
}
module.exports = UserController;
I'm having problem with bcrypt-nodejs' compare function.
The compare function is returning the false value even the password is the right one.
I've tried everything I could and I don't know the what is wrong with my code.
My Folder Structure
src
-config
-config.js
-controller
-AuthenticationController.js
-models
-index.js
-User.js
-policies
-AuthenticationControllerPolicy.js
app.js
routes.js
package.json
I think the problem is with the User.js in models folder.
User.js
const Promise = require('bluebird')
const bcrypt = Promise.promisifyAll(require('bcrypt-nodejs'))
function hashPassword (user, options) {
const SALT_FACTOR = 8
if (!user.changed('password')) {
return
}
return bcrypt
.genSaltAsync(SALT_FACTOR)
.then(salt => bcrypt.hashAsync(user.password, salt, null))
.then(hash => {
user.setDataValue('password', hash)
})
}
module.exports = (sequelize, DataTypes) => {
const User = sequelize.define('User', {
email: {
type: DataTypes.STRING,
unique: true
},
password: DataTypes.STRING
}, {
hooks: {
beforeCreate: hashPassword,
beforeUpdate: hashPassword,
beforeSave: hashPassword
}
})
User.prototype.comparePassword = function (password) {
return bcrypt.compareAsync(password, this.password)
}
User.associate = function (models) {
}
return User
}
AuthenticationController.js
const {User} = require('../models')
const jwt = require('jsonwebtoken')
const config = require('../config/config')
function jwtSignUser (user) {
const ONE_WEEK = 60 * 60 * 24 * 7
return jwt.sign(user, config.authentication.jwtSecret, {
expiresIn: ONE_WEEK
})
}
module.exports = {
async register (req, res) {
try {
const user = await User.create(req.body)
const userJson = user.toJSON()
res.send({
user: userJson
})
} catch (err) {
res.status(400).send({
error: 'This email account is already in use.'
})
}
},
async login (req, res) {
try {
const {email, password} = req.body
const user = await User.findOne({
where: {
email: email
}
})
console.log('user BEFORE', user)
if (!user) {
console.log('!user')
return res.status(403).send({
error: 'The login information was incorrect'
})
}
console.log('user AFTER', user)
const isPasswordValid = await user.comparePassword(password)
console.log('isPasswordValid BEFORE : ', isPasswordValid)
if (!isPasswordValid) {
console.log('isPasswordValid AFTER : ', isPasswordValid)
return res.status(403).send({
error: 'The login information was incorrect'
})
}
const userJson = user.toJSON()
res.send({
user: userJson,
token: jwtSignUser(userJson)
})
} catch (err) {
res.status(500).send({
error: 'An error has occured trying to log in'
})
}
}
}
route.js
const AuthenticationController = require('./controller/AuthenticationController')
const AuthenticationControllerPolicy = require('./policies/AuthenticationControllerPolicy')
module.exports = (app) => {
app.post('/register',
AuthenticationControllerPolicy.register,
AuthenticationController.register)
app.post('/login',
AuthenticationController.login)
}
You can also check the repo if you want.
GitHubRepo
The usage of bcrypt-nodejs appears to be correct. I would verify that both the password coming in and the hash in the database are what you expect them to be (particularly inside the comparePassword function) to rule out if it's a data issue or not.