mirror of
https://github.com/logto-io/logto.git
synced 2024-12-16 20:26:19 -05:00
refactor(core): encryptUserPassword (#135)
This commit is contained in:
parent
7d7b8112f6
commit
3480d05366
4 changed files with 45 additions and 23 deletions
|
@ -1,6 +1,8 @@
|
||||||
|
import { PasswordEncryptionMethod } from '@logto/schemas';
|
||||||
|
|
||||||
import { hasUserWithId } from '@/queries/user';
|
import { hasUserWithId } from '@/queries/user';
|
||||||
|
|
||||||
import { generateUserId } from './user';
|
import { encryptUserPassword, generateUserId } from './user';
|
||||||
|
|
||||||
jest.mock('@/queries/user');
|
jest.mock('@/queries/user');
|
||||||
|
|
||||||
|
@ -48,3 +50,13 @@ describe('generateUserId()', () => {
|
||||||
expect(mockedHasUserWithId).toBeCalledTimes(11);
|
expect(mockedHasUserWithId).toBeCalledTimes(11);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('encryptUserPassword()', () => {
|
||||||
|
it('generates salt, encrypted and method', () => {
|
||||||
|
const { passwordEncryptionMethod, passwordEncrypted, passwordEncryptionSalt } =
|
||||||
|
encryptUserPassword('user-id', 'password');
|
||||||
|
expect(passwordEncryptionMethod).toEqual(PasswordEncryptionMethod.SaltAndPepper);
|
||||||
|
expect(passwordEncrypted).toHaveLength(64);
|
||||||
|
expect(passwordEncryptionSalt).toHaveLength(21);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
|
import { PasswordEncryptionMethod } from '@logto/schemas';
|
||||||
|
import { nanoid } from 'nanoid';
|
||||||
import pRetry from 'p-retry';
|
import pRetry from 'p-retry';
|
||||||
|
|
||||||
import { hasUserWithId } from '@/queries/user';
|
import { hasUserWithId } from '@/queries/user';
|
||||||
import { buildIdGenerator } from '@/utils/id';
|
import { buildIdGenerator } from '@/utils/id';
|
||||||
|
import { encryptPassword } from '@/utils/password';
|
||||||
|
|
||||||
const userId = buildIdGenerator(12);
|
const userId = buildIdGenerator(12);
|
||||||
|
|
||||||
|
@ -18,3 +21,23 @@ export const generateUserId = async (retries = 500) =>
|
||||||
},
|
},
|
||||||
{ retries, factor: 0 } // No need for exponential backoff
|
{ retries, factor: 0 } // No need for exponential backoff
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const encryptUserPassword = (
|
||||||
|
userId: string,
|
||||||
|
password: string
|
||||||
|
): {
|
||||||
|
passwordEncryptionSalt: string;
|
||||||
|
passwordEncrypted: string;
|
||||||
|
passwordEncryptionMethod: PasswordEncryptionMethod;
|
||||||
|
} => {
|
||||||
|
const passwordEncryptionSalt = nanoid();
|
||||||
|
const passwordEncryptionMethod = PasswordEncryptionMethod.SaltAndPepper;
|
||||||
|
const passwordEncrypted = encryptPassword(
|
||||||
|
userId,
|
||||||
|
password,
|
||||||
|
passwordEncryptionSalt,
|
||||||
|
passwordEncryptionMethod
|
||||||
|
);
|
||||||
|
|
||||||
|
return { passwordEncrypted, passwordEncryptionMethod, passwordEncryptionSalt };
|
||||||
|
};
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
import { PasswordEncryptionMethod, userInfoSelectFields } from '@logto/schemas';
|
import { userInfoSelectFields } from '@logto/schemas';
|
||||||
import pick from 'lodash.pick';
|
import pick from 'lodash.pick';
|
||||||
import { nanoid } from 'nanoid';
|
|
||||||
import { Provider } from 'oidc-provider';
|
import { Provider } from 'oidc-provider';
|
||||||
import { object, string } from 'zod';
|
import { object, string } from 'zod';
|
||||||
|
|
||||||
import RequestError from '@/errors/RequestError';
|
import RequestError from '@/errors/RequestError';
|
||||||
import { generateUserId } from '@/lib/user';
|
import { encryptUserPassword, generateUserId } from '@/lib/user';
|
||||||
import koaGuard from '@/middleware/koa-guard';
|
import koaGuard from '@/middleware/koa-guard';
|
||||||
import {
|
import {
|
||||||
deleteUserById,
|
deleteUserById,
|
||||||
|
@ -15,7 +14,6 @@ import {
|
||||||
insertUser,
|
insertUser,
|
||||||
updateUserById,
|
updateUserById,
|
||||||
} from '@/queries/user';
|
} from '@/queries/user';
|
||||||
import { encryptPassword } from '@/utils/password';
|
|
||||||
|
|
||||||
import { AnonymousRouter } from './types';
|
import { AnonymousRouter } from './types';
|
||||||
|
|
||||||
|
@ -36,14 +34,9 @@ export default function userRoutes<T extends AnonymousRouter>(router: T, provide
|
||||||
}
|
}
|
||||||
|
|
||||||
const id = await generateUserId();
|
const id = await generateUserId();
|
||||||
const passwordEncryptionSalt = nanoid();
|
|
||||||
const passwordEncryptionMethod = PasswordEncryptionMethod.SaltAndPepper;
|
const { passwordEncryptionSalt, passwordEncrypted, passwordEncryptionMethod } =
|
||||||
const passwordEncrypted = encryptPassword(
|
encryptUserPassword(id, password);
|
||||||
id,
|
|
||||||
password,
|
|
||||||
passwordEncryptionSalt,
|
|
||||||
passwordEncryptionMethod
|
|
||||||
);
|
|
||||||
|
|
||||||
await insertUser({
|
await insertUser({
|
||||||
id,
|
id,
|
||||||
|
@ -102,14 +95,7 @@ export default function userRoutes<T extends AnonymousRouter>(router: T, provide
|
||||||
|
|
||||||
await findUserById(userId);
|
await findUserById(userId);
|
||||||
|
|
||||||
const passwordEncryptionSalt = nanoid();
|
const { passwordEncrypted, passwordEncryptionSalt } = encryptUserPassword(userId, password);
|
||||||
const passwordEncryptionMethod = PasswordEncryptionMethod.SaltAndPepper;
|
|
||||||
const passwordEncrypted = encryptPassword(
|
|
||||||
userId,
|
|
||||||
password,
|
|
||||||
passwordEncryptionSalt,
|
|
||||||
passwordEncryptionMethod
|
|
||||||
);
|
|
||||||
|
|
||||||
await updateUserById(userId, {
|
await updateUserById(userId, {
|
||||||
passwordEncryptionSalt,
|
passwordEncryptionSalt,
|
||||||
|
|
|
@ -2,16 +2,17 @@ import { createHash } from 'crypto';
|
||||||
|
|
||||||
import { PasswordEncryptionMethod } from '@logto/schemas';
|
import { PasswordEncryptionMethod } from '@logto/schemas';
|
||||||
import { assertEnv, repeat } from '@silverhand/essentials';
|
import { assertEnv, repeat } from '@silverhand/essentials';
|
||||||
|
import { nanoid } from 'nanoid';
|
||||||
import { number, string } from 'zod';
|
import { number, string } from 'zod';
|
||||||
|
|
||||||
import assertThat from '@/utils/assert-that';
|
import assertThat from '@/utils/assert-that';
|
||||||
|
|
||||||
const peppers = string()
|
const peppers = string()
|
||||||
.array()
|
.array()
|
||||||
.parse(JSON.parse(assertEnv('PASSWORD_PEPPERS')));
|
.parse(process.env.NODE_ENV === 'test' ? [nanoid()] : JSON.parse(assertEnv('PASSWORD_PEPPERS')));
|
||||||
const iterationCount = number()
|
const iterationCount = number()
|
||||||
.min(100)
|
.min(100)
|
||||||
.parse(Number(assertEnv('PASSWORD_INTERATION_COUNT')));
|
.parse(process.env.NODE_ENV === 'test' ? 1000 : Number(assertEnv('PASSWORD_INTERATION_COUNT')));
|
||||||
|
|
||||||
export const encryptPassword = (
|
export const encryptPassword = (
|
||||||
id: string,
|
id: string,
|
||||||
|
|
Loading…
Reference in a new issue