feat: implement birthday checks and handle all errors

This commit is contained in:
mrjvs
2026-08-18 00:03:11 +02:00
parent 0cd6b85b1f
commit 0c2c641b54
4 changed files with 78 additions and 14 deletions

View File

@@ -1,7 +1,14 @@
import { ClientError } from 'nice-grpc';
import { LoginSchema } from '#shared/api-types';
import type { ApiErrorCodes } from '~~/shared/errors';
import type { ApiAuthLogin } from '#shared/api-types';
const errors: Record<string, ApiErrorCodes> = {
'INVALID_ARGUMENT: User not found': 'INVALID_USERNAME',
'INVALID_ARGUMENT: Password is incorrect': 'INVALID_PASSWORD',
'UNAUTHENTICATED: Account has been deleted': 'ACCOUNT_DELETED'
};
export default defineEventHandler(async (event): Promise<ApiAuthLogin> => {
const body = await readZodBody(event, LoginSchema);
const grpc = useApiGrpc(event);
@@ -19,11 +26,9 @@ export default defineEventHandler(async (event): Promise<ApiAuthLogin> => {
};
} catch (error: unknown) {
if (error instanceof ClientError) {
if (error.details === 'INVALID_ARGUMENT: User not found') {
throw createApiError('INVALID_USERNAME');
}
if (error.details === 'INVALID_ARGUMENT: Password is incorrect') {
throw createApiError('INVALID_PASSWORD');
const errorCode = errors[error.details];
if (errorCode) {
throw createApiError(errorCode);
}
}
throw error;

View File

@@ -1,23 +1,57 @@
import { ClientError } from 'nice-grpc';
import { RegisterSchema } from '#shared/api-types';
import type { ApiErrorCodes } from '~~/shared/errors';
import type { ApiAuthLogin } from '#shared/api-types';
const errors: Record<string, ApiErrorCodes> = {
'INVALID_ARGUMENT: Captcha verification failed': 'INVALID_CAPTCHA',
'INVALID_ARGUMENT: Invalid email address': 'INVALID_EMAIL',
'INVALID_ARGUMENT: Username is too short': 'USERNAME_TOO_SHORT',
'INVALID_ARGUMENT: Username is too long': 'USERNAME_TOO_LONG',
'INVALID_ARGUMENT: Username contains invalid characters': 'USERNAME_INVALID_CHARS',
'INVALID_ARGUMENT: Username cannot begin with punctuation characters': 'USERNAME_INVALID_CHARS',
'INVALID_ARGUMENT: Username cannot end with punctuation characters': 'USERNAME_INVALID_CHARS',
'INVALID_ARGUMENT: Two or more punctuation characters cannot be used in a row': 'USERNAME_INVALID_CHARS',
'INVALID_ARGUMENT: PNID already in use': 'USERNAME_IN_USE',
'INVALID_ARGUMENT: Mii name too long': 'MIINAME_TOO_LONG',
'INVALID_ARGUMENT: Password must be between 6 and 16 characters long': 'INVALID_PASSWORD_INPUT',
'INVALID_ARGUMENT: Password cannot be the same as username': 'INVALID_PASSWORD_INPUT',
'INVALID_ARGUMENT: Password must have combination of letters, numbers, and/or punctuation characters': 'INVALID_PASSWORD_INPUT',
'INVALID_ARGUMENT: Password may not have 3 repeating characters': 'INVALID_PASSWORD_INPUT',
'INVALID_ARGUMENT: Passwords do not match': 'INVALID_PASSWORD_NO_MATCH'
};
function getCutoffDateForAge(today: Date, age: number) {
return new Date(today.getFullYear() - age, today.getMonth(), today.getDate());
}
function assertAge(birthDate: string | undefined) {
if (!birthDate) {
throw createApiError('INVALID_INPUT');
}
const date = new Date(birthDate);
const today = new Date();
// Prevent users below 13
if (date > getCutoffDateForAge(today, 13)) {
throw createApiError('UNDER_THIRTEEN');
}
}
export default defineEventHandler(async (event): Promise<ApiAuthLogin> => {
const body = await readZodBody(event, RegisterSchema);
const grpc = useApiGrpc(event);
assertAge(body.birthday);
console.log(body.birthday);
// eslint-disable-next-line no-useless-catch -- Temp before error handling is implemented
try {
// TODO Add ip
// TODO Add birthday
const res = await grpc.register({
email: body.email,
miiName: body.miiName,
captchaResponse: body.captchaResponse,
username: body.username,
password: body.password,
passwordConfirm: body.password,
passwordConfirm: body.password
});
return {
@@ -25,7 +59,12 @@ export default defineEventHandler(async (event): Promise<ApiAuthLogin> => {
refreshToken: res.refreshToken
};
} catch (error: unknown) {
// TODO handle errors
if (error instanceof ClientError) {
const errorCode = errors[error.details];
if (errorCode) {
throw createApiError(errorCode);
}
}
throw error;
}
});

View File

@@ -100,7 +100,7 @@ export const RegisterSchema = z.object({
username: z.string(),
miiName: z.string(),
password: z.string(),
birthday: z.string(),
birthday: z.iso.date(),
captchaResponse: z.string().optional()
});
export type ApiAuthRegisterRequest = z.infer<typeof RegisterSchema>;

View File

@@ -7,7 +7,17 @@ const apiErrorCodes = {
INVALID_CAPTCHA: 'Invalid captcha, try again',
INVALID_USERNAME: 'Could not find user',
INVALID_PASSWORD: 'Incorrect password',
UNAUTHENTICATED: 'This request needs authentication'
UNAUTHENTICATED: 'This request needs authentication',
UNDER_THIRTEEN: 'Must be 13 or older to use these services',
INVALID_EMAIL: 'Invalid email address',
USERNAME_TOO_SHORT: 'Username is too short',
USERNAME_TOO_LONG: 'Username is too long',
USERNAME_INVALID_CHARS: 'Username contains invalid characters',
USERNAME_IN_USE: 'PNID already in use',
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'
} as const;
export type ApiErrorCodes = keyof typeof apiErrorCodes;
@@ -21,7 +31,17 @@ export const apiErrorCodeStatus: Record<ApiErrorCodes, number> = {
INVALID_CAPTCHA: 400,
INVALID_USERNAME: 400,
INVALID_PASSWORD: 400,
UNAUTHENTICATED: 401
UNAUTHENTICATED: 401,
UNDER_THIRTEEN: 400,
ACCOUNT_DELETED: 400,
INVALID_EMAIL: 400,
INVALID_PASSWORD_INPUT: 400,
INVALID_PASSWORD_NO_MATCH: 400,
MIINAME_TOO_LONG: 400,
USERNAME_IN_USE: 400,
USERNAME_INVALID_CHARS: 400,
USERNAME_TOO_LONG: 400,
USERNAME_TOO_SHORT: 400
};
export function getTextForApiErrorCode(code: ApiErrorCodes): string {