feat(server): introduce user friendly server errors (#7111)
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
import { randomUUID } from 'node:crypto';
|
||||
|
||||
import {
|
||||
BadRequestException,
|
||||
Body,
|
||||
Controller,
|
||||
Get,
|
||||
@@ -14,7 +13,16 @@ import {
|
||||
} from '@nestjs/common';
|
||||
import type { Request, Response } from 'express';
|
||||
|
||||
import { Config, Throttle, URLHelper } from '../../fundamentals';
|
||||
import {
|
||||
Config,
|
||||
EarlyAccessRequired,
|
||||
EmailTokenNotFound,
|
||||
InternalServerError,
|
||||
InvalidEmailToken,
|
||||
SignUpForbidden,
|
||||
Throttle,
|
||||
URLHelper,
|
||||
} from '../../fundamentals';
|
||||
import { UserService } from '../user';
|
||||
import { validators } from '../utils/validators';
|
||||
import { CurrentUser } from './current-user';
|
||||
@@ -55,9 +63,7 @@ export class AuthController {
|
||||
validators.assertValidEmail(credential.email);
|
||||
const canSignIn = await this.auth.canSignIn(credential.email);
|
||||
if (!canSignIn) {
|
||||
throw new BadRequestException(
|
||||
`You don't have early access permission\nVisit https://community.affine.pro/c/insider-general/ for more information`
|
||||
);
|
||||
throw new EarlyAccessRequired();
|
||||
}
|
||||
|
||||
if (credential.password) {
|
||||
@@ -74,7 +80,7 @@ export class AuthController {
|
||||
if (!user) {
|
||||
const allowSignup = await this.config.runtime.fetch('auth/allowSignup');
|
||||
if (!allowSignup) {
|
||||
throw new BadRequestException('You are not allows to sign up.');
|
||||
throw new SignUpForbidden();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,7 +90,7 @@ export class AuthController {
|
||||
);
|
||||
|
||||
if (result.rejected.length) {
|
||||
throw new Error('Failed to send sign-in email.');
|
||||
throw new InternalServerError('Failed to send sign-in email.');
|
||||
}
|
||||
|
||||
res.status(HttpStatus.OK).send({
|
||||
@@ -145,7 +151,7 @@ export class AuthController {
|
||||
@Body() { email, token }: MagicLinkCredential
|
||||
) {
|
||||
if (!token || !email) {
|
||||
throw new BadRequestException('Missing sign-in mail token');
|
||||
throw new EmailTokenNotFound();
|
||||
}
|
||||
|
||||
validators.assertValidEmail(email);
|
||||
@@ -155,7 +161,7 @@ export class AuthController {
|
||||
});
|
||||
|
||||
if (!valid) {
|
||||
throw new BadRequestException('Invalid sign-in mail token');
|
||||
throw new InvalidEmailToken();
|
||||
}
|
||||
|
||||
const user = await this.user.fulfillUser(email, {
|
||||
|
||||
@@ -3,15 +3,13 @@ import type {
|
||||
ExecutionContext,
|
||||
OnModuleInit,
|
||||
} from '@nestjs/common';
|
||||
import {
|
||||
Injectable,
|
||||
SetMetadata,
|
||||
UnauthorizedException,
|
||||
UseGuards,
|
||||
} from '@nestjs/common';
|
||||
import { Injectable, SetMetadata, UseGuards } from '@nestjs/common';
|
||||
import { ModuleRef, Reflector } from '@nestjs/core';
|
||||
|
||||
import { getRequestResponseFromContext } from '../../fundamentals';
|
||||
import {
|
||||
AuthenticationRequired,
|
||||
getRequestResponseFromContext,
|
||||
} from '../../fundamentals';
|
||||
import { AuthService, parseAuthUserSeqNum } from './service';
|
||||
|
||||
function extractTokenFromHeader(authorization: string) {
|
||||
@@ -84,7 +82,7 @@ export class AuthGuard implements CanActivate, OnModuleInit {
|
||||
}
|
||||
|
||||
if (!req.user) {
|
||||
throw new UnauthorizedException('You are not signed in.');
|
||||
throw new AuthenticationRequired();
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { BadRequestException, ForbiddenException } from '@nestjs/common';
|
||||
import {
|
||||
Args,
|
||||
Field,
|
||||
@@ -10,7 +9,18 @@ import {
|
||||
Resolver,
|
||||
} from '@nestjs/graphql';
|
||||
|
||||
import { Config, SkipThrottle, Throttle, URLHelper } from '../../fundamentals';
|
||||
import {
|
||||
ActionForbidden,
|
||||
Config,
|
||||
EmailAlreadyUsed,
|
||||
EmailTokenNotFound,
|
||||
EmailVerificationRequired,
|
||||
InvalidEmailToken,
|
||||
SameEmailProvided,
|
||||
SkipThrottle,
|
||||
Throttle,
|
||||
URLHelper,
|
||||
} from '../../fundamentals';
|
||||
import { UserService } from '../user';
|
||||
import { UserType } from '../user/types';
|
||||
import { validators } from '../utils/validators';
|
||||
@@ -62,7 +72,7 @@ export class AuthResolver {
|
||||
@Parent() user: UserType
|
||||
): Promise<ClientTokenType> {
|
||||
if (user.id !== currentUser.id) {
|
||||
throw new ForbiddenException('Invalid user');
|
||||
throw new ActionForbidden();
|
||||
}
|
||||
|
||||
const session = await this.auth.createUserSession(
|
||||
@@ -102,7 +112,7 @@ export class AuthResolver {
|
||||
);
|
||||
|
||||
if (!valid) {
|
||||
throw new ForbiddenException('Invalid token');
|
||||
throw new InvalidEmailToken();
|
||||
}
|
||||
|
||||
await this.auth.changePassword(user.id, newPassword);
|
||||
@@ -124,7 +134,7 @@ export class AuthResolver {
|
||||
});
|
||||
|
||||
if (!valid) {
|
||||
throw new ForbiddenException('Invalid token');
|
||||
throw new InvalidEmailToken();
|
||||
}
|
||||
|
||||
email = decodeURIComponent(email);
|
||||
@@ -144,7 +154,7 @@ export class AuthResolver {
|
||||
@Args('email', { nullable: true }) _email?: string
|
||||
) {
|
||||
if (!user.emailVerified) {
|
||||
throw new ForbiddenException('Please verify your email first.');
|
||||
throw new EmailVerificationRequired();
|
||||
}
|
||||
|
||||
const token = await this.token.createToken(
|
||||
@@ -166,7 +176,7 @@ export class AuthResolver {
|
||||
@Args('email', { nullable: true }) _email?: string
|
||||
) {
|
||||
if (!user.emailVerified) {
|
||||
throw new ForbiddenException('Please verify your email first.');
|
||||
throw new EmailVerificationRequired();
|
||||
}
|
||||
|
||||
const token = await this.token.createToken(
|
||||
@@ -195,7 +205,7 @@ export class AuthResolver {
|
||||
@Args('email', { nullable: true }) _email?: string
|
||||
) {
|
||||
if (!user.emailVerified) {
|
||||
throw new ForbiddenException('Please verify your email first.');
|
||||
throw new EmailVerificationRequired();
|
||||
}
|
||||
|
||||
const token = await this.token.createToken(TokenType.ChangeEmail, user.id);
|
||||
@@ -213,24 +223,26 @@ export class AuthResolver {
|
||||
@Args('email') email: string,
|
||||
@Args('callbackUrl') callbackUrl: string
|
||||
) {
|
||||
if (!token) {
|
||||
throw new EmailTokenNotFound();
|
||||
}
|
||||
|
||||
validators.assertValidEmail(email);
|
||||
const valid = await this.token.verifyToken(TokenType.ChangeEmail, token, {
|
||||
credential: user.id,
|
||||
});
|
||||
|
||||
if (!valid) {
|
||||
throw new ForbiddenException('Invalid token');
|
||||
throw new InvalidEmailToken();
|
||||
}
|
||||
|
||||
const hasRegistered = await this.user.findUserByEmail(email);
|
||||
|
||||
if (hasRegistered) {
|
||||
if (hasRegistered.id !== user.id) {
|
||||
throw new BadRequestException(`The email provided has been taken.`);
|
||||
throw new EmailAlreadyUsed();
|
||||
} else {
|
||||
throw new BadRequestException(
|
||||
`The email provided is the same as the current email.`
|
||||
);
|
||||
throw new SameEmailProvided();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -264,7 +276,7 @@ export class AuthResolver {
|
||||
@Args('token') token: string
|
||||
) {
|
||||
if (!token) {
|
||||
throw new BadRequestException('Invalid token');
|
||||
throw new EmailTokenNotFound();
|
||||
}
|
||||
|
||||
const valid = await this.token.verifyToken(TokenType.VerifyEmail, token, {
|
||||
@@ -272,7 +284,7 @@ export class AuthResolver {
|
||||
});
|
||||
|
||||
if (!valid) {
|
||||
throw new ForbiddenException('Invalid token');
|
||||
throw new InvalidEmailToken();
|
||||
}
|
||||
|
||||
const { emailVerifiedAt } = await this.auth.setEmailVerified(user.id);
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
import {
|
||||
BadRequestException,
|
||||
Injectable,
|
||||
NotAcceptableException,
|
||||
OnApplicationBootstrap,
|
||||
} from '@nestjs/common';
|
||||
import { Injectable, OnApplicationBootstrap } from '@nestjs/common';
|
||||
import { Cron, CronExpression } from '@nestjs/schedule';
|
||||
import type { User } from '@prisma/client';
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
import type { CookieOptions, Request, Response } from 'express';
|
||||
import { assign, omit } from 'lodash-es';
|
||||
|
||||
import { Config, CryptoHelper, MailService } from '../../fundamentals';
|
||||
import {
|
||||
Config,
|
||||
CryptoHelper,
|
||||
EmailAlreadyUsed,
|
||||
MailService,
|
||||
WrongSignInCredentials,
|
||||
WrongSignInMethod,
|
||||
} from '../../fundamentals';
|
||||
import { FeatureManagementService } from '../features/management';
|
||||
import { QuotaService } from '../quota/service';
|
||||
import { QuotaType } from '../quota/types';
|
||||
@@ -109,7 +111,7 @@ export class AuthService implements OnApplicationBootstrap {
|
||||
const user = await this.user.findUserByEmail(email);
|
||||
|
||||
if (user) {
|
||||
throw new BadRequestException('Email was taken');
|
||||
throw new EmailAlreadyUsed();
|
||||
}
|
||||
|
||||
const hashedPassword = await this.crypto.encryptPassword(password);
|
||||
@@ -127,13 +129,11 @@ export class AuthService implements OnApplicationBootstrap {
|
||||
const user = await this.user.findUserWithHashedPasswordByEmail(email);
|
||||
|
||||
if (!user) {
|
||||
throw new NotAcceptableException('Invalid sign in credentials');
|
||||
throw new WrongSignInCredentials();
|
||||
}
|
||||
|
||||
if (!user.password) {
|
||||
throw new NotAcceptableException(
|
||||
'User Password is not set. Should login through email link.'
|
||||
);
|
||||
throw new WrongSignInMethod();
|
||||
}
|
||||
|
||||
const passwordMatches = await this.crypto.verifyPassword(
|
||||
@@ -142,7 +142,7 @@ export class AuthService implements OnApplicationBootstrap {
|
||||
);
|
||||
|
||||
if (!passwordMatches) {
|
||||
throw new NotAcceptableException('Invalid sign in credentials');
|
||||
throw new WrongSignInCredentials();
|
||||
}
|
||||
|
||||
return sessionUser(user);
|
||||
@@ -382,27 +382,14 @@ export class AuthService implements OnApplicationBootstrap {
|
||||
id: string,
|
||||
newPassword: string
|
||||
): Promise<Omit<User, 'password'>> {
|
||||
const user = await this.user.findUserById(id);
|
||||
|
||||
if (!user) {
|
||||
throw new BadRequestException('Invalid email');
|
||||
}
|
||||
|
||||
const hashedPassword = await this.crypto.encryptPassword(newPassword);
|
||||
|
||||
return this.user.updateUser(user.id, { password: hashedPassword });
|
||||
return this.user.updateUser(id, { password: hashedPassword });
|
||||
}
|
||||
|
||||
async changeEmail(
|
||||
id: string,
|
||||
newEmail: string
|
||||
): Promise<Omit<User, 'password'>> {
|
||||
const user = await this.user.findUserById(id);
|
||||
|
||||
if (!user) {
|
||||
throw new BadRequestException('Invalid email');
|
||||
}
|
||||
|
||||
return this.user.updateUser(id, {
|
||||
email: newEmail,
|
||||
emailVerifiedAt: new Date(),
|
||||
|
||||
Reference in New Issue
Block a user