From 711bf3f578c5c1340bbc1bf9fd79162eef3b5c2b Mon Sep 17 00:00:00 2001 From: limes Date: Thu, 20 Aug 2026 21:02:39 +0200 Subject: [PATCH] feat: handle errors, add lenient ratelimin --- server/api/account/update.patch.ts | 48 +++++++++++++++++++++++++----- shared/errors.ts | 20 +++++++++++-- src/pages/account/index.vue | 7 +++-- 3 files changed, 63 insertions(+), 12 deletions(-) diff --git a/server/api/account/update.patch.ts b/server/api/account/update.patch.ts index a843639..a0c8a5c 100644 --- a/server/api/account/update.patch.ts +++ b/server/api/account/update.patch.ts @@ -1,16 +1,48 @@ +import { ClientError } from 'nice-grpc'; import { AccountUpdateSchema } from '~~/shared/api-types'; +import type { ApiErrorCodes } from '~~/shared/errors'; + +const bucket = createRatelimitBucket({ + id: 'account-update', + points: 100, + durationSec: 5 * 60, // 5 minutes + blockDurationSec: 1 * 60 * 60 // 1 hour +}); + +const errors: Record = { + 'INVALID_ARGUMENT: Must be one of: prod, test, dev': 'INVALID_ACCESS_LEVEL', + 'PERMISSION_DENIED: Banned': 'BANNED', + 'INVALID_ARGUMENT: Do not have permission to enter this environment': 'INSUFFICIENT_ACCESS_LEVEL', + 'INVALID_ARGUMENT: Must be a valid date formatted as: YYYY-MM-DD': 'INVALID_DATE', + 'INVALID_ARGUMENT: Must be one of: F, M': 'INVALID_GENDER', + 'INVALID_ARGUMENT: Invalid region': 'INVALID_REGION', + 'INVALID_ARGUMENT: Invalid timezone': 'INVALID_TIMEZONE', + 'INVALID_ARGUMENT: Invalid mii data': 'INVALID_MII_DATA' +}; export default defineEventHandler(async (event): Promise => { + await enforceRatelimit(event, bucket); + const body = await readZodBody(event, AccountUpdateSchema); const auth = enforceLoggedIn(event); const grpc = useApiGrpcWithToken(event, auth.token); - await grpc.updateUserData({ - gender: body.gender, - birthday: body.birthday, - region: body.region, - timezone: body.timezone, - mii: body.mii, - serverAccessLevel: body.serverAccessLevel - }); + try { + await grpc.updateUserData({ + gender: body.gender, + birthday: body.birthday, + region: body.region, + timezone: body.timezone, + mii: body.mii, + serverAccessLevel: body.serverAccessLevel + }); + } catch (error: unknown) { + if (error instanceof ClientError) { + const errorCode = errors[error.details]; + if (errorCode) { + throw createApiError(errorCode); + } + } + throw error; + } }); diff --git a/shared/errors.ts b/shared/errors.ts index fb45ede..bd83c98 100644 --- a/shared/errors.ts +++ b/shared/errors.ts @@ -18,7 +18,15 @@ const apiErrorCodes = { MIINAME_TOO_LONG: 'Mii name too long', INVALID_PASSWORD_INPUT: 'Password must be between 6 and 16 characters long', INVALID_PASSWORD_NO_MATCH: 'Passwords do not match', - ACCOUNT_DELETED: 'Account has been deleted' + ACCOUNT_DELETED: 'Account has been deleted', + INVALID_ACCESS_LEVEL: 'Invalid access level', + BANNED: 'Account is banned', + INSUFFICIENT_ACCESS_LEVEL: 'Do not have permission to enter this environment', + INVALID_DATE: 'Invalid date', + INVALID_GENDER: 'Invalid gender', + INVALID_REGION: 'Invalid region', + INVALID_TIMEZONE: 'Invalid timezone', + INVALID_MII_DATA: 'Invalid mii data' } as const; export type ApiErrorCodes = keyof typeof apiErrorCodes; @@ -43,7 +51,15 @@ export const apiErrorCodeStatus: Record = { USERNAME_IN_USE: 400, USERNAME_INVALID_CHARS: 400, USERNAME_TOO_LONG: 400, - USERNAME_TOO_SHORT: 400 + USERNAME_TOO_SHORT: 400, + INVALID_ACCESS_LEVEL: 400, + BANNED: 403, + INSUFFICIENT_ACCESS_LEVEL: 403, + INVALID_DATE: 400, + INVALID_GENDER: 400, + INVALID_REGION: 400, + INVALID_TIMEZONE: 400, + INVALID_MII_DATA: 400 }; export function getTextForApiErrorCode(code: ApiErrorCodes): string { diff --git a/src/pages/account/index.vue b/src/pages/account/index.vue index 23551c7..afb8d63 100644 --- a/src/pages/account/index.vue +++ b/src/pages/account/index.vue @@ -114,7 +114,7 @@ watchOnce(newRegion, () => { watch(newRegion, (o, n) => { if ((((o || 0) >>> 24) & 0xff) === (((n || 0) >>> 24) & 0xff)) { - // same country so we leave the timezone as is, or we might override the user's choice. + // same country so we leave the timezone as is, else we might override the user's choice. return; } @@ -634,7 +634,10 @@ useHead({ }) " > - {{ $t("modals.confirm") }} + + {{ + $t("modals.confirm") + }}