I am currently working developing a login screen where I am using djoser for authentication.
I already use the /users/ and /token/login/ endpoints to create and log in users respectively in the frontend (JS fetch) and I know that /users/ will return an error message if the user already exists (which I'm displaying with react-hook-form).
My question is: is there a way of verifying if the user already exists with their email only (without trying to create an account and working it out from the error message)
From what I've found, Djoser doesn't provide a way to check if the email exists, so I wrote an APIView
class CheckEmail(APIView):
permission_classes = [IsAuthenticatedOrReadOnly]
def get(self, request, format=None):
email = request.GET.get('email')
user = UserAccount.objects.filter(email=email)
if user:
return Response('Email exists')
else:
return Response('Welcome aboard!')
and used a get request with Yup and Formik:
const LoginSchema = Yup.object().shape({
checkEmail: Yup.boolean().default(true),
email: Yup.string()
.email("Please enter valid email")
.required("Email is required")
.when("checkEmail", {
is: true,
then: Yup.string()
.test({
message: () => "Email already exists",
test: async(email) => {
if(email) {
try {
let response = await axios.get(`http://localhost:8000/accounts/users/check_email/?email=${email}`);
if (response.data === 'Welcome aboard!') {
return true;
} else {
return false;
}
} catch (error) {
//console.log(error);
}
}
}
})
}),
Hope that helps
Related
This question already has answers here:
Firestore- checking if username already exists [duplicate]
(1 answer)
Firestore unique index or unique constraint?
(5 answers)
Check a document field for a specific value in Cloud Firestore
(3 answers)
Closed 1 year ago.
I need a littel help with Firestore. I have a users data collection, user id-documents with each user information. I want to check if username already exists; // "That username already exists". How do I do it?
const signUpForm = document.querySelector('#signup-form');
signUpForm.addEventListener('submit', (evt) =>{
evt.preventDefault();
//get user email, password
const email= signUpForm['signup-email'].value;
const password = signUpForm['signup-password'].value;
const repassword = signUpForm['signup-re-password'].value;
if(password != repassword)
{
alert("Passwords don't match \nPlease try again");
}
else{
if(//check username exist?)
{
alert('This username already exist !');
}
else { //sign up the user
auth.createUserWithEmailAndPassword(email, password)
.then(cred => {
return db.collection('users').doc(cred.user.uid).set({
neptun: signUpForm['signup-neptun'].value,
nickName: signUpForm['signup-nickName'].value
});
});
}
});
Update!
A working solution :
const username = signUpForm['signup-username'].value;
db.collection("users").where("username", "==", username).get().then((doc) => {
if(!doc.empty) {
alert("This username is already taken!");
}
else{
//sign up the user
});
To be straight forward you can just try this code:
const username = ""
const userNameDoc = await firebase.firestore().collection("users").where("username", "==", username).get()
if(!userNameDoc.empty) {
console.log("This username is already taken!");
}
But as this is all frontend so this can be bypassed if anyone wants to. All this requires you to give all users access to the whole users collection which won't be ideal. So you should ideally use a cloud function or you own server environment for better security.
For example, you can block all direct requests to Firestore collection using security rules and create users using the Admin SDK in cloud functions.
You can use the code below in your cloud function to create a new user by checking if the username is still valid.
exports.createNewUser = functions.https.onCall(async (data, context) => {
const userEmail = data.email
const userName = data.name
const userPass = data.password
const userNameDoc = await admin.firestore().collection("users").where("username", "==", username).get()
if(!userNameDoc.empty) {
console.log("This username is already taken!");
return {result: "Username is already taken!"}
}
return admin
.auth()
.createUser({
email: 'user#example.com',
password: userPassword,
displayName: userName,
})
.then((userRecord) => {
// See the UserRecord reference doc for the contents of userRecord.
console.log('Successfully created new user:', userRecord.uid);
return {result: "User Created!"}
})
.catch((error) => {
console.log('Error creating new user:', error);
return {result: error}
});
});
The 2nd method is way more secure than first one for your Firestore DB.
Firebase automatically checks if an email is already in use.
The createUserWithEmailAndPassword function throws an error, when a user tries to sign up with an email address already in use.
Catch errors and then check for the "auth/email-already-in-use" error code:
.catch(function(error){
if(error.code=="auth/email-already-in-use"){//do something}
})
For more details, see the firebase auth reference https://firebase.google.com/docs/reference/js/firebase.auth.Auth?authuser=1#createuserwithemailandpassword
I've created a simple createUser function which is executed on call. I have one problem though. The function is crashing when the user is trying to register with an already existing email. I mean, it's ok, since no one wants to have 2 users with the same email address but I want to prevent crushing function, instead, I want to send an error message as a response.
export const createUserTest = functions.https.onCall((data, context) => {
const {email, password} = data;
return new Promise((resolve, reject)=>{
try{
admin
.auth()
.createUser({
email: email,
emailVerified: false,
password: password,
disabled: false,
})
.then((user) => {
resolve({
result: 'success',
user: user,
}) ;
})
.catch((error) => {
reject(error) ;
});
}catch(error) {
reject (error)
}
})
});
I tried to put the function in to try/catch block but it didn't help. Do you have an idea of how I can achieve my goal?
As explained in the doc for Callable Cloud Functions, "to ensure the client gets useful error details, return errors from a callable by throwing (or returning a Promise rejected with) an instance of functions.https.HttpsError".
The error has a code attribute that can be one of the values listed here. In your case, the most appropriate seems to be already-exists.
On, the other hand, you'll find here the Admin SDK Authentication errors list and you'll see that in case the provided email is already in use by an existing user the error code is auth/email-already-exists.
So you can adapt your code as follows:
export const createUserTest = functions.https.onCall((data, context) => {
const { email, password } = data;
return admin
.auth()
.createUser({
email: email,
emailVerified: false,
password: password,
disabled: false,
})
.then((user) => {
return {
result: 'success',
user: user,
}
})
.catch((error) => {
if (error.code === 'auth/email-already-exists') {
throw new functions.https.HttpsError('already-exists', 'The provided email is already in use by an existing user');
} else {
throw new functions.https.HttpsError('...other code....', '...');
// If an error other than HttpsError is thrown, your client instead receives an error with the message INTERNAL and the code internal.
}
});
});
See here in the doc, how to handle errors on the client side. If error.code == 'already-exists' you know that it's because the email is already in use.
I'm using AWS Amplify for authentication and Stripe for the payment to create sign up page.
PROBLEM: I can't find a way to combine validations for Email and password section(from AWS Amplify) with payment info section(from Stripe).
My current code creates a Stripe token and call API(with valid payment info) then handles the error message from userSignupRequest which takes care of email and password fields.
How do I validate the email and password with payment info then create account in AWS and Stripe?
// Stripe payment process
this.props.stripe.createToken(
{
email: this.state.email
}
).then(result => {
// PROBLEM: Form server validation from Stripe
if(result.error){
return this.setState({ errors: { errorMsg: result.error.message }, isLoading: false })
}
// if success, create customer and subscription with result.token.id
const apiName = 'NameOfAPI';
const path = '/stripe/signup';
let myInit = {
body: {
"stripeToken": result.token.id,
"email": this.state.email
}
}
API.post(apiName , path, myInit).then(reponse => {
this.props.userSignupRequest(this.state.email, this.state.password, reponse).then(user => {
this.setState({
confirmAccount: true,
isLoading: false,
userEmail: this.state.email,
errors: {}
})
this.props.history.push('/signup#confirm-account')
}).catch(err => {
// PROBLEM: Form server validation
this.setState({ errors: { errorMsg: err.message }, isLoading: false })
})
}).catch(err => {
console.log(err)
this.setState({ errors: { errorMsg: err }, isLoading: false })
});
})
It seems like we have a very similar stack. My solution was to handle everything server-side. You'll need to give your lambda functions the appropriate IAM permissions to access Cognito. The code below is a little long. I use async/await, which really cleans things up for me. You'll need to use Lambda with node 8 to use async/await though.
I validate that everything matches the right format client-side (i.e. emails are really emails, passwords are the right length). I realized the only error that could come up is an "existing user" error from Cognito. The idea is: test if the user exists before you attempt to sign the person up with Stripe. There's no way to "test" if the user's credit card is valid with Stripe. It's all or nothing. If it's valid it will go through, if not, you'll get an error. If it goes through, you can then sign up the user with Cognito, knowing you should not get an error (you've validated the email and password client-side and, you know the use doesn't already exist).
For reference, here's the aws-sdk for cognito.
const AWS = require('aws-sdk');
const cognito = new AWS.CognitoIdentityServiceProvider({
region: "region",
userPoolId: "cognito_user_pool_id",
});
module.exports.signUpUser = (payload) => {
const usernamePayload = {
UserPoolId: "cognito_user_pool_id",
Username: payload.email,
};
// I use emails for usernames.
new Promise((resolve, reject) => {
cognito.adminGetUser(usernamePayload, (error, response) => {
if (error && error.code === 'UserNotFoundException') {
resolve(false);
} else if (error) {
reject(error);
} else {
// if adminGetUser doesn't fail, it means the username exists
resolve(true);
}
});
}).then((usernameExists) => {
if (!usernameExists) {
// run stripe API stuff
// always run before sign up below to catch stripe errors
// and return those errors to client
// before you sign up the user to Cognito
// since you've already verified the user does not exist
// it would be rare for an error to come up here
// as long as you validate passwords and emails client-side
const signUpPayload = {
ClientId: "cognito_user_pool_client_id",
Username: payload.email,
Password: payload.password,
UserAttributes: [
{
Name: 'email',
Value: payload.email,
},
],
};
new Promise((resolve, reject) => {
cognito.signUp(signUpPayload, (error, response) => {
if (error) {
reject(error);
} else {
resolve(response);
}
});
}).catch((error) => {
// you should hopefully encounter no errors here
// once you get everything setup correctly
console.log(error);
})
} else {
// means username already exists, send error to client
// saying username exists
}
}).catch((error) => {
// may want to dispatch this error to client
console.log(error);
});
return null;
};
I have a login in my Angular 2 app, and I have been converting it from using a fake backend (which works) to connect to our mongoDB-based API instead.
This is the login function I am using in the authentication service:
login(username: string, password: string) {
const u = encodeURIComponent(username);
const p = encodeURIComponent(password);
this._url = `https://api.somesite.com/v0/staff/login/${u}/${p}?apikey=somekey`;
console.log(this._url);
return this.http.post(this._url, JSON.stringify({ username: username, password: password }))
.map((response: Response) => {
// login successful if there's a jwt token in the response
const user = response.json();
if (user && user.token) {
// store user details and jwt token in local storage to keep user logged in between page refreshes
localStorage.setItem('currentUser', JSON.stringify(user));
}
});
}
In my login component I am subscribing like this:
login() {
this.loading = true;
this.authenticationService.login(this.model.username, this.model.password)
.subscribe(
data => {
this.router.navigate(['/']);
console.log('User logged in as: ' + this.model.username);
},
error => {
this.alertService.error(error);
this.loading = false;
});
this.authenticationService.username = this.model.username;
}
When I try this, and log to the console "this_url", I get what I would expect. For instance, if the user typed in "billsmith" for username, and "parisnow" for password, I see this in the console for "this_url":
https://api.somesite.com/v0/staff/login/billsmith/parisnow?apikey=somekey
Furthermore, I can type that url directly into the browser address window and see data (when the username and password correctly correspond to actual records in our database). So it's accessing the correct info in that sense.
But in the console I get a "404" error for that generated url. It also doesn't "do anything". In other words, it doesn't correctly redirect to the main component as it did with the fakeBackend-enabled login. And the only thing that's different now is the url that I am calling (because I'm connecting to our actual API now, as opposed to a fake backend provider).
FYI, the url when using the fake backend looked like this:
return this.http.post('/api/authenticate', JSON.stringify({ username: username, password: password}))
What am I missing here?
By the way, this is how things look on the server side re: our mongoDB:
exports.byLogin = function(req, res, next) {
let ioOnly = false, username, password;
if (_.isUndefined(req.params)){
ioOnly=true;
username = req.username;
password = req.password;
}
else {
username = req.params.username;
password = req.params.password;
}
staff.findOne({username: username, password: password}, function(err, doc) {
if (err) { if (!ioOnly) { return next(err) } else { return res(err)}}
else if(doc) ((!ioOnly) ? res.send(doc) : res(doc));
else ((!ioOnly) ? res.sendStatus(204) : res(doc));
});
};
I have a MongoDB/Webpack/NodeJS Express set up in my ReactJS + Redux project.
I am making API calls from action creators in redux, and reach the API server and get a successful status back, yet the data never gets saved and the database never gets created even checking with in terminal mongo -> dbs and it doesn't show practicedb database which I named it as.
What could be the issue? Am I missing something?
Any guidance or insight would be greatly appreciated. Thank you
This is my set up for API:
import axios from 'axios';
import { browserHistory } from 'react-router';
import cookie from 'react-cookie';
import { AUTH_USER, AUTH_ERROR } from './types';
const API_URL = 'http://localhost:3000/api';
export function errorHandler(dispatch, error, type) {
let errorMessage = (error.data.error) ? error.data.error : error.data;
// NOT AUTHENTICATED ERROR
if(error.status === 401) {
errorMessage = 'You are not authorized to do this.';
}
dispatch({
type: type,
payload: errorMessage
});
}
export function registerUser({ email }) {
return function(dispatch) {
axios.post(`${API_URL}/auth/register`, { email })
.then(response => {
console.log('THIS IS TESTING PURPOSE')
console.log(response)
dispatch({ type: AUTH_USER });
})
.catch((error) => {
errorHandler(dispatch, error.response, AUTH_ERROR)
});
}
}
And my API controller is set up as such:
"use strict";
const User = require('../models/user')
exports.register = function(req, res, next) {
const email = req.body.email;
console.log('ERROR 1')
if(!email) {
return res.status(422).send({ error: 'You must enter an email address.'})
console.log('ERROR 1')
}
User.findOne({ email: email }, function(err, existingUser) {
if(err) { return next(err); }
console.log('ERROR 2')
if(existingUser) {
return res.status(422).send({ error: 'That email address is already in use.'})
}
console.log('ERROR 3')
let user = new User({
email: email,
})
console.log('ERROR 4')
user.save(function(err, user) {
if(err) { return next(err); }
console.log('ERROR 5')
res.status(201).json({
user: user,
})
})
})
console.log('ERROR 6')
}
Configuration for the API:
module.exports = {
'database': 'mongodb://localhost/practicedb',
'port': process.env.PORT || 3000,
'secret': 'dogcat',
}
The project so far just has an input text field, where it accepts an email address. If the email has already been registered, the API should return the error That email address is already in use. and it does.
So I tried console logging to see what the problem is, and the first time I submit the POST request, it logs the following (the terminal showing API console logs):
And if I try to submit the same email again, it throws me the API error that the email is already in use with 422 error, yet the data do not get saved and database (practicedb) never get created:
Also, what is the OPTIONS request that shows up in terminal? I only made an attempt to POST. Lastly, is OPTIONS why the ERROR log in API server is not logging in chronological order?
EDIT
You're using the wrong Mongo shell command: db will only show you the current database (test), but if you want to see a list of all databases, you should use show dbs.
If you want to switch databases:
use practicedb
And, if you want to see the collections in the current database:
show collections