diff --git a/.changeset/popular-wasps-begin.md b/.changeset/popular-wasps-begin.md new file mode 100644 index 000000000..718bcfcc6 --- /dev/null +++ b/.changeset/popular-wasps-begin.md @@ -0,0 +1,5 @@ +--- +"@logto/core": patch +--- + +Provide management API to detect if a user has set the password. diff --git a/packages/core/src/routes/admin-user.test.ts b/packages/core/src/routes/admin-user.test.ts index 7d63a8381..9fedf640b 100644 --- a/packages/core/src/routes/admin-user.test.ts +++ b/packages/core/src/routes/admin-user.test.ts @@ -1,3 +1,4 @@ +/* eslint-disable max-lines */ import type { CreateUser, Role, SignInExperience, User } from '@logto/schemas'; import { userInfoSelectFields } from '@logto/schemas'; import { createMockUtils, pickDefault } from '@logto/shared/esm'; @@ -375,6 +376,19 @@ describe('adminUserRoutes', () => { expect(verifyUserPassword).toHaveBeenCalledWith(mockUser, password); }); + it('GET /users/:userId/has-password should return true if user has password', async () => { + const response = await userRequest.get(`/users/foo/has-password`); + expect(response.status).toEqual(200); + expect(response.body).toEqual({ hasPassword: true }); + }); + + it('GET /users/:userId/has-password should return false if user does not have password', async () => { + findUserById.mockImplementationOnce(async () => ({ ...mockUser, passwordEncrypted: null })); + const response = await userRequest.get(`/users/foo/has-password`); + expect(response.status).toEqual(200); + expect(response.body).toEqual({ hasPassword: false }); + }); + it('PATCH /users/:userId/is-suspended', async () => { const mockedUserId = 'foo'; const response = await userRequest @@ -460,3 +474,4 @@ describe('adminUserRoutes', () => { expect(deleteUserIdentity).toHaveBeenCalledWith(arbitraryUserId, arbitraryTarget); }); }); +/* eslint-enable max-lines */ diff --git a/packages/core/src/routes/admin-user.ts b/packages/core/src/routes/admin-user.ts index 89a0b64bb..be95b99f6 100644 --- a/packages/core/src/routes/admin-user.ts +++ b/packages/core/src/routes/admin-user.ts @@ -255,6 +255,25 @@ export default function adminUserRoutes( } ); + router.get( + '/users/:userId/has-password', + koaGuard({ + params: object({ userId: string() }), + response: object({ hasPassword: boolean() }), + status: [200], + }), + async (ctx, next) => { + const { userId } = ctx.guard.params; + const user = await findUserById(userId); + + ctx.body = { + hasPassword: Boolean(user.passwordEncrypted), + }; + + return next(); + } + ); + router.patch( '/users/:userId/is-suspended', koaGuard({