mirror of
https://github.com/logto-io/logto.git
synced 2025-04-07 23:01:25 -05:00
feat(core): add management API to verify user password (#3680)
* feat(core): add management API to verify user password * chore: add changeset
This commit is contained in:
parent
e07355c42e
commit
7af8e9c9b1
3 changed files with 61 additions and 7 deletions
5
.changeset/new-windows-rescue.md
Normal file
5
.changeset/new-windows-rescue.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
"@logto/core": patch
|
||||
---
|
||||
|
||||
Add new management API `/users/:userId/password/verify` to help verify user password, which would be helpful when building custom profile or sign-in pages
|
|
@ -9,6 +9,7 @@ import {
|
|||
mockUserListResponse,
|
||||
mockUserResponse,
|
||||
} from '#src/__mocks__/index.js';
|
||||
import RequestError from '#src/errors/RequestError/index.js';
|
||||
import type Libraries from '#src/tenants/Libraries.js';
|
||||
import type Queries from '#src/tenants/Queries.js';
|
||||
import { MockTenant, type Partial2 } from '#src/test-utils/tenant.js';
|
||||
|
@ -85,12 +86,16 @@ const { revokeInstanceByUserId } = mockedQueries.oidcModelInstances;
|
|||
const { hasUser, findUserById, updateUserById, deleteUserIdentity, deleteUserById } =
|
||||
mockedQueries.users;
|
||||
|
||||
const { encryptUserPassword } = await mockEsmWithActual('#src/libraries/user.js', () => ({
|
||||
encryptUserPassword: jest.fn(() => ({
|
||||
passwordEncrypted: 'password',
|
||||
passwordEncryptionMethod: 'Argon2i',
|
||||
})),
|
||||
}));
|
||||
const { encryptUserPassword, verifyUserPassword } = await mockEsmWithActual(
|
||||
'#src/libraries/user.js',
|
||||
() => ({
|
||||
encryptUserPassword: jest.fn(() => ({
|
||||
passwordEncrypted: 'password',
|
||||
passwordEncryptionMethod: 'Argon2i',
|
||||
})),
|
||||
verifyUserPassword: jest.fn(),
|
||||
})
|
||||
);
|
||||
|
||||
const usersLibraries = {
|
||||
generateUserId: jest.fn(async () => 'fooId'),
|
||||
|
@ -347,6 +352,29 @@ describe('adminUserRoutes', () => {
|
|||
expect(updateUserById).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('POST /users/:userId/password/verify', async () => {
|
||||
const mockedUserId = 'foo';
|
||||
const password = '1234asd$';
|
||||
const response = await userRequest
|
||||
.post(`/users/${mockedUserId}/password/verify`)
|
||||
.send({ password });
|
||||
|
||||
expect(findUserById).toHaveBeenCalledWith(mockedUserId);
|
||||
expect(verifyUserPassword).toHaveBeenCalledWith(mockUser, password);
|
||||
expect(response.status).toEqual(204);
|
||||
});
|
||||
|
||||
it('POST /users/:userId/password/verify should throw if password is invalid', async () => {
|
||||
const password = 'invalidPassword';
|
||||
verifyUserPassword.mockImplementationOnce(async () => {
|
||||
throw new RequestError({ code: 'session.invalid_credentials', status: 422 });
|
||||
});
|
||||
await expect(
|
||||
userRequest.post(`/users/foo/password/verify`).send({ password })
|
||||
).resolves.toHaveProperty('status', 422);
|
||||
expect(verifyUserPassword).toHaveBeenCalledWith(mockUser, password);
|
||||
});
|
||||
|
||||
it('PATCH /users/:userId/is-suspended', async () => {
|
||||
const mockedUserId = 'foo';
|
||||
const response = await userRequest
|
||||
|
|
|
@ -4,7 +4,7 @@ import { conditional, has, pick, tryThat } from '@silverhand/essentials';
|
|||
import { boolean, literal, object, string } from 'zod';
|
||||
|
||||
import RequestError from '#src/errors/RequestError/index.js';
|
||||
import { encryptUserPassword } from '#src/libraries/user.js';
|
||||
import { encryptUserPassword, verifyUserPassword } from '#src/libraries/user.js';
|
||||
import koaGuard from '#src/middleware/koa-guard.js';
|
||||
import koaPagination from '#src/middleware/koa-pagination.js';
|
||||
import assertThat from '#src/utils/assert-that.js';
|
||||
|
@ -234,6 +234,27 @@ export default function adminUserRoutes<T extends AuthedRouter>(
|
|||
}
|
||||
);
|
||||
|
||||
router.post(
|
||||
'/users/:userId/password/verify',
|
||||
koaGuard({
|
||||
params: object({ userId: string() }),
|
||||
body: object({ password: string() }),
|
||||
}),
|
||||
async (ctx, next) => {
|
||||
const {
|
||||
params: { userId },
|
||||
body: { password },
|
||||
} = ctx.guard;
|
||||
|
||||
const user = await findUserById(userId);
|
||||
await verifyUserPassword(user, password);
|
||||
|
||||
ctx.status = 204;
|
||||
|
||||
return next();
|
||||
}
|
||||
);
|
||||
|
||||
router.patch(
|
||||
'/users/:userId/is-suspended',
|
||||
koaGuard({
|
||||
|
|
Loading…
Add table
Reference in a new issue