0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2024-12-30 20:33:54 -05:00

refactor(core,schemas): rename password encryption to password digest

This commit is contained in:
wangsijie 2024-03-18 11:07:19 +08:00
parent 2ae8c112f5
commit 5e72e9fd5c
No known key found for this signature in database
GPG key ID: C72642FE24F7D42B
19 changed files with 136 additions and 120 deletions

View file

@ -1,5 +1,5 @@
import type { User } from '@logto/schemas';
import { MfaFactor, userInfoSelectFields, UsersPasswordEncryptionMethod } from '@logto/schemas';
import { MfaFactor, userInfoSelectFields, UsersPasswordAlgorithm } from '@logto/schemas';
import { pick } from '@silverhand/essentials';
export const mockUser: User = {
@ -8,9 +8,9 @@ export const mockUser: User = {
username: 'foo',
primaryEmail: 'foo@logto.io',
primaryPhone: '111111',
passwordEncrypted:
passwordDigest:
'$argon2i$v=19$m=4096,t=256,p=1$SYD0xSoVR8l+CN63Nz8fGw$ln5T09X9u4yd0DwLBKnlNV/eUHxwSWo32scw40ov4kI',
passwordEncryptionMethod: UsersPasswordEncryptionMethod.Argon2i,
passwordAlgorithm: UsersPasswordAlgorithm.Argon2i,
name: null,
avatar: null,
identities: {
@ -58,15 +58,15 @@ export const mockUserWithMfaVerifications: User = {
export const mockUserResponse = pick(mockUser, ...userInfoSelectFields);
export const mockPasswordEncrypted = 'a1b2c3';
export const mockPasswordDigest = 'a1b2c3';
export const mockUserWithPassword: User = {
tenantId: 'fake_tenant',
id: 'id',
username: 'username',
primaryEmail: 'foo@logto.io',
primaryPhone: '111111',
passwordEncrypted: mockPasswordEncrypted,
passwordEncryptionMethod: UsersPasswordEncryptionMethod.Argon2i,
passwordDigest: mockPasswordDigest,
passwordAlgorithm: UsersPasswordAlgorithm.Argon2i,
name: null,
avatar: null,
identities: {
@ -90,8 +90,8 @@ export const mockUserList: User[] = [
username: 'foo1',
primaryEmail: 'foo1@logto.io',
primaryPhone: '111111',
passwordEncrypted: null,
passwordEncryptionMethod: null,
passwordDigest: null,
passwordAlgorithm: null,
name: null,
avatar: null,
identities: {},
@ -111,8 +111,8 @@ export const mockUserList: User[] = [
username: 'foo2',
primaryEmail: 'foo2@logto.io',
primaryPhone: '111111',
passwordEncrypted: null,
passwordEncryptionMethod: null,
passwordDigest: null,
passwordAlgorithm: null,
name: null,
avatar: null,
identities: {},
@ -132,8 +132,8 @@ export const mockUserList: User[] = [
username: 'foo3',
primaryEmail: 'foo3@logto.io',
primaryPhone: '111111',
passwordEncrypted: null,
passwordEncryptionMethod: null,
passwordDigest: null,
passwordAlgorithm: null,
name: null,
avatar: null,
identities: {},
@ -153,8 +153,8 @@ export const mockUserList: User[] = [
username: 'bar1',
primaryEmail: 'bar1@logto.io',
primaryPhone: '111111',
passwordEncrypted: null,
passwordEncryptionMethod: null,
passwordDigest: null,
passwordAlgorithm: null,
name: null,
avatar: null,
identities: {},
@ -174,8 +174,8 @@ export const mockUserList: User[] = [
username: 'bar2',
primaryEmail: 'bar2@logto.io',
primaryPhone: '111111',
passwordEncrypted: null,
passwordEncryptionMethod: null,
passwordDigest: null,
passwordAlgorithm: null,
name: null,
avatar: null,
identities: {},

View file

@ -1,4 +1,4 @@
import { MfaFactor, UsersPasswordEncryptionMethod } from '@logto/schemas';
import { MfaFactor, UsersPasswordAlgorithm } from '@logto/schemas';
import { createMockUtils } from '@logto/shared/esm';
import { mockResource, mockAdminUserRole, mockScope } from '#src/__mocks__/index.js';
@ -88,10 +88,10 @@ describe('generateUserId()', () => {
});
describe('encryptUserPassword()', () => {
it('generates salt, encrypted and method', async () => {
const { passwordEncryptionMethod, passwordEncrypted } = await encryptUserPassword('password');
expect(passwordEncryptionMethod).toEqual(UsersPasswordEncryptionMethod.Argon2i);
expect(passwordEncrypted).toContain('argon2');
it('generates salt, digest and method', async () => {
const { passwordAlgorithm, passwordDigest } = await encryptUserPassword('password');
expect(passwordAlgorithm).toEqual(UsersPasswordAlgorithm.Argon2i);
expect(passwordDigest).toContain('argon2');
});
});
@ -113,8 +113,8 @@ describe('verifyUserPassword()', () => {
describe('MD5', () => {
const user = {
...mockUser,
passwordEncrypted: '5f4dcc3b5aa765d61d8327deb882cf99',
passwordEncryptionMethod: UsersPasswordEncryptionMethod.MD5,
passwordDigest: '5f4dcc3b5aa765d61d8327deb882cf99',
passwordAlgorithm: UsersPasswordAlgorithm.MD5,
};
it('resolves when password is correct', async () => {
await expect(verifyUserPassword(user, 'password')).resolves.not.toThrowError();
@ -130,8 +130,8 @@ describe('verifyUserPassword()', () => {
describe('SHA1', () => {
const user = {
...mockUser,
passwordEncrypted: '5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8',
passwordEncryptionMethod: UsersPasswordEncryptionMethod.SHA1,
passwordDigest: '5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8',
passwordAlgorithm: UsersPasswordAlgorithm.SHA1,
};
it('resolves when password is correct', async () => {
await expect(verifyUserPassword(user, 'password')).resolves.not.toThrowError();
@ -147,8 +147,8 @@ describe('verifyUserPassword()', () => {
describe('SHA256', () => {
const user = {
...mockUser,
passwordEncrypted: '5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8',
passwordEncryptionMethod: UsersPasswordEncryptionMethod.SHA256,
passwordDigest: '5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8',
passwordAlgorithm: UsersPasswordAlgorithm.SHA256,
};
it('resolves when password is correct', async () => {
await expect(verifyUserPassword(user, 'password')).resolves.not.toThrowError();
@ -164,8 +164,8 @@ describe('verifyUserPassword()', () => {
describe('Bcrypt', () => {
const user = {
...mockUser,
passwordEncrypted: '$2a$12$WQMqTfbtcZFBC1C1u8wpie6lXOSciUr5kk/8yEydoIMKltb9UKJ.6',
passwordEncryptionMethod: UsersPasswordEncryptionMethod.Bcrypt,
passwordDigest: '$2a$12$WQMqTfbtcZFBC1C1u8wpie6lXOSciUr5kk/8yEydoIMKltb9UKJ.6',
passwordAlgorithm: UsersPasswordAlgorithm.Bcrypt,
};
it('resolves when password is correct', async () => {
await expect(verifyUserPassword(user, 'password')).resolves.not.toThrowError();
@ -181,15 +181,15 @@ describe('verifyUserPassword()', () => {
describe('Migrate other algorithms to Argon2', () => {
const user = {
...mockUser,
passwordEncrypted: '5f4dcc3b5aa765d61d8327deb882cf99',
passwordEncryptionMethod: UsersPasswordEncryptionMethod.MD5,
passwordDigest: '5f4dcc3b5aa765d61d8327deb882cf99',
passwordAlgorithm: UsersPasswordAlgorithm.MD5,
};
it('migrates password to Argon2', async () => {
await verifyUserPassword(user, 'password');
expect(updateUserById).toHaveBeenCalledWith(user.id, {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
passwordEncrypted: expect.stringContaining('argon2'),
passwordEncryptionMethod: UsersPasswordEncryptionMethod.Argon2i,
passwordDigest: expect.stringContaining('argon2'),
passwordAlgorithm: UsersPasswordAlgorithm.Argon2i,
});
});
});

View file

@ -1,5 +1,5 @@
import type { User, CreateUser, Scope, BindMfa, MfaVerification } from '@logto/schemas';
import { MfaFactor, Users, UsersPasswordEncryptionMethod } from '@logto/schemas';
import { MfaFactor, Users, UsersPasswordAlgorithm } from '@logto/schemas';
import { generateStandardShortId, generateStandardId } from '@logto/shared';
import type { Nullable } from '@silverhand/essentials';
import { deduplicate } from '@silverhand/essentials';
@ -18,13 +18,13 @@ import type { OmitAutoSetFields } from '#src/utils/sql.js';
export const encryptUserPassword = async (
password: string
): Promise<{
passwordEncrypted: string;
passwordEncryptionMethod: UsersPasswordEncryptionMethod;
passwordDigest: string;
passwordAlgorithm: UsersPasswordAlgorithm;
}> => {
const passwordEncryptionMethod = UsersPasswordEncryptionMethod.Argon2i;
const passwordEncrypted = await encryptPassword(password, passwordEncryptionMethod);
const passwordAlgorithm = UsersPasswordAlgorithm.Argon2i;
const passwordDigest = await encryptPassword(password, passwordAlgorithm);
return { passwordEncrypted, passwordEncryptionMethod };
return { passwordDigest, passwordAlgorithm };
};
/**
@ -199,45 +199,45 @@ export const createUserLibrary = (queries: Queries) => {
const verifyUserPassword = async (user: Nullable<User>, password: string): Promise<User> => {
assertThat(user, new RequestError({ code: 'session.invalid_credentials', status: 422 }));
const { passwordEncrypted, passwordEncryptionMethod, id } = user;
const { passwordDigest, passwordAlgorithm, id } = user;
assertThat(
passwordEncrypted && passwordEncryptionMethod,
passwordDigest && passwordAlgorithm,
new RequestError({ code: 'session.invalid_credentials', status: 422 })
);
switch (passwordEncryptionMethod) {
case UsersPasswordEncryptionMethod.Argon2i: {
const result = await argon2Verify({ password, hash: passwordEncrypted });
switch (passwordAlgorithm) {
case UsersPasswordAlgorithm.Argon2i: {
const result = await argon2Verify({ password, hash: passwordDigest });
assertThat(result, new RequestError({ code: 'session.invalid_credentials', status: 422 }));
break;
}
case UsersPasswordEncryptionMethod.MD5: {
const expectedEncrypted = await md5(password);
case UsersPasswordAlgorithm.MD5: {
const expectedDigest = await md5(password);
assertThat(
expectedEncrypted === passwordEncrypted,
expectedDigest === passwordDigest,
new RequestError({ code: 'session.invalid_credentials', status: 422 })
);
break;
}
case UsersPasswordEncryptionMethod.SHA1: {
const expectedEncrypted = await sha1(password);
case UsersPasswordAlgorithm.SHA1: {
const expectedDigest = await sha1(password);
assertThat(
expectedEncrypted === passwordEncrypted,
expectedDigest === passwordDigest,
new RequestError({ code: 'session.invalid_credentials', status: 422 })
);
break;
}
case UsersPasswordEncryptionMethod.SHA256: {
const expectedEncrypted = await sha256(password);
case UsersPasswordAlgorithm.SHA256: {
const expectedDigest = await sha256(password);
assertThat(
expectedEncrypted === passwordEncrypted,
expectedDigest === passwordDigest,
new RequestError({ code: 'session.invalid_credentials', status: 422 })
);
break;
}
case UsersPasswordEncryptionMethod.Bcrypt: {
const result = await bcryptVerify({ password, hash: passwordEncrypted });
case UsersPasswordAlgorithm.Bcrypt: {
const result = await bcryptVerify({ password, hash: passwordDigest });
assertThat(result, new RequestError({ code: 'session.invalid_credentials', status: 422 }));
break;
}
@ -247,12 +247,12 @@ export const createUserLibrary = (queries: Queries) => {
}
// Migrate password to default algorithm: argon2i
if (passwordEncryptionMethod !== UsersPasswordEncryptionMethod.Argon2i) {
const { passwordEncrypted: newEncrypted, passwordEncryptionMethod: newMethod } =
if (passwordAlgorithm !== UsersPasswordAlgorithm.Argon2i) {
const { passwordDigest: newDigest, passwordAlgorithm: newAlgorithm } =
await encryptUserPassword(password);
return updateUserById(id, {
passwordEncrypted: newEncrypted,
passwordEncryptionMethod: newMethod,
passwordDigest: newDigest,
passwordAlgorithm: newAlgorithm,
});
}

View file

@ -32,7 +32,7 @@ export default function userRoutes<T extends AuthedMeRouter>(
const responseData = {
...pick(user, ...userInfoSelectFields),
...conditional(user.passwordEncrypted && { hasPassword: Boolean(user.passwordEncrypted) }),
...conditional(user.passwordDigest && { hasPassword: Boolean(user.passwordDigest) }),
};
ctx.body = responseData;
@ -128,16 +128,16 @@ export default function userRoutes<T extends AuthedMeRouter>(
const { id: userId } = ctx.auth;
const { password } = ctx.guard.body;
const { isSuspended, passwordEncrypted: oldPasswordEncrypted } = await findUserById(userId);
const { isSuspended, passwordDigest: oldPasswordDigest } = await findUserById(userId);
assertThat(!isSuspended, new RequestError({ code: 'user.suspended', status: 401 }));
if (oldPasswordEncrypted) {
if (oldPasswordDigest) {
await checkVerificationStatus(userId);
}
const { passwordEncrypted, passwordEncryptionMethod } = await encryptUserPassword(password);
await updateUserById(userId, { passwordEncrypted, passwordEncryptionMethod });
const { passwordDigest, passwordAlgorithm } = await encryptUserPassword(password);
await updateUserById(userId, { passwordDigest, passwordAlgorithm });
ctx.status = 204;

View file

@ -1,5 +1,5 @@
import type { CreateUser, Role, SignInExperience, User } from '@logto/schemas';
import { RoleType, UsersPasswordEncryptionMethod } from '@logto/schemas';
import { RoleType, UsersPasswordAlgorithm } from '@logto/schemas';
import { createMockUtils, pickDefault } from '@logto/shared/esm';
import { removeUndefinedKeys } from '@silverhand/essentials';
@ -71,8 +71,8 @@ const { hasUser, findUserById, updateUserById, deleteUserIdentity, deleteUserByI
const { encryptUserPassword } = await mockEsmWithActual('#src/libraries/user.js', () => ({
encryptUserPassword: jest.fn(() => ({
passwordEncrypted: 'password',
passwordEncryptionMethod: 'Argon2i',
passwordDigest: 'password',
passwordAlgorithm: 'Argon2i',
})),
}));
@ -143,7 +143,7 @@ describe('adminUserRoutes', () => {
username,
name,
passwordDigest: '5f4dcc3b5aa765d61d8327deb882cf99',
passwordAlgorithm: UsersPasswordEncryptionMethod.MD5,
passwordAlgorithm: UsersPasswordAlgorithm.MD5,
})
).resolves.toHaveProperty('status', 200);
});
@ -365,7 +365,7 @@ describe('adminUserRoutes', () => {
});
it('GET /users/:userId/has-password should return false if user does not have password', async () => {
findUserById.mockImplementationOnce(async () => ({ ...mockUser, passwordEncrypted: null }));
findUserById.mockImplementationOnce(async () => ({ ...mockUser, passwordDigest: null }));
const response = await userRequest.get(`/users/foo/has-password`);
expect(response.status).toEqual(200);
expect(response.body).toEqual({ hasPassword: false });

View file

@ -1,6 +1,6 @@
import { emailRegEx, phoneRegEx, usernameRegEx } from '@logto/core-kit';
import {
UsersPasswordEncryptionMethod,
UsersPasswordAlgorithm,
jsonObjectGuard,
userInfoSelectFields,
userProfileGuard,
@ -144,7 +144,7 @@ export default function adminUserBasicsRoutes<T extends AuthedRouter>(...args: R
username: string().regex(usernameRegEx),
password: string().min(1),
passwordDigest: string(),
passwordAlgorithm: nativeEnum(UsersPasswordEncryptionMethod),
passwordAlgorithm: nativeEnum(UsersPasswordAlgorithm),
name: string(),
avatar: string().url().or(literal('')).nullable(),
customData: jsonObjectGuard,
@ -203,8 +203,8 @@ export default function adminUserBasicsRoutes<T extends AuthedRouter>(...args: R
...conditional(password && (await encryptUserPassword(password))),
...conditional(
passwordDigest && {
passwordEncrypted: passwordDigest,
passwordEncryptionMethod: passwordAlgorithm,
passwordDigest,
passwordAlgorithm,
}
),
...conditional(profile && { profile }),
@ -266,11 +266,11 @@ export default function adminUserBasicsRoutes<T extends AuthedRouter>(...args: R
await findUserById(userId);
const { passwordEncrypted, passwordEncryptionMethod } = await encryptUserPassword(password);
const { passwordDigest, passwordAlgorithm } = await encryptUserPassword(password);
const user = await updateUserById(userId, {
passwordEncrypted,
passwordEncryptionMethod,
passwordDigest,
passwordAlgorithm,
});
ctx.body = pick(user, ...userInfoSelectFields);
@ -313,7 +313,7 @@ export default function adminUserBasicsRoutes<T extends AuthedRouter>(...args: R
const user = await findUserById(userId);
ctx.body = {
hasPassword: Boolean(user.passwordEncrypted),
hasPassword: Boolean(user.passwordDigest),
};
return next();

View file

@ -26,8 +26,8 @@ const { assignInteractionResults } = mockEsm('#src/libraries/session.js', () =>
mockEsm('#src/libraries/user.js', () => ({
encryptUserPassword: jest.fn().mockResolvedValue({
passwordEncrypted: 'passwordEncrypted',
passwordEncryptionMethod: 'plain',
passwordDigest: 'passwordDigest',
passwordAlgorithm: 'plain',
}),
}));
@ -102,8 +102,8 @@ describe('submit action', () => {
username: 'username',
primaryPhone: '123456',
primaryEmail: 'email@logto.io',
passwordEncrypted: 'passwordEncrypted',
passwordEncryptionMethod: 'plain',
passwordDigest: 'passwordDigest',
passwordAlgorithm: 'plain',
identities: {
logto: { userId: userInfo.id, details: userInfo },
},

View file

@ -27,8 +27,8 @@ const { assignInteractionResults } = mockEsm('#src/libraries/session.js', () =>
const { encryptUserPassword } = mockEsm('#src/libraries/user.js', () => ({
encryptUserPassword: jest.fn().mockResolvedValue({
passwordEncrypted: 'passwordEncrypted',
passwordEncryptionMethod: 'plain',
passwordDigest: 'passwordDigest',
passwordAlgorithm: 'plain',
}),
}));
@ -103,8 +103,8 @@ describe('submit action', () => {
username: 'username',
primaryPhone: '123456',
primaryEmail: 'email@logto.io',
passwordEncrypted: 'passwordEncrypted',
passwordEncryptionMethod: 'plain',
passwordDigest: 'passwordDigest',
passwordAlgorithm: 'plain',
identities: {
logto: { userId: userInfo.id, details: userInfo },
},
@ -312,8 +312,8 @@ describe('submit action', () => {
expect(getLogtoConnectorById).toBeCalledWith('logto');
expect(updateUserById).toBeCalledWith('foo', {
passwordEncrypted: 'passwordEncrypted',
passwordEncryptionMethod: 'plain',
passwordDigest: 'passwordDigest',
passwordAlgorithm: 'plain',
identities: {
logto: { userId: userInfo.id, details: userInfo },
google: { userId: 'googleId', details: {} },
@ -341,8 +341,8 @@ describe('submit action', () => {
await submitInteraction(interaction, ctx, tenant);
expect(updateUserById).toBeCalledWith('foo', {
passwordEncrypted: 'passwordEncrypted',
passwordEncryptionMethod: 'plain',
passwordDigest: 'passwordDigest',
passwordAlgorithm: 'plain',
identities: {
logto: { userId: userInfo.id, details: userInfo },
google: { userId: 'googleId', details: {} },
@ -394,8 +394,8 @@ describe('submit action', () => {
expect(encryptUserPassword).toBeCalledWith('password');
expect(updateUserById).toBeCalledWith('foo', {
passwordEncrypted: 'passwordEncrypted',
passwordEncryptionMethod: 'plain',
passwordDigest: 'passwordDigest',
passwordAlgorithm: 'plain',
});
expect(assignInteractionResults).not.toBeCalled();

View file

@ -248,11 +248,9 @@ export default async function submitInteraction(
}
// Forgot Password
const { passwordEncrypted, passwordEncryptionMethod } = await encryptUserPassword(
profile.password
);
const { passwordDigest, passwordAlgorithm } = await encryptUserPassword(profile.password);
await updateUserById(accountId, { passwordEncrypted, passwordEncryptionMethod });
await updateUserById(accountId, { passwordDigest, passwordAlgorithm });
ctx.assignInteractionHookResult({ userId: accountId });
await clearInteractionStorage(ctx, provider);
ctx.status = 204;

View file

@ -22,8 +22,8 @@ export const isSocialIdentifier = (
// Social identities can take place the role of password
export const isUserPasswordSet = ({
passwordEncrypted,
passwordDigest,
identities,
}: Pick<User, 'passwordEncrypted' | 'identities'>): boolean => {
return Boolean(passwordEncrypted) || Object.keys(identities).length > 0;
}: Pick<User, 'passwordDigest' | 'identities'>): boolean => {
return Boolean(passwordDigest) || Object.keys(identities).length > 0;
};

View file

@ -9,7 +9,7 @@ import type { Identifier } from '../types/index.js';
const { jest } = import.meta;
const { mockEsm } = createMockUtils(jest);
const findUserById = jest.fn().mockResolvedValue({ id: 'foo', passwordEncrypted: 'passwordHash' });
const findUserById = jest.fn().mockResolvedValue({ id: 'foo', passwordDigest: 'passwordHash' });
const tenantContext = new MockTenant(undefined, { users: { findUserById } });

View file

@ -192,11 +192,11 @@ export default async function verifyProfile(
const passwordProfile = passwordProfileResult.data;
const { passwordEncrypted: oldPasswordEncrypted } = await findUserById(accountId);
const { passwordDigest: oldPasswordDigest } = await findUserById(accountId);
assertThat(
!oldPasswordEncrypted ||
!(await argon2Verify({ password: passwordProfile.password, hash: oldPasswordEncrypted })),
!oldPasswordDigest ||
!(await argon2Verify({ password: passwordProfile.password, hash: oldPasswordDigest })),
new RequestError({ code: 'user.same_password', status: 422 })
);

View file

@ -1,6 +1,6 @@
import crypto from 'node:crypto';
import { UsersPasswordEncryptionMethod } from '@logto/schemas';
import { UsersPasswordAlgorithm } from '@logto/schemas';
import { argon2i } from 'hash-wasm';
import RequestError from '#src/errors/RequestError/index.js';
@ -8,10 +8,10 @@ import assertThat from '#src/utils/assert-that.js';
export const encryptPassword = async (
password: string,
method: UsersPasswordEncryptionMethod
method: UsersPasswordAlgorithm
): Promise<string> => {
assertThat(
method === UsersPasswordEncryptionMethod.Argon2i,
method === UsersPasswordAlgorithm.Argon2i,
new RequestError({ code: 'password.unsupported_encryption_method', method })
);

View file

@ -6,7 +6,7 @@ import type {
Role,
User,
UserSsoIdentity,
UsersPasswordEncryptionMethod,
UsersPasswordAlgorithm,
} from '@logto/schemas';
import { conditional } from '@silverhand/essentials';
@ -19,7 +19,7 @@ export type CreateUserPayload = Partial<{
password: string;
name: string;
passwordDigest: string;
passwordAlgorithm: UsersPasswordEncryptionMethod;
passwordAlgorithm: UsersPasswordAlgorithm;
}>;
export const createUser = async (payload: CreateUserPayload = {}) =>

View file

@ -2,11 +2,7 @@ import fs from 'node:fs/promises';
import { createServer, type RequestListener } from 'node:http';
import { mockConnectorFilePaths, type SendMessagePayload } from '@logto/connector-kit';
import {
type UserProfile,
type JsonObject,
type UsersPasswordEncryptionMethod,
} from '@logto/schemas';
import { type UserProfile, type JsonObject, type UsersPasswordAlgorithm } from '@logto/schemas';
import { RequestError } from 'got';
import { createUser } from '#src/api/index.js';
@ -20,7 +16,7 @@ export const createUserByAdmin = async (
primaryPhone?: string;
name?: string;
passwordDigest?: string;
passwordAlgorithm?: UsersPasswordEncryptionMethod;
passwordAlgorithm?: UsersPasswordAlgorithm;
customData?: JsonObject;
profile?: UserProfile;
} = {}

View file

@ -1,6 +1,6 @@
import crypto from 'node:crypto';
import { UsersPasswordEncryptionMethod, ConnectorType } from '@logto/schemas';
import { UsersPasswordAlgorithm, ConnectorType } from '@logto/schemas';
import { HTTPError } from 'got';
import {
@ -61,7 +61,7 @@ describe('admin console user management', () => {
it('should create user with password digest successfully', async () => {
const user = await createUserByAdmin({
passwordDigest: '5f4dcc3b5aa765d61d8327deb882cf99',
passwordAlgorithm: UsersPasswordEncryptionMethod.MD5,
passwordAlgorithm: UsersPasswordAlgorithm.MD5,
});
await expect(verifyUserPassword(user.id, 'password')).resolves.not.toThrow();

View file

@ -2,7 +2,7 @@ import {
InteractionEvent,
ConnectorType,
SignInIdentifier,
UsersPasswordEncryptionMethod,
UsersPasswordAlgorithm,
} from '@logto/schemas';
import {
@ -64,7 +64,7 @@ describe('Sign-in flow using password identifiers', () => {
const user = await createUserByAdmin({
username,
passwordDigest: '5f4dcc3b5aa765d61d8327deb882cf99',
passwordAlgorithm: UsersPasswordEncryptionMethod.MD5,
passwordAlgorithm: UsersPasswordAlgorithm.MD5,
});
const client = await initClient();

View file

@ -0,0 +1,22 @@
import { sql } from '@silverhand/slonik';
import type { AlterationScript } from '../lib/types/alteration.js';
const alteration: AlterationScript = {
up: async (pool) => {
await pool.query(sql`
alter table users rename column password_encrypted to password_digest;
alter table users rename column password_encryption_method to password_algorithm;
alter type users_password_encryption_method rename to users_password_algorithm;
`);
},
down: async (pool) => {
await pool.query(sql`
alter type users_password_algorithm rename to users_password_encryption_method;
alter table users rename column password_digest to password_encrypted;
alter table users rename column password_algorithm to password_encryption_method;
`);
},
};
export default alteration;

View file

@ -1,6 +1,6 @@
/* init_order = 1 */
create type users_password_encryption_method as enum ('Argon2i', 'SHA1', 'SHA256', 'MD5', 'Bcrypt');
create type users_password_algorithm as enum ('Argon2i', 'SHA1', 'SHA256', 'MD5', 'Bcrypt');
create table users (
tenant_id varchar(21) not null
@ -9,8 +9,8 @@ create table users (
username varchar(128),
primary_email varchar(128),
primary_phone varchar(128),
password_encrypted varchar(128),
password_encryption_method users_password_encryption_method,
password_digest varchar(128),
password_algorithm users_password_algorithm,
name varchar(128),
/** The URL that points to the user's profile picture. Mapped to OpenID Connect's `picture` claim. */
avatar varchar(2048),