8000 fix(crux): prisma error handling & team user delete by robot9706 · Pull Request #746 · dyrector-io/dyrectorio · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

fix(crux): prisma error handling & team user delete #746

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Jul 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 10000 additions & 1 deletion web/crux-ui/locales/en/login.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@
"refresh": "Your login session is too old. Please log in again.",
"loginToAcceptInv": "You have to log in to accept the invitation.",
"dontHaveAnAccount": "Don't have an account yet?",
"newToDyo": "New to dyrector.io? Check out the"
"newToDyo": "New to dyrector.io? Check out the",
"validSessionDetected": "A valid session is detected. Please refresh the page."
}
24 changes: 22 additions & 2 deletions web/crux-ui/src/pages/api/auth/login.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { HEADER_LOCATION } from '@app/const'
import { invalidArgument } from '@app/error-responses'
import { Login, toKratosLocationChangeRequiredError } from '@app/models'
import { AxiosErrorResponse, Login, toKratosLocationChangeRequiredError } from '@app/models'
import {
LoginFlow,
UiContainer,
UpdateLoginFlowBody,
UpdateLoginFlowWithOidcMethod,
UpdateLoginFlowWithPasswordMethod,
Expand All @@ -18,6 +20,8 @@ import useKratosErrorMiddleware from '@server/kratos-error-middleware'
import { withMiddlewares } from '@server/middlewares'
import { NextApiRequest, NextApiResponse } from 'next'

const LOGIN_DETECTED = 'A valid session was detected and thus login is not possible.'

const dtoToKratosBody = (dto: Login): UpdateLoginFlowBody => {
switch (dto.method) {
case 'password': {
Expand All @@ -44,6 +48,16 @@ const dtoToKratosBody = (dto: Login): UpdateLoginFlowBody => {
}
}

const isAlreadyLoggedIn = (res: AxiosErrorResponse<LoginFlow>): boolean => {
if (!res.data.ui) {
return false
}

const newUi = res.data.ui as UiContainer
// TODO(@robot9706): this is the best solution I found
return newUi.messages.some(it => it.id === 4000001 && it.text.startsWith(LOGIN_DETECTED))
}

const (req: NextApiRequest, res: NextApiResponse) => {
const dto = req.body as Login
await validateCaptcha(dto.captcha)
Expand All @@ -66,8 +80,14 @@ const (req: NextApiRequest, res: NextApiResponse) => {

res.status(kratosRes.status).end()
} catch (err) {
const error = toKratosLocationChangeRequiredError(err)
const flowResponse = err.response as AxiosErrorResponse<LoginFlow>
if (isAlreadyLoggedIn(flowResponse)) {
forwardCookieToResponse(res, err.response)
res.status(409).end()
return
}

const error = toKratosLocationChangeRequiredError(err)
if (!error) {
throw err
}
Expand Down
2 changes: 2 additions & 0 deletions web/crux-ui/src/pages/auth/login.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,8 @@ const LoginPage = (props: LoginPageProps) => {
router.replace(invitation ? teamInvitationUrl(invitation) : ROUTE_INDEX)
} else if (res.status === 410) {
await router.reload()
} else if (res.status === 409) {
toast.error(t('validSessionDetected'))
} else {
recaptcha.current?.reset()
const result = await res.json()
Expand Down
17 changes: 11 additions & 6 deletions web/crux/src/app/team/team.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import DomainNotificationService from 'src/services/domain.notification.service'
import KratosService from 'src/services/kratos.service'
import PrismaService from 'src/services/prisma.service'
import { REGISTRY_HUB_URL } from 'src/shared/const'
import PrismaErrorInterceptor from 'src/interceptors/prisma-error-interceptor'
import EmailBuilder, { InviteTemplateOptions } from '../../builders/email.builder'
import AuditLoggerService from '../audit.logger/audit.logger.service'
import { AuthorizedHttpRequest } from '../token/jwt-auth.guard'
Expand Down Expand Up @@ -415,9 +416,11 @@ export default class TeamService {
},
})
deleted = true
// TODO(@polaroi8d): remove this catch or implement a better way to handle this
} catch {
this.logger.error(`User ${userId} is not in the team: ${team.id}`)
} catch (err) {
const exception = PrismaErrorInterceptor.transformPrismaError(err)
if (!(exception instanceof CruxNotFoundException)) {
throw exception
}
}

try {
Expand All @@ -430,9 +433,11 @@ export default class TeamService {
},
})
deleted = true
// TODO(@polaroi8d): remove this catch or implement a better way to handle this
} catch {
this.logger.error(`User ${userId} is not in the team: ${team.id}`)
} catch (err) {
const exception = PrismaErrorInterceptor.transformPrismaError(err)
if (!(exception instanceof CruxNotFoundException)) {
throw exception
}
}

if (!deleted) {
Expand Down
6 changes: 6 additions & 0 deletions web/crux/src/filters/crux.exception-filter.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { ArgumentsHost, ExceptionFilter, HttpException, Logger } from '@nestjs/common'
import { Prisma } from '@prisma/client'
import { CruxInternalServerErrorException } from 'src/exception/crux-exception'
import PrismaErrorInterceptor from 'src/interceptors/prisma-error-interceptor'

export default abstract class CruxExceptionFilter implements ExceptionFilter {
constructor(protected readonly logger: Logger) {}
Expand All @@ -8,6 +10,10 @@ export default abstract class CruxExceptionFilter implements ExceptionFilter {
this.logger.verbose(exception)
if (exception instanceof HttpException) {
this.handleHttpException(exception, host)
} else if (exception instanceof Prisma.PrismaClientKnownRequestError) {
this.logger.error('Unhandled Prisma Exception')
const prismaError = PrismaErrorInterceptor.transformPrismaError(exception)
this.handleHttpException(prismaError, host)
} else {
this.logger.error('Unhandled Exception')
this.logger.error(`${exception.name}: ${exception.message}`, exception.stack)
Expand Down
26 changes: 18 additions & 8 deletions web/crux/src/interceptors/prisma-error-interceptor.ts
< 9E19 tbody>
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Observable, catchError } from 'rxjs'
import {
CruxBadRequestException,
CruxConflictException,
CruxException,
CruxExceptionOptions,
CruxNotFoundException,
} from 'src/exception/crux-exception'
Expand All @@ -23,13 +24,16 @@ export default class PrismaErrorInterceptor implements NestInterceptor {

onError(_context: ExecutionContext, err: Error): any {
if (err instanceof Prisma.PrismaClientKnownRequestError) {
this.handlePrismaError(err)
const exception = PrismaErrorInterceptor.transformPrismaError(err)
if (exception != null) {
throw exception
}
}

throw err
}

private handlePrismaError(err: Prisma.PrismaClientKnownRequestError) {
public static transformPrismaError(err: Prisma.PrismaClientKnownRequestError): CruxException {
if (err.code === UNIQUE_CONSTRAINT_FAILED) {
const meta = err.meta ?? ({} as any)
const { target } = meta
Expand All @@ -42,22 +46,28 @@ export default class PrismaErrorInterceptor implements NestInterceptor {
property,
}

throw new CruxConflictException(error)
} else if (err.code === NOT_FOUND) {
return new CruxConflictException(error)
}

if (err.code === NOT_FOUND) {
const error = {
property: this.prismaMessageToProperty(err.message),
message: err.message,
}

throw new CruxNotFoundException(error)
} else if (err.code === UUID_INVALID) {
throw new CruxBadRequestException({
return new CruxNotFoundException(error)
}

if (err.code === UUID_INVALID) {
return new CruxBadRequestException({
message: 'Invalid uuid',
})
}

return null
}

private prismaMessageToProperty(message: string) {
public static prismaMessageToProperty(message: string) {
const FIRST_PART = 'No '
const SECOND_PART = ' found'

Expand Down
0