Unexpected behavior when email has uppercase letters
ErikGoH opened this issue · 1 comments
I was creating some test users when I found the following behavior:
- Create a user with an email that has an uppercase letter
curl --request POST \ --url http://localhost:3000/auth/register \ --header 'Content-Type: application/json' \ --data '{ "email": "testAdmin@example.com", "password": "bigsecret", "confirmPassword": "bigsecret", }'
- Try to login
curl --request POST \ --url http://localhost:3000/auth/login \ --header 'Content-Type: application/json' \ --data '{ "email": "testAdmin@example.com", "password": "bigsecret" } '
- Receive an unauthorized error message
{ "error": "Unauthorized", "message": "Invalid username or password" }
- Change the email to all lowercase letters
curl --request POST \ --url http://localhost:3000/auth/login \ --header 'Content-Type: application/json' \ --data '{ "email": "testadmin@example.com", "password": "bigsecret" } '
- Everything works normally
{ "issued": 1676830866748, "expires": 1676917266748, "provider": "local", "token": "NlKFvdkRSh-Z4Zf-z5QmbA", "password": "ZsO0x9N4Q6mUaOs_XHF7Jg", "user_id": "re46jmvr", "roles": [ "user", "Admin", "user" ], "userDBs": { "interviewer": "http://NlKFvdkRSh-Z4Zf-z5QmbA:ZsO0x9N4Q6mUaOs_XHF7Jg@devf.local:5985/interviewer_d31c511c86334591bcae45d9a5b6086e" } }
It took me a while to figure out why I was getting the Unauthorized message if I had just created the user, after checking that my configuration was correct (I disabled everything related to email confirmation) I had to start adding console.logs in the node_modules > couch-auth
files
Finally realized the thing that was happening when I checked node_modules/@perfood/couch-auth/lib/user/DbManager.js
that everything was correct but when querying the auth.email
view it wasn't returning anything thats when I realized that the emails are saved in lower case.
I don't know if the login route should also lowercase the email or the create should save the email as is sent, my quick fix will be to lowercase the email before sending the login request as I have had some users report that they couldn't login even when the browser password manager saved their credentials.
PS:
I am currently using my fork https://github.com/ErikGoH/superlogin-next/tree/main-keycode but I don't think there are any significant changes
My package.json
looks like this
...
"dependencies": {
...,
"@perfood/couch-auth": "github:ErikGoH/superlogin-next#main-keycode",
...
}
My config
const config = {
security: {
loginOnRegistration: true,
},
testMode: {
debugEmail: this.configService.get('TEST_MODE_DEBUG_EMAIL') === 'true',
noEmail: this.configService.get('TEST_MODE_NO_EMAIL') === 'true',
},
dbServer: {
protocol: this.configService.get<'http://' | 'https://'>('DB_PROTOCOL'),
host: this.configService.get('DB_HOST'),
user: this.configService.get('DB_USER'),
password: this.configService.get('DB_PASSWORD'),
userDB: this.configService.get('DB_USERDB_SL'),
couchAuthDB: this.configService.get('DB_COUCH_AUTHDB'),
publicURL: this.configService.get('DB_PUBLIC_URL'),
},
mailer: {
fromEmail: this.configService.get('MAILER_FROMUSER'),
transport: this.configService.get('MAILER_SENDGRID_APIKEY')
? nodemailerSendgrid
: undefined,
options: this.configService.get('MAILER_SENDGRID_APIKEY')
? {
apiKey: this.configService.get(
'MAILER_SENDGRID_APIKEY',
) as string,
}
: {
host: this.configService.get('MAILER_HOST'),
port: this.configService.get('MAILER_PORT'),
secure:
this.configService.get('MAILER_PORT') === '465' ? true : false,
auth: {
user: this.configService.get('MAILER_AUTHUSER'),
pass: this.configService.get('MAILER_PASSWORD'),
},
},
},
local: {
// Custom names for the username and password fields in your sign-in form
usernameField: 'email',
passwordField: 'password',
emailUsername: true,
usernameLogin: false,
// Send out a confirm email after each user signs up with local login
sendConfirmEmail: false,
// Require the email be confirmed before the user can login or before his changed email is updated
requireEmailConfirm: false,
},
providers: {
google: {
credentials: {
clientID: this.configService.get('GOOGLE_CLIENT_ID'),
clientSecret: this.configService.get('GOOGLE_CLIENT_SECRET'),
audience: [
this.configService.get('GOOGLE_CLIENT_ID'),
this.configService.get('GOOGLE_CLIENT_ID_CORDOVA'),
],
},
options: {
scope: ['email'],
},
template: path.join(
__dirname,
'./templates/oauth/my-custom-secure-auth-callback.ejs',
),
templateTest: path.join(
__dirname,
'./templates/oauth/my-custom-secure-auth-callback-test.ejs',
),
},
facebook: {
credentials: {
clientID: this.configService.get('FACEBOOK_APP_ID'),
clientSecret: this.configService.get('FACEBOOK_APP_SECRET'),
profileFields: ['id', 'displayName', 'name', 'emails'],
fbGraphVersion: 'v3.2',
},
options: {
scope: ['email', 'public_profile'],
},
template: path.join(
__dirname,
'./templates/oauth/my-custom-secure-auth-callback.ejs',
),
},
},
userDBs: {
defaultDBs: {
private: ['interviewer'],
},
model: {
_default: {
designDocs: [],
},
interviewer: {
type: 'private',
adminRoles: ['admin', 'AdminActive'],
appendSeparator: this.configService.get('DB_APPEND_SEPARATOR'),
designDocs: [], //'consumos', 'sesiones', 'seguimientos'
},
},
designDocDir: path.join(__dirname, './ddocs'),
},
userModel: {
whitelist: ['roles'],
customValidators: {
roleValidator: function (value: string[], validRoles: string[]) {
if (!value || value.length < 1) {
return 'El rol es requerido';
}
try {
value.forEach((role) => {
if (!validRoles.includes(role)) {
throw new Error(`: El rol no puede ser ${role}`);
}
});
} catch (error) {
type validatorError = { message?: string };
const posibleError = error as validatorError;
return posibleError?.message ?? 'Ocurrio un error';
}
return null;
},
},
validate: {
roles: {
roleValidator: ['user', 'Interviewer', 'Admin'],
},
},
},
};
I don't know if the login route should also lowercase the email or the create should save the email as is sent, my quick fix will be to lowercase the email before sending the login request as I have had some users report that they couldn't login even when the browser password manager saved their credentials.
Yeah, lowercasing all emails before sending them to login is the correct workaround. I guess I'm always doing that on the client side, so I forgot about it. I've also been bitten by email casing issues in superlogin and moved towards lowercasing everything, e.g. in:
The .toLowerCase().trim()
isn't yet included in the local strategy. Would be a good idea to add it there, too:
https://github.com/perfood/couch-auth/blob/master/src/local.ts#L52
Should be also fine when using username as login, since that's validated with /^[a-z0-9_-]{3,16}$/