Material that i used
windows10
Firebase (fire-store)
postman
Javascript , express
situation
I tried to sign up to firebase and make data on the firestore. but the internal error occurs. Does someone confirm my code?
index.js
// Signup route
app.post('/signup', (req,res) => {
const newUser = {
email: req.body.email,
password: req.body.password,
confirmPassward: req.body.confirmPassword,
handle: req.body.handle
};
//TODO validate data
let token, userId;
db.doc(`/users/${newUser.handle}`)
.get()
.then(doc => {
if(doc.exists) {
return res.status(400).json({ hanldle: 'this hanlde is already taken'});
}else {
return firebase()
.auth()
.createUserWithEmailAndPassword(newUser.email, newUser.password);
}
})
.then((data) => {
userId = data.user.uid;
return data.usergetIdToken()
})
.then( ( idToken ) => {
token = idToken ;
const userCredentials = {
handle: newUser.handle,
email: newUser.email,
createdAt: new Date().toISOString(),
userId
};
return db.doc(`/users/${newUser.handle}`).set(userCredentials);
})
.then(() => {
return res.status(201).json({ token });
})
.catch(err => {
if(err.code === 'auth/email=already-in-use') {
return res.status(400).json({ email: 'email is alread is used '})
} else {
return res.status(500).json({ err : err.code });
}
});
});
exports.api = functions.https.onRequest(app);
My console visual studio
! functions: The Cloud Firestore emulator is not running, so calls to Firestore will affect production.
i functions: Beginning execution of "api"
! External network resource requested!
- URL: "http://169.254.169.254/computeMetadata/v1/instance"
- Be careful, this may be a production service.
! External network resource requested!
- URL: "http://metadata.google.internal./computeMetadata/v1/instance"
- Be careful, this may be a production service.
i functions: Finished "api" in ~9s
i functions: Beginning execution of "api"
i functions: Finished "api" in ~8s
Postman
Related
I am using NodeJS, Express and plain vanilla javascript/html. Not React or anything else.
With firebase I made it to when the user registers, they will automatically be called a customer (on the server-side). As shown:
server.js
app.post('/register', (req,res) => {
let {first_name, last_name, email, uid} = req.body;
db.collection('users').doc(uid).set(req.body)
.then(data => {
res.json({
uid: req.body.uid,
first_name: req.body.first_name,
last_name: req.body.last_name,
email: req.body.email,
seller: req.body.seller
})
})
admin.auth()
.setCustomUserClaims(uid, {
type: "customer",
})
.then(() => console.log('done'))
})
But now, I would like to make this route to where it will redirect if the type is a customer.
if(idToken.claim.type === 'customer') {redirect('/') }
app.get('/seller', (req,res) => {
res.sendFile(path.join(staticPath, "seller.html"));
})
So I thought, what if I were to get the Token from the user and the type as soon as they log in, and send it back to the client. This will work.
login.js
firebase.auth().currentUser.getIdTokenResult()
.then((idTokenResult) => {
fetch('/getMyClaims', {
method: 'post',
headers: {'Content-Type':'application/json'},
body: JSON.stringify({uid: user.uid,
idToken: idTokenResult.claims.type}),
})
.then(() => res.json)
.catch(err => console.log(err));
});
and now my server.js now includes:
app.post('/getMyClaims', async(req,res) => {
let {uid,idToken} = req.body;
admin.auth()
.getUser(uid)
.then((userRecord) => console.log(userRecord))
})
and this is where I get stuck, because I am trying to find out how can I call the results of '/getMyClaims' to redirect a user, if they are a customer and are trying to access the '/seller' URL.
I did read the documents as given https://firebase.google.com/docs/auth/admin/custom-claims, but it does not really show how to re-route if claim has a specific type in the backend.
I've figured things out after hours of this!
server.js
var block;
var blockware = (req,res,next) => {
if(block == true || block == undefined){
console.log("deny access", block);
return res.sendStatus(401);
}
console.log("allow",block);
next();
}
app.post('/getMyClaims', async(req,res) => {
let {uid,idToken} = req.body;
if(idToken === 'customer'){
block = true;
} else if(idToken === 'admin'){
block = false;
} else {
block = true;
}
admin.auth()
.getUser(uid)
.then((userRecord) => console.log(userRecord))
})
app.get(['/seller', '/products', '/'], blockware, (req,res) => {
res.sendFile(path.join(staticPath, ""));
})
So now if user has a customer type claim then they are blocked from accessing seller. Otherwise, admin can access seller.
Even when user is logged out since it is automatically set to true, everyone will be blocked from it.
referenced this: express.js - single routing handler for multiple routes in a single line
I'm integrating next-auth package to my fresh Next.js project. I have followed all of the Next.js and next-auth documentations but not able to find a solution.
The issue I'm facing goes like this:
I want to Login to my Next.js app using Email & Password submitted to my API Server running on Laravel.
When submitting the login form I'm executing the below function.
import { signIn } from "next-auth/client";
const loginHandler = async (event) => {
event.preventDefault();
const enteredEmail = emailInputRef.current.value;
const enteredPassword = passwordInputRef.current.value;
const result = await signIn("credentials", {
redirect: false,
email: enteredEmail,
password: enteredPassword,
});
console.log("finished signIn call");
console.log(result);
};
And code shown below is in my pages/api/auth/[...nextauth].js
import axios from "axios";
import NextAuth from "next-auth";
import Providers from "next-auth/providers";
export default NextAuth({
session: {
jwt: true,
},
providers: [
Providers.Credentials({
async authorize(credentials) {
axios
.post("MY_LOGIN_API", {
email: credentials.email,
password: credentials.password,
})
.then(function (response) {
console.log(response);
return true;
})
.catch(function (error) {
console.log(error);
throw new Error('I will handle this later!');
});
},
}),
],
});
But when try to login with correct/incorrect credentials, I get the below error in Google Chrome console log.
POST http://localhost:3000/api/auth/callback/credentials? 401 (Unauthorized)
{error: "CredentialsSignin", status: 401, ok: false, url: null}
Am I missing something here?
From the documentation (https://next-auth.js.org/providers/credentials#example)
async authorize(credentials, req) {
// Add logic here to look up the user from the credentials supplied
const user = { id: 1, name: 'J Smith', email: 'jsmith#example.com' }
if (user) {
// Any object returned will be saved in `user` property of the JWT
return user
} else {
// If you return null or false then the credentials will be rejected
return null
// You can also Reject this callback with an Error or with a URL:
// throw new Error('error message') // Redirect to error page
// throw '/path/to/redirect' // Redirect to a URL
}
}
You are not currently returning a user or null from the authorize callback.
Answer posted by shanewwarren is correct, but here is more elaborated answer,
Using axios to solve this
async authorize(credentials, req) {
return axios
.post(`${process.env.NEXT_PUBLIC_STRAPI_API}/auth/login`, {
identifier: credentials.identifier,
password: credentials.password,
})
.then((response) => {
return response.data;
})
.catch((error) => {
console.log(error.response);
throw new Error(error.response.data.message);
}) || null;
},
So basically, I'm trying to separate my code that handles data (mongoose) from my express Router code, since I might want to use it elsewhere too.
The first thing I did was, I got rid of the res.json() calls, since I don't want the code to only work returning a http response. I want it to return data, so I can then return that data from my router as a http response, but still use it as regular data elsewhere.
Here is a function I wrote to get data from mongoose.
module.exports.user_login = data => {
console.log(data);
ModelUser.findOne({email: data.email}).then(user => {
if(!user){
console.log({email: 'E-mail address not found'});
return {
status: response_code.HTTP_404,
response: {email: 'E-mail address not found'}
}
}
bcrypt.compare(data.password, user.password).then(isMatch => {
if(!isMatch){
console.log({password: 'Invalid password'});
return {
status: response_code.HTTP_400,
response: {password: 'Invalid password'}
}
}
const payload = {
id: user.id,
email: user.email
};
jwt.sign(
payload,
config.PASSPORT_SECRET,
{
expiresIn: "1h"
},
(err, token) => {
console.log({
status: response_code.HTTP_200,
response: {
success: true,
token: token
}
});
return {
status: response_code.HTTP_200,
response: {
success: true,
token: token
}
}
}
);
});
});
};
When this code gets executed in my route like so:
router.post("/login", (req, res) => {
const { errors, isValid } = validateLogin(req.body);
if(!isValid) return res.status(400).json(errors);
console.log("ret", dm_user.user_login(req.body));
});
The log says the return value of user_login() is undefined, even though right before the return statement in user_login() I am logging the exact same values and they are getting logged.
Before I changed it to a log, I tried to store the return value in a variable, but obviously that remained undefined as well, and I got the error: 'Cannot read propery 'status' of undefined' when trying to use the value.
I am definitely missing something..
Well you have an small callback hell here. It might be a good idea to go with async / await and splitting up your code into smaller chunks instead of putting everyhing in 1 file.
I rewrote your user_login function:
const { generateToken } = require("./token.js");
module.exports.user_login = async data => {
let user = await ModelUser.findOne({ email: data.email });
if (!user) {
console.log({ email: "E-mail address not found" });
return {
status: response_code.HTTP_404,
response: { email: "E-mail address not found" }
};
}
let isMatch = await bcrypt.compare(data.password, user.password);
if (!isMatch) {
console.log({ password: "Invalid password" });
return {
status: response_code.HTTP_400,
response: { password: "Invalid password" }
};
}
const payload = {
id: user.id,
email: user.email
};
let response = await generateToken(
payload,
config.PASSPORT_SECRET,
response_code
);
return response;
};
I have moved your token signing method into another file and promisfied it:
module.exports.generateToken = (payload, secret, response_code) => {
return new Promise((res, rej) => {
jwt.sign(
payload,
secret,
{
expiresIn: "1h"
},
(err, token) => {
if (err) {
rej(err);
}
res({
status: response_code.HTTP_200,
response: {
success: true,
token: token
}
});
}
);
});
};
Now you need to change your router function into an async:
router.post("/login", async (req, res) => {
const { errors, isValid } = validateLogin(req.body);
if(!isValid) return res.status(400).json(errors);
let result = await dm_user.user_login(req.body);
console.log(result);
});
In addition: You get undefined because you return your value to an callback function
I also would seperate your routes from your controllers instead of writing your code inside an anonymous function
Please notice that whenever you are trying to return any value you are always present in the callback function and that is definitely not going to return any value to its intended place.
There are a couple of things you can improve about your code :
1.Donot use jwt inside your code where you are making database calls, instead move it where your routes are defined or make a separate file.
2.If you are intending to re-use the code, I would suggest you either use async-await as shown in the answer above by Ifaruki or you can use something like async.js. But the above shown approach is better.
Also always use 'error' field when you are making db calls like this:
ModelUser.findOne({email: data.email}).then((error,user) => {
I'm making a react-redux app with firetore as database. Now, I wanted to use firebase cloud functions for handling stripe payments.
Here is the cloud function "createSubscription":
exports.createSubscription = functions.database
.ref("/teachers/{userId}/pro-membership/token")
.onWrite((event, context) => {
const tokenId = event.after.val();
const userId = context.params.userId;
if (!tokenId) throw new Error("Token Missing");
return admin
.database()
.ref(`teachers/${userId}`)
.once("value")
.then(snapshot => snapshot.val())
.then(user => {
console.log(user);
return stripe.subscriptions.create({
customer: user.customerId,
source: tokenId, **// Here is the error occuring**
items: [
{
plan: "pro-membership"
}
]
});
})
.then(sub => {
admin
.database()
.ref(`teachers/${userId}/pro-membership`)
.update({
status: "active"
});
})
.catch(err => {
console.log("ERRor", err);
});
});
Below is the error information from cloud function's logs:
source is not a valid parameter on a stripe.subscriptions.create request, see https://stripe.com/docs/api/subscriptions/create
Try updating the customer first, adding the token, https://stripe.com/docs/api/customers/update, then create a subscription!
I've mostly utilised the Hapi framework to build RESTful APIs. For this project I'm using Express and I'm a bit lost as to why this is happening.
When I test the POST endpoint using Postman, the first request is fine, but I would get an error when I make the second request.
Error: Can't set headers after they are sent.
The code for the route handler is below:
const login = (req, res) => {
const validation = authScema.loginPayload.validate(req.body)
if (validation.error) {
return res.status(400).send(validation.error.details[0].message)
}
const { email, password } = req.body
firebase
.auth()
.signInWithEmailAndPassword(email, password)
.catch(error => {
// Handle Errors here.
if (error) {
return res.status(400).send('Invalid login details.')
}
})
firebase.auth().onAuthStateChanged(user => {
if (user) {
const userObject = {
email: user.email,
uid: user.uid
}
const token = jwt.sign(userObject, secret)
return res.status(200).send(token)
}
})
}
I don't understand why headers are resent since in every branch, I return. It should have exited the function, right?
Turns out, signInWithEmailAndPassword
is a promise that returns the user in the happy path
So, the following is the final code:
firebase
.auth()
.signInWithEmailAndPassword(email, password)
.then(user => {
const userObject = {
email: user.email,
uid: user.uid
}
const token = jwt.sign(userObject, secret)
res.status(200).json({ token })
})
.catch(error => {
if (error) {
res.status(400).json({ message: 'Invalid login details.' })
}
})
The onOnAuthStateChanged is not necessary in this case.