You've already forked joplin
mirror of
https://github.com/laurent22/joplin.git
synced 2025-11-06 09:19:22 +02:00
Server: throwing an error if the password being saved already seems to be hashed (#8637)
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
import { createUserAndSession, beforeAllDb, afterAllTests, beforeEachDb, models, checkThrowAsync, expectThrow } from '../utils/testing/testUtils';
|
import { createUserAndSession, beforeAllDb, afterAllTests, beforeEachDb, models, checkThrowAsync, expectThrow } from '../utils/testing/testUtils';
|
||||||
import { EmailSender, UserFlagType } from '../services/database/types';
|
import { EmailSender, UserFlagType } from '../services/database/types';
|
||||||
import { ErrorUnprocessableEntity } from '../utils/errors';
|
import { ErrorBadRequest, ErrorUnprocessableEntity } from '../utils/errors';
|
||||||
import { betaUserDateRange, stripeConfig } from '../utils/stripe';
|
import { betaUserDateRange, stripeConfig } from '../utils/stripe';
|
||||||
import { accountByType, AccountType } from './UserModel';
|
import { accountByType, AccountType } from './UserModel';
|
||||||
import { failedPaymentFinalAccount, failedPaymentWarningInterval } from './SubscriptionModel';
|
import { failedPaymentFinalAccount, failedPaymentWarningInterval } from './SubscriptionModel';
|
||||||
@@ -425,4 +425,13 @@ describe('UserModel', () => {
|
|||||||
expect((await models().user().load(user1.id)).enabled).toBe(0);
|
expect((await models().user().load(user1.id)).enabled).toBe(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should throw an error if the password being saved seems to be hashed', async () => {
|
||||||
|
const passwordSimilarToHash = '$2a$10';
|
||||||
|
|
||||||
|
const error = await checkThrowAsync(async () => await models().user().save({ password: passwordSimilarToHash }));
|
||||||
|
|
||||||
|
expect(error.message).toBe('Unable to save user because password already seems to be hashed. User id: undefined');
|
||||||
|
expect(error instanceof ErrorBadRequest).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { ModelType } from '@joplin/lib/BaseModel';
|
|||||||
import { _ } from '@joplin/lib/locale';
|
import { _ } from '@joplin/lib/locale';
|
||||||
import { formatBytes, GB, MB } from '../utils/bytes';
|
import { formatBytes, GB, MB } from '../utils/bytes';
|
||||||
import { itemIsEncrypted } from '../utils/joplinUtils';
|
import { itemIsEncrypted } from '../utils/joplinUtils';
|
||||||
import { getMaxItemSize, getMaxTotalItemSize } from './utils/user';
|
import { getMaxItemSize, getMaxTotalItemSize, isHashedPassword } from './utils/user';
|
||||||
import * as zxcvbn from 'zxcvbn';
|
import * as zxcvbn from 'zxcvbn';
|
||||||
import { confirmUrl, resetPasswordUrl } from '../utils/urlUtils';
|
import { confirmUrl, resetPasswordUrl } from '../utils/urlUtils';
|
||||||
import { checkRepeatPassword, CheckRepeatPasswordInput } from '../routes/index/users';
|
import { checkRepeatPassword, CheckRepeatPasswordInput } from '../routes/index/users';
|
||||||
@@ -636,6 +636,9 @@ export default class UserModel extends BaseModel<User> {
|
|||||||
const user = this.formatValues(object);
|
const user = this.formatValues(object);
|
||||||
|
|
||||||
if (user.password) {
|
if (user.password) {
|
||||||
|
if (isHashedPassword(user.password)) {
|
||||||
|
throw new ErrorBadRequest(`Unable to save user because password already seems to be hashed. User id: ${user.id}`);
|
||||||
|
}
|
||||||
if (!options.skipValidation) this.validatePassword(user.password);
|
if (!options.skipValidation) this.validatePassword(user.password);
|
||||||
user.password = auth.hashPassword(user.password);
|
user.password = auth.hashPassword(user.password);
|
||||||
}
|
}
|
||||||
|
|||||||
19
packages/server/src/models/utils/user.test.ts
Normal file
19
packages/server/src/models/utils/user.test.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import { isHashedPassword } from './user';
|
||||||
|
|
||||||
|
describe('isHashedPassword', () => {
|
||||||
|
|
||||||
|
it('should be true if password starts with $2a$10', () => {
|
||||||
|
expect(isHashedPassword('$2a$10$LMKVPiNOWDZhtw9NizNIEuNGLsjOxQAcrwQJ0lnKuiaOtyFgZEnwO')).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it.each(
|
||||||
|
[
|
||||||
|
'password',
|
||||||
|
'123456',
|
||||||
|
'simple-password-that-takes-is-long',
|
||||||
|
'nuXUhqecx!RzK3wv6^xYaVEP%9fc$T%$E2k%9Q&TKvtDhR#2PUw3kA8KX3w2baAD8m#N9@52!DvfYn*X6hP#uAvpGF57*H9avcoePbR&4Q2XzckJnSW*EVm4G@a#YvnR',
|
||||||
|
]
|
||||||
|
)('should be false if password starts with $2a$10: %', (password) => {
|
||||||
|
expect(isHashedPassword(password)).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -37,3 +37,7 @@ export function totalSizeClass(user: User) {
|
|||||||
if (d >= .7) return 'is-warning';
|
if (d >= .7) return 'is-warning';
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const isHashedPassword = (password: string) => {
|
||||||
|
return password.startsWith('$2a$10');
|
||||||
|
};
|
||||||
|
|||||||
18
packages/server/src/utils/auth.test.ts
Normal file
18
packages/server/src/utils/auth.test.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import { hashPassword } from './auth';
|
||||||
|
|
||||||
|
describe('hashPassword', () => {
|
||||||
|
|
||||||
|
it.each(
|
||||||
|
[
|
||||||
|
'password',
|
||||||
|
'123456',
|
||||||
|
'simple-password-that-takes-is-long',
|
||||||
|
'nuXUhqecx!RzK3wv6^xYaVEP%9fc$T%$E2k%9Q&TKvtDhR#2PUw3kA8KX3w2baAD8m#N9@52!DvfYn*X6hP#uAvpGF57*H9avcoePbR&4Q2XzckJnSW*EVm4G@a#YvnR',
|
||||||
|
'$2a$10',
|
||||||
|
'$2a$10$LMKVPiNOWDZhtw9NizNIEuNGLsjOxQAcrwQJ0lnKuiaOtyFgZEnwO',
|
||||||
|
]
|
||||||
|
)('should return a string that starts with $2a$10 for the password: %', async (plainText) => {
|
||||||
|
expect(hashPassword(plainText).startsWith('$2a$10')).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user