Merge branch 'master' into develop

This commit is contained in:
Barış Soner Uşaklı
2024-09-04 11:31:50 -04:00
12 changed files with 104 additions and 48 deletions

View File

@@ -179,6 +179,7 @@
"cant-chat-with-yourself": "You can't chat with yourself!",
"chat-restricted": "This user has restricted their chat messages. They must follow you before you can chat with them",
"chat-user-blocked": "You have been blocked by this user.",
"chat-disabled": "Chat system disabled",
"too-many-messages": "You have sent too many messages, please wait awhile.",
"invalid-chat-message": "Invalid chat message",

View File

@@ -83,6 +83,7 @@
"email-confirmed": "Email Confirmed",
"email-confirmed-message": "Thank you for validating your email. Your account is now fully activated.",
"email-confirm-error-message": "There was a problem validating your email address. Perhaps the code was invalid or has expired.",
"email-confirm-error-message-already-validated": "Your email address was already validated.",
"email-confirm-sent": "Confirmation email sent.",
"none": "None",

View File

@@ -81,6 +81,7 @@
"change-password": "Change Password",
"change-password-error": "Invalid Password!",
"change-password-error-wrong-current": "Your current password is not correct!",
"change-password-error-same-password": "Your new password matches your current password, please use a new password.",
"change-password-error-match": "Passwords must match!",
"change-password-error-privileges": "You do not have the rights to change this password.",
"change-password-success": "Your password is updated!",

View File

@@ -24,6 +24,9 @@ get:
error:
type: string
description: Translation key for client-side localisation
alreadyValidated:
type: boolean
description: set to true if the email was already validated
required:
- title
- $ref: ../../components/schemas/CommonProps.yaml#/CommonProps

View File

@@ -23,7 +23,7 @@ put:
example: '123456'
newPassword:
type: string
example: '123456'
example: '654321'
required:
- newPassword
responses:

View File

@@ -219,20 +219,31 @@ Controllers.registerInterstitial = async function (req, res, next) {
}
};
Controllers.confirmEmail = async (req, res, next) => {
Controllers.confirmEmail = async (req, res) => {
function renderPage(opts = {}) {
res.render('confirm', {
title: '[[pages:confirm]]',
...opts,
});
}
try {
if (req.loggedIn) {
const emailValidated = await user.getUserField(req.uid, 'email:confirmed');
if (emailValidated) {
return renderPage({ alreadyValidated: true });
}
}
await user.email.confirmByCode(req.params.code, req.session.id);
if (req.session.registration) {
// After confirmation, no need to send user back to email change form
delete req.session.registration.updateEmail;
}
res.render('confirm', {
title: '[[pages:confirm]]',
});
renderPage();
} catch (e) {
if (e.message === '[[error:invalid-data]]') {
return next();
if (e.message === '[[error:invalid-data]]' || e.message === '[[error:confirm-email-expired]]') {
renderPage({ error: true });
return;
}
throw e;

View File

@@ -363,7 +363,10 @@ Messaging.canMessageUser = async (uid, toUid) => {
user.blocks.is(uid, toUid),
]);
if (isBlocked || (settings.restrictChat && !isAdmin && !isModerator && !isFollowing)) {
if (isBlocked) {
throw new Error('[[error:chat-user-blocked]]');
}
if (settings.restrictChat && !isAdmin && !isModerator && !isFollowing) {
throw new Error('[[error:chat-restricted]]');
}

View File

@@ -317,6 +317,9 @@ module.exports = function (User) {
if (!correct) {
throw new Error('[[user:change-password-error-wrong-current]]');
}
if (data.currentPassword === data.newPassword) {
throw new Error('[[user:change-password-error-same-password]]');
}
}
const hashedPassword = await User.hashPassword(data.newPassword);

View File

@@ -1,7 +1,19 @@
{{{ if alreadyValidated }}}
<div class="alert alert-info">
<p>[[notifications:email-confirm-error-message-already-validated]]</p>
{{{ end }}}
{{{ if error }}}
<div class="alert alert-warning">
<p>[[notifications:email-confirm-error-message]]</p>
{{{ end }}}
{{{ if (!error && !alreadyValidated )}}}
<div class="alert alert-success">
<strong>[[notifications:email-confirmed]]</strong>
<p>[[notifications:email-confirmed-message]]</p>
<p>
{{{ end }}}
<p class="mb-0">
<a href="{config.relative_path}/">[[notifications:back-to-home, {config.siteTitle}]]</a>
</p>
</div>
</div>

View File

@@ -1,40 +1,42 @@
<div class="row col-12 col-sm-6 offset-sm-3">
{{{ if valid }}}
<div class="card card-body bg-light">
{{{ if displayExpiryNotice }}}
<div class="alert alert-warning">
[[reset_password:password-expired]]
<div class="row">
<div class="col-12 col-sm-6 offset-sm-3">
{{{ if valid }}}
<div class="card card-body bg-light">
{{{ if displayExpiryNotice }}}
<div class="alert alert-warning">
[[reset_password:password-expired]]
</div>
{{{ end }}}
<div class="alert alert-success alert-dismissible hidden" id="success">
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
<strong>[[reset_password:password-changed.title]]</strong>
<p>[[reset_password:password-changed.message]]</p>
</div>
<div class="alert alert-warning hidden" id="notice">
<strong></strong>
<p></p>
</div>
<form onsubmit="return false;" id="reset-form">
<div class="mb-3">
<label class="form-label" for="password">[[reset_password:new-password]]</label>
<input class="form-control" type="password" placeholder="[[reset_password:new-password]]" id="password" />
</div>
<div class="mb-3">
<label class="form-label" for="repeat">[[reset_password:repeat-password]]</label>
<input class="form-control" type="password" placeholder="[[reset_password:repeat-password]]" id="repeat" />
</div>
<button class="btn btn-primary" id="reset" type="submit">[[reset_password:reset-password]]</button>
</form>
</div>
{{{ else }}}
<div class="card">
<div class="card-header">
<h5 class="mb-0">[[reset_password:wrong-reset-code.title]]</h5>
</div>
<div class="card-body">
<p>[[reset_password:wrong-reset-code.message]]</p>
</div>
</div>
{{{ end }}}
<div class="alert alert-success alert-dismissible hidden" id="success">
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
<strong>[[reset_password:password-changed.title]]</strong>
<p>[[reset_password:password-changed.message]]</p>
</div>
<div class="alert alert-warning hidden" id="notice">
<strong></strong>
<p></p>
</div>
<form onsubmit="return false;" id="reset-form">
<div class="mb-3">
<label class="form-label" for="password">[[reset_password:new-password]]</label>
<input class="form-control" type="password" placeholder="[[reset_password:new-password]]" id="password" /><br />
</div>
<div class="mb-3">
<label class="form-label" for="repeat">[[reset_password:repeat-password]]</label>
<input class="form-control" type="password" placeholder="[[reset_password:repeat-password]]" id="repeat" /><br />
</div>
<button class="btn btn-primary btn-block" id="reset" type="submit">[[reset_password:reset-password]]</button>
</form>
</div>
{{{ else }}}
<div class="card text-bg-danger">
<h5 class="card-header">
[[reset_password:wrong-reset-code.title]]
</h5>
<div class="card-body">
<p>[[reset_password:wrong-reset-code.message]]</p>
</div>
</div>
{{{ end }}}
</div>

View File

@@ -562,8 +562,15 @@ describe('API', async () => {
const reloginPaths = ['GET /api/user/{userslug}/edit/email', 'PUT /users/{uid}/password', 'DELETE /users/{uid}/sessions/{uuid}'];
if (reloginPaths.includes(`${method.toUpperCase()} ${path}`)) {
({ jar } = await helpers.loginUser('admin', '123456'));
const sessionIds = await db.getSortedSetRange('uid:1:sessions', 0, -1);
const sessObj = await db.sessionStoreGet(sessionIds[0]);
let sessionIds = await db.getSortedSetRange('uid:1:sessions', 0, -1);
let sessObj = await db.sessionStoreGet(sessionIds[0]);
if (!sessObj) {
// password changed so login with new pwd
({ jar } = await helpers.loginUser('admin', '654321'));
sessionIds = await db.getSortedSetRange('uid:1:sessions', 0, -1);
sessObj = await db.sessionStoreGet(sessionIds[0]);
}
const { uuid } = sessObj.meta;
mocks.delete['/users/{uid}/sessions/{uuid}'][1].example = uuid;

View File

@@ -776,6 +776,18 @@ describe('User', () => {
assert(correct);
});
it('should not let user change their password to their current password', async () => {
const uid = await User.create({ username: 'changepasswordsame', password: '123456' });
await assert.rejects(
apiUser.changePassword({ uid: uid }, {
uid: uid,
newPassword: '123456',
currentPassword: '123456',
}),
{ message: '[[user:change-password-error-same-password]]' },
);
});
it('should not let user change another user\'s password', async () => {
const regularUserUid = await User.create({ username: 'regularuserpwdchange', password: 'regularuser1234' });
const uid = await User.create({ username: 'changeadminpwd1', password: '123456' });