Compare commits

...

6 Commits

Author SHA1 Message Date
Phillip Thelen
a09c6b84d5 make emails lowercase 2025-05-07 12:17:20 +02:00
Phillip Thelen
3ba161abbe remove unused import 2025-04-09 13:27:01 +02:00
Phillip Thelen
9038572ff9 add tests for new api route 2025-04-09 13:13:03 +02:00
Phillip Thelen
80825f3478 update social tests 2025-04-09 10:49:03 +02:00
Phillip Thelen
a254c50a8e new api route to check if an email is available 2025-04-09 10:21:51 +02:00
Phillip Thelen
918a769441 Add field to not register social account when called 2025-04-09 10:21:39 +02:00
6 changed files with 143 additions and 5 deletions

View File

@@ -64,6 +64,18 @@ describe('POST /user/auth/social', () => {
await expect(getProperty('users', response.id, 'profile.name')).to.eventually.equal('a google user');
});
it('fails if allowRegister is false and user does not exist', async () => {
await expect(api.post(endpoint, {
authResponse: { access_token: randomAccessToken }, // eslint-disable-line camelcase
network,
allowRegister: false,
})).to.eventually.be.rejected.and.eql({
code: 404,
error: 'NotFound',
message: t('userNotFound'),
});
});
it('logs an existing user in', async () => {
const registerResponse = await api.post(endpoint, {
authResponse: { access_token: randomAccessToken }, // eslint-disable-line camelcase
@@ -131,6 +143,36 @@ describe('POST /user/auth/social', () => {
expect(response.newUser).to.be.false;
});
it('logs an existing user into their social account if allowRegister is false', async () => {
const registerResponse = await api.post(endpoint, {
authResponse: { access_token: randomAccessToken }, // eslint-disable-line camelcase
network,
});
expect(registerResponse.newUser).to.be.true;
// This is important for existing accounts before the new social handling
passport._strategies.google.userProfile.restore();
const expectedResult = {
id: randomGoogleId,
displayName: 'a google user',
emails: [
{ value: user.auth.local.email },
],
};
sandbox.stub(passport._strategies.google, 'userProfile').yields(null, expectedResult);
const response = await api.post(endpoint, {
authResponse: { access_token: randomAccessToken }, // eslint-disable-line camelcase
network,
allowRegister: false,
});
expect(response.apiToken).to.eql(registerResponse.apiToken);
expect(response.id).to.eql(registerResponse.id);
expect(response.apiToken).not.to.eql(user.apiToken);
expect(response.id).not.to.eql(user._id);
expect(response.newUser).to.be.false;
});
it('add social auth to an existing user', async () => {
const response = await user.post(endpoint, {
authResponse: { access_token: randomAccessToken }, // eslint-disable-line camelcase

View File

@@ -0,0 +1,56 @@
import {
translate as t,
requester,
generateUser,
} from '../../../../helpers/api-integration/v4';
const ENDPOINT = '/user/auth/check-email';
describe('POST /user/auth/check-email', () => {
const email = 'SOmE-nEw-emAIl_2@example.net';
let api;
beforeEach(async () => {
api = requester();
});
it('returns email if it is not used yet', async () => {
const response = await api.post(ENDPOINT, {
email,
});
expect(response.email).to.eql(email);
});
it('rejects if email is not provided', async () => {
await expect(api.post(ENDPOINT, {
})).to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: 'Invalid request parameters.',
});
});
it('rejects if email is already taken', async () => {
const user = await generateUser();
await expect(api.post(ENDPOINT, {
email: user.auth.local.email,
})).to.eventually.be.rejected.and.eql({
code: 401,
error: 'NotAuthorized',
message: t('emailTaken'),
});
});
it('rejects if casing is different', async () => {
const user = await generateUser();
await expect(api.post(ENDPOINT, {
email: user.auth.local.email.toUpperCase(),
})).to.eventually.be.rejected.and.eql({
code: 401,
error: 'NotAuthorized',
message: t('emailTaken'),
});
});
});

View File

@@ -479,7 +479,7 @@ api.updateHero = {
}
if (updateData.auth.local && updateData.auth.local.email) {
hero.auth.local.email = updateData.auth.local.email;
hero.auth.local.email = updateData.auth.local.email.toLowerCase();
}
}

View File

@@ -1,6 +1,9 @@
import {
authWithHeaders,
} from '../../middlewares/auth';
import {
NotAuthorized,
} from '../../libs/errors';
import * as authLib from '../../libs/auth';
import { model as User } from '../../models/user';
import { verifyUsername } from '../../libs/user/validation';
@@ -83,4 +86,37 @@ api.registerLocal = {
},
};
/**
* @api {put} /api/v3/user/auth/check-email Check if email is used
* @apiDescription Check if the email is already used by another user
* @apiName CheckEmail
* @apiGroup User
*
* @apiParam (Body) {String} email The checked email address.
*
* @apiSuccess {String} data.email The checked email address
*/
api.checkEmail = {
method: 'POST',
url: '/user/auth/check-email',
async handler (req, res) {
req.checkBody({
email: {
notEmpty: { errorMessage: res.t('missingEmail') },
},
});
const validationErrors = req.validationErrors();
if (validationErrors) throw validationErrors;
const emailAlreadyInUse = await User.findOne({
'auth.local.email': req.body.email.toLowerCase(),
}).select({ _id: 1 }).lean().exec();
if (emailAlreadyInUse) throw new NotAuthorized(res.t('emailTaken'));
return res.respond(200, { email: req.body.email });
},
};
export default api;

View File

@@ -1,6 +1,6 @@
import passport from 'passport';
import common from '../../../common';
import { BadRequest, NotAuthorized } from '../errors';
import { BadRequest, NotAuthorized, NotFound } from '../errors';
import logger from '../logger';
import {
generateUsername,
@@ -33,14 +33,14 @@ export async function socialEmailToLocal (user) {
{ 'auth.local.email': socialEmail },
{ _id: 1 },
).exec();
if (!conflictingUser) return socialEmail;
if (!conflictingUser) return socialEmail.toLowerCase();
}
return undefined;
}
export async function loginSocial (req, res) { // eslint-disable-line import/prefer-default-export
let existingUser = res.locals.user;
const { network } = req.body;
const { network, allowRegister = true } = req.body;
const isSupportedNetwork = common.constants.SUPPORTED_SOCIAL_NETWORKS
.find(supportedNetwork => supportedNetwork.key === network);
@@ -74,6 +74,10 @@ export async function loginSocial (req, res) { // eslint-disable-line import/pre
return loginRes(user, req, res);
}
if (!allowRegister) {
throw new NotFound(res.t('userNotFound'));
}
let email;
if (profile.emails && profile.emails[0] && profile.emails[0].value) {
email = profile.emails[0].value.toLowerCase();