diff --git a/idp/src/infra/error/core.ts b/idp/src/infra/error/core.ts index 2905dc24c..9807e8a46 100644 --- a/idp/src/infra/error/core.ts +++ b/idp/src/infra/error/core.ts @@ -60,7 +60,6 @@ export type ErrorData = { message: string userMessage: string moreInfo: string - /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ error?: any } @@ -76,7 +75,6 @@ export type ErrorOptions = { code: ErrorCode message?: string userMessage?: string - /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ error?: any } diff --git a/idp/src/infra/error/validation.ts b/idp/src/infra/error/validation.ts index e86eb265c..64953d14c 100644 --- a/idp/src/infra/error/validation.ts +++ b/idp/src/infra/error/validation.ts @@ -73,19 +73,42 @@ export const password = z export const picture = z .string() .optional() - .refine((value) => { - if (!value) { - return true - } - try { - return Buffer.from(value, 'base64').length <= 3000000 - } catch { - return false - } - }, { message: 'Picture must be a valid Base64 string and <= 3MB' }) + .refine( + (value) => { + if (!value) { + return true + } + try { + return Buffer.from(value, 'base64').length <= 3000000 + } catch { + return false + } + }, + { message: 'Picture must be a valid Base64 string and <= 3MB' }, + ) + +export const email = z + .string() + .email() + .trim() + .max(255) + +export const fullName = z + .string() + .nonempty() + .trim() + .max(255) -export const email = z.string().email().trim().max(255) +export const token = z + .string() + .nonempty() -export const fullName = z.string().nonempty().trim().max(255) +export const page = z + .string() + .regex(/^\d+$/, 'Must be a numeric value') + .transform(Number) -export const token = z.string().nonempty() +export const size = z + .string() + .regex(/^\d+$/, 'Must be a numeric value') + .transform(Number) diff --git a/idp/src/infra/mail.ts b/idp/src/infra/mail.ts index a5bd4e288..72a027432 100644 --- a/idp/src/infra/mail.ts +++ b/idp/src/infra/mail.ts @@ -35,7 +35,6 @@ const transporter = nodemailer.createTransport({ export function sendTemplateMail( template: string, address: string, - /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ variables: Record, ) { const params = yaml.load( diff --git a/idp/src/token/service.ts b/idp/src/token/service.ts index 1d3d531ea..baae82018 100644 --- a/idp/src/token/service.ts +++ b/idp/src/token/service.ts @@ -7,7 +7,7 @@ // the Business Source License, use of this software will be governed // by the GNU Affero General Public License v3.0 only, included in the file // AGPL-3.0-only in the root of this repository. -import { decode, sign } from 'hono/jwt' +import { sign } from 'hono/jwt' import { getConfig } from '@/config/config.ts' import { newEmailNotConfirmedError, @@ -15,7 +15,6 @@ import { newInvalidUsernameOrPasswordError, newMissingFormParamError, newRefreshTokenExpiredError, - newUserIsNotAdminError, newUserSuspendedError, newUserTemporarilyLockedError, } from '@/infra/error/creators.ts' @@ -92,13 +91,6 @@ export async function exchange(options: TokenExchangeOptions): Promise { } } -export function checkAdmin(jwt: string) { - const { payload } = decode(jwt) - if (!payload.is_admin) { - throw newUserIsNotAdminError() - } -} - function validateParameters(options: TokenExchangeOptions) { if (!options.grant_type) { throw newMissingFormParamError('grant_type') diff --git a/idp/src/user/repo.ts b/idp/src/user/repo.ts index 6d5f8d35d..515a60036 100644 --- a/idp/src/user/repo.ts +++ b/idp/src/user/repo.ts @@ -95,7 +95,7 @@ class UserRepoImpl { } async list(page: number, size: number): Promise { - const { rows } = await client.queryArray( + const { rows } = await client.queryObject( `SELECT * FROM "user" ORDER BY create_time @@ -103,18 +103,18 @@ class UserRepoImpl { LIMIT $2`, [(page - 1) * size, size], ) - return this.mapList(rows) + return rows.map(this.mapRow) } async findMany(ids: string[]): Promise { - const { rows } = await client.queryArray( + const { rows } = await client.queryObject( `SELECT * FROM "user" WHERE id = ANY ($1) ORDER BY create_time`, [ids], ) - return this.mapList(rows) + return rows.map(this.mapRow) } async getCount(): Promise { @@ -255,7 +255,6 @@ class UserRepoImpl { return result[0].count > 1 } - /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ private mapRow(row: any): User { return { id: row.id, @@ -279,11 +278,6 @@ class UserRepoImpl { updateTime: row.update_time, } } - - /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ - private mapList(list: any): User[] { - return list.map(this.mapRow) - } } const userRepo: UserRepoImpl = new UserRepoImpl() diff --git a/idp/src/user/router.ts b/idp/src/user/router.ts index 001457ad3..bcbee6624 100644 --- a/idp/src/user/router.ts +++ b/idp/src/user/router.ts @@ -18,15 +18,17 @@ import { newInvalidJwtError, newMissingQueryParamError, newPictureNotFoundError, + newUserIsNotAdminError, } from '@/infra/error/creators.ts' import { email, fullName, handleValidationError, + page, password, + size, token, } from '@/infra/error/validation.ts' -import { checkAdmin } from '@/token/service.ts' import { deletePicture, deleteUser, @@ -194,17 +196,15 @@ router.get( 'query', z.object({ query: z.string().optional(), - page: z.string().regex(/^\d+$/, 'Must be a numeric value.').transform( - Number, - ), - size: z.string().regex(/^\d+$/, 'Must be a numeric value.').transform( - Number, - ), + page, + size, }), handleValidationError, ), async (c) => { - checkAdmin(c.req.header('Authorization')!) + if (!c.get('user').isAdmin) { + throw newUserIsNotAdminError() + } const { query, size, page } = c.req.valid('query') as UserListOptions return c.json(await list({ query, size, page })) }, @@ -218,7 +218,9 @@ router.post( handleValidationError, ), async (c) => { - checkAdmin(c.req.header('Authorization')!) + if (!c.get('user').isAdmin) { + throw newUserIsNotAdminError() + } const { id } = c.req.param() const body = c.req.valid('json') as UserSuspendOptions await suspendUser(id, body) @@ -234,7 +236,9 @@ router.post( handleValidationError, ), async (c) => { - checkAdmin(c.req.header('Authorization')!) + if (!c.get('user').isAdmin) { + throw newUserIsNotAdminError() + } const { id } = c.req.param() const body = c.req.valid('json') as UserMakeAdminOptions await makeAdminUser(id, body) @@ -243,7 +247,9 @@ router.post( ) router.get('/:id', async (c) => { - checkAdmin(c.req.header('Authorization')!) + if (!c.get('user').isAdmin) { + throw newUserIsNotAdminError() + } const { id } = c.req.param() return c.json(await getUserByAdmin(id)) }) diff --git a/idp/src/user/service.ts b/idp/src/user/service.ts index dcbbd662f..bcc23f817 100644 --- a/idp/src/user/service.ts +++ b/idp/src/user/service.ts @@ -151,8 +151,8 @@ export async function list({ size, page, }: UserListOptions): Promise { - if (query && query.length >= 3) { - const users = await meilisearch + if (query) { + const hits = await meilisearch .index(USER_SEARCH_INDEX) .search(query, { page: page, hitsPerPage: size }) .then((value) => { @@ -161,24 +161,18 @@ export async function list({ totalElements: value.totalHits, } }) + const users = await userRepo.findMany(hits.data.map((value) => value.id)) return { - data: ( - await userRepo.findMany( - users.data.map((value) => { - return value.id - }), - ) - ).map((value) => adminMapEntity(value)), - totalElements: users.totalElements, - totalPages: Math.floor((users.totalElements + size - 1) / size), + data: users.map(adminMapEntity), + totalElements: hits.totalElements, + totalPages: Math.floor((hits.totalElements + size - 1) / size), size: size, page: page, } } else { + const users = await userRepo.list(page, size) return { - data: (await userRepo.list(page, size)).map((value) => - adminMapEntity(value) - ), + data: users.map(adminMapEntity), totalElements: await userRepo.getCount(), totalPages: Math.floor(((await userRepo.getCount()) + size - 1) / size), size: size,