From 4f32ad3a511985b1ccb8706cff3b604c86a7d50b Mon Sep 17 00:00:00 2001 From: simeng-li Date: Mon, 20 Jun 2022 11:32:50 +0800 Subject: [PATCH] feat(core): register with admin role (#1140) * feat(core): register with admin role register user with admin role * fix(core): cr fix cr fix --- .../core/src/routes/session/session.test.ts | 41 +++++++++++++++++++ packages/core/src/routes/session/session.ts | 16 +++++++- 2 files changed, 55 insertions(+), 2 deletions(-) diff --git a/packages/core/src/routes/session/session.test.ts b/packages/core/src/routes/session/session.test.ts index d26364463..4cc38eb74 100644 --- a/packages/core/src/routes/session/session.test.ts +++ b/packages/core/src/routes/session/session.test.ts @@ -1,4 +1,5 @@ import { User } from '@logto/schemas'; +import { adminConsoleApplicationId } from '@logto/schemas/lib/seeds'; import { Provider } from 'oidc-provider'; import { mockUser } from '@/__mocks__'; @@ -29,6 +30,8 @@ jest.mock('@/lib/user', () => ({ const insertUser = jest.fn(async (..._args: unknown[]) => ({ id: 'id' })); const findUserById = jest.fn(async (): Promise => mockUser); const updateUserById = jest.fn(async (..._args: unknown[]) => ({ id: 'id' })); +const hasActiveUsers = jest.fn(async () => true); + jest.mock('@/queries/user', () => ({ findUserById: async () => findUserById(), findUserByIdentity: async () => ({ id: 'id', identities: {} }), @@ -41,6 +44,7 @@ jest.mock('@/queries/user', () => ({ connectorId === 'connectorId' && userId === 'id', hasUserWithPhone: async (phone: string) => phone === '13000000000', hasUserWithEmail: async (email: string) => email === 'a@a.com', + hasActiveUsers: async () => hasActiveUsers(), })); const grantSave = jest.fn(async () => 'finalGrantId'); @@ -149,6 +153,8 @@ describe('sessionRoutes', () => { describe('POST /session/register/username-password', () => { it('assign result and redirect', async () => { + interactionDetails.mockResolvedValueOnce({ params: {} }); + const response = await sessionRequest .post('/session/register/username-password') .send({ username: 'username', password: 'password' }); @@ -158,6 +164,7 @@ describe('sessionRoutes', () => { username: 'username', passwordEncrypted: 'password_user1', passwordEncryptionMethod: 'Argon2i', + roleNames: [], }) ); expect(response.body).toHaveProperty('redirectTo'); @@ -169,6 +176,40 @@ describe('sessionRoutes', () => { ); }); + it('register user with admin role for admin console if no active user found', async () => { + interactionDetails.mockResolvedValueOnce({ + params: { client_id: adminConsoleApplicationId }, + }); + + hasActiveUsers.mockResolvedValueOnce(false); + + await sessionRequest + .post('/session/register/username-password') + .send({ username: 'username', password: 'password' }); + + expect(insertUser).toHaveBeenCalledWith( + expect.objectContaining({ + roleNames: ['admin'], + }) + ); + }); + + it('should not register user with admin role for admin console if any active user found', async () => { + interactionDetails.mockResolvedValueOnce({ + params: { client_id: adminConsoleApplicationId }, + }); + + await sessionRequest + .post('/session/register/username-password') + .send({ username: 'username', password: 'password' }); + + expect(insertUser).toHaveBeenCalledWith( + expect.objectContaining({ + roleNames: [], + }) + ); + }); + it('throw error if username not valid', async () => { const usernameStartedWithNumber = '1username'; const response = await sessionRequest diff --git a/packages/core/src/routes/session/session.ts b/packages/core/src/routes/session/session.ts index f2a497e77..b8f5fa607 100644 --- a/packages/core/src/routes/session/session.ts +++ b/packages/core/src/routes/session/session.ts @@ -1,6 +1,8 @@ import path from 'path'; import { LogtoErrorCode } from '@logto/phrases'; +import { UserRole } from '@logto/schemas'; +import { adminConsoleApplicationId } from '@logto/schemas/lib/seeds'; import { passwordRegEx, usernameRegEx } from '@logto/shared'; import { conditional } from '@silverhand/essentials'; import { Provider } from 'oidc-provider'; @@ -15,7 +17,7 @@ import { updateLastSignInAt, } from '@/lib/user'; import koaGuard from '@/middleware/koa-guard'; -import { hasUser, insertUser } from '@/queries/user'; +import { hasUser, insertUser, hasActiveUsers } from '@/queries/user'; import assertThat from '@/utils/assert-that'; import { AnonymousRouter } from '../types'; @@ -120,8 +122,17 @@ export default function sessionRoutes(router: T, prov }) ); + const { + params: { client_id }, + } = await provider.interactionDetails(ctx.req, ctx.res); + + const createAdminUser = + String(client_id) === adminConsoleApplicationId && !(await hasActiveUsers()); + const roleNames = createAdminUser ? [UserRole.Admin] : []; + const id = await generateUserId(); - ctx.log(type, { userId: id }); + + ctx.log(type, { userId: id, roleNames }); const { passwordEncrypted, passwordEncryptionMethod } = await encryptUserPassword(password); @@ -130,6 +141,7 @@ export default function sessionRoutes(router: T, prov username, passwordEncrypted, passwordEncryptionMethod, + roleNames, }); await updateLastSignInAt(id); await assignInteractionResults(ctx, provider, { login: { accountId: id } });