Skip to content

Commit

Permalink
fix(core): improve error handling in node classes (#2703)
Browse files Browse the repository at this point in the history
  • Loading branch information
szkl authored Oct 2, 2023
1 parent 4b18c26 commit a545db5
Show file tree
Hide file tree
Showing 11 changed files with 79 additions and 38 deletions.
21 changes: 8 additions & 13 deletions packages/platform-middleware/jwt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,19 +21,14 @@ export const ValidateJWT: BaseMiddlewareFunction<{
token?: string
}> = ({ ctx, next }) => {
if (ctx.token) {
try {
const { sub: subject } = checkToken(ctx.token)
if (subject && IdentityURNSpace.is(subject)) {
return next({
ctx: {
...ctx,
identityURN: subject,
},
})
}
} catch (error) {
if (error instanceof RollupError) return next({ ctx })
else throw error
const { sub: subject } = checkToken(ctx.token)
if (subject && IdentityURNSpace.is(subject)) {
return next({
ctx: {
...ctx,
identityURN: subject,
},
})
}
}

Expand Down
1 change: 1 addition & 0 deletions packages/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,6 @@ export * as Router from './router'
export * as Headers from './headers'
export * as Email from './email'
export * as Graph from './graph'
export * as Node from './node'

export type { default as BaseContext, DeploymentMetadata } from './context'
11 changes: 11 additions & 0 deletions packages/types/node.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { RollupError } from '@proofzero/errors'

export type NodeMethodReturnValue<T, E = RollupError> =
| {
value: T
error?: undefined
}
| {
value?: undefined
error: E | Error
}
3 changes: 3 additions & 0 deletions packages/types/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,8 @@
"npm-run-all": "4.1.5",
"prettier": "2.8.0",
"typescript": "5.0.4"
},
"dependencies": {
"@proofzero/errors": "workspace:*"
}
}
2 changes: 1 addition & 1 deletion packages/utils/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ export const ROLLUP_ERROR_CLASS_BY_CODE = {
[ERROR_CODES.NOT_FOUND]: NotFoundError,
}

export const getErrorCause = (error: unknown): Error => {
export const getErrorCause = (error: unknown): RollupError | Error => {
if (error instanceof RollupError) {
return error
} else if (error instanceof TRPCClientError) {
Expand Down
17 changes: 15 additions & 2 deletions packages/utils/yoga.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,20 @@
import { RollupError, HTTP_STATUS_CODES } from '@proofzero/errors'

import { getErrorCause } from './errors'

export const formatError = (error: unknown): Error | undefined => {
export const formatError = (error: unknown) => {
if (!(error instanceof Error)) return
if (!('originalError' in error)) return
return getErrorCause(error.originalError)
const cause = getErrorCause(error.originalError)
if (cause instanceof RollupError)
return {
...cause,
extensions: {
http: {
status: HTTP_STATUS_CODES[cause.code],
},
},
}

return cause
}
4 changes: 3 additions & 1 deletion platform/authorization/src/jsonrpc/methods/exchangeToken.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import {
} from '@proofzero/security/persona'
import { UnauthorizedError } from '@proofzero/errors'
import { createAnalyticsEvent } from '@proofzero/utils/analytics'
import { getErrorCause } from '@proofzero/utils/errors'

const AuthenticationCodeInput = z.object({
grantType: z.literal(GrantType.AuthenticationCode),
Expand Down Expand Up @@ -329,7 +330,8 @@ const handleRefreshToken: ExchangeTokenMethod<
const authorizationNode = initAuthorizationNodeByName(urn, ctx.Authorization)

const jwks = getJWKS(ctx)
await authorizationNode.class.verify(refreshToken, jwks)
const { error } = await authorizationNode.class.verify(refreshToken, jwks)
if (error) throw getErrorCause(error)

const scope = payload.scope.split(' ')
const { expirationTime } = ACCESS_TOKEN_OPTIONS
Expand Down
14 changes: 8 additions & 6 deletions platform/authorization/src/jsonrpc/methods/getUserInfo.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
import { z } from 'zod'
import { decodeJwt } from 'jose'

import {
BadRequestError,
InternalServerError,
UnauthorizedError,
} from '@proofzero/errors'
import { BadRequestError, UnauthorizedError } from '@proofzero/errors'
import { AuthorizationURNSpace } from '@proofzero/urns/authorization'
import { IdentityURNSpace } from '@proofzero/urns/identity'
import { AuthorizationJWTPayload } from '@proofzero/types/authorization'
Expand All @@ -19,6 +15,7 @@ import {
userClaimsFormatter,
} from '@proofzero/security/persona'
import { PersonaData } from '@proofzero/types/application'
import { getErrorCause } from '@proofzero/utils/errors'

export const GetUserInfoInput = z.object({
access_token: z.string(),
Expand Down Expand Up @@ -54,7 +51,12 @@ export const getUserInfoMethod = async ({
const urn = AuthorizationURNSpace.componentizedUrn(nss)
const authorizationNode = initAuthorizationNodeByName(urn, ctx.Authorization)
const jwks = getJWKS(ctx)
await authorizationNode.class.verify(token, jwks, { issuer: input.issuer })

const { error } = await authorizationNode.class.verify(token, jwks, {
issuer: input.issuer,
})

if (error) throw getErrorCause(error)

const personaData = await authorizationNode.storage.get<PersonaData>(
'personaData'
Expand Down
6 changes: 5 additions & 1 deletion platform/authorization/src/jsonrpc/methods/verifyToken.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { router } from '@proofzero/platform.core'
import type { AuthorizationJWTPayload } from '@proofzero/types/authorization'
import { AuthorizationURNSpace } from '@proofzero/urns/authorization'
import { IdentityURNSpace } from '@proofzero/urns/identity'
import { getErrorCause } from '@proofzero/utils/errors'

import { Context } from '../../context'
import { getJWKS } from '../../jwk'
Expand Down Expand Up @@ -62,5 +63,8 @@ export const verifyTokenMethod: VerifyTokenMethod = async ({ ctx, input }) => {
const urn = AuthorizationURNSpace.componentizedUrn(nss)
const node = initAuthorizationNodeByName(urn, ctx.Authorization)
const jwks = getJWKS(ctx)
return node.class.verify(token, jwks, { issuer })

const { value, error } = await node.class.verify(token, jwks, { issuer })
if (error) throw getErrorCause(error)
return value
}
37 changes: 23 additions & 14 deletions platform/authorization/src/nodes/authorization.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import * as jose from 'jose'
import { hexlify } from '@ethersproject/bytes'
import { randomBytes } from '@ethersproject/random'

import { InternalServerError } from '@proofzero/errors'
import { InternalServerError, RollupError } from '@proofzero/errors'
import { NodeMethodReturnValue } from '@proofzero/types/node'

import { IdentityURN } from '@proofzero/urns/identity'
import type { Scope } from '@proofzero/types/authorization'
Expand Down Expand Up @@ -176,19 +177,25 @@ export default class Authorization extends DOProxy {
jwt: string,
jwks: jose.JSONWebKeySet,
options: jose.JWTVerifyOptions = {}
): Promise<jose.JWTVerifyResult> {
): Promise<NodeMethodReturnValue<jose.JWTVerifyResult, RollupError>> {
const { kid } = jose.decodeProtectedHeader(jwt)
if (kid) {
try {
return await jose.jwtVerify(jwt, jose.createLocalJWKSet(jwks), options)
return {
value: await jose.jwtVerify(
jwt,
jose.createLocalJWKSet(jwks),
options
),
}
} catch (error) {
if (error instanceof jose.errors.JWTClaimValidationFailed)
throw TokenClaimValidationFailedError
return { error: TokenClaimValidationFailedError }
else if (error instanceof jose.errors.JWTExpired)
throw ExpiredTokenError
return { error: ExpiredTokenError }
else if (error instanceof jose.errors.JWTInvalid)
throw InvalidTokenError
else throw TokenVerificationFailedError
return { error: InvalidTokenError }
else return { error: TokenVerificationFailedError }
}
} else {
// TODO: Initial signing keys didn't have `kid` property.
Expand All @@ -199,28 +206,30 @@ export default class Authorization extends DOProxy {
const { alg } = JWT_OPTIONS
const key = await jose.importJWK(local, alg)
try {
return await jose.jwtVerify(jwt, key, options)
return { value: await jose.jwtVerify(jwt, key, options) }
} catch (error) {
if (error instanceof jose.errors.JWTClaimValidationFailed)
throw TokenClaimValidationFailedError
return { error: TokenClaimValidationFailedError }
else if (error instanceof jose.errors.JWTExpired)
throw ExpiredTokenError
return { error: ExpiredTokenError }
else if (error instanceof jose.errors.JWTInvalid)
throw InvalidTokenError
else throw TokenVerificationFailedError
return { error: InvalidTokenError }
else return { error: TokenVerificationFailedError }
}
}
}

throw TokenVerificationFailedError
return { error: TokenVerificationFailedError }
}

async revoke(
token: string,
jwks: jose.JSONWebKeySet,
options: jose.JWTVerifyOptions = {}
): Promise<void> {
const { payload } = await this.verify(token, jwks, options)
const { value, error } = await this.verify(token, jwks, options)
if (error) throw error
const { payload } = value
await this.state.storage.transaction(async (txn) => {
const { jti } = payload
if (!jti) {
Expand Down
1 change: 1 addition & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -6702,6 +6702,7 @@ __metadata:
version: 0.0.0-use.local
resolution: "@proofzero/types@workspace:packages/types"
dependencies:
"@proofzero/errors": "workspace:*"
"@types/node": 18.15.3
"@typescript-eslint/eslint-plugin": 5.45.0
"@typescript-eslint/parser": 5.45.0
Expand Down

0 comments on commit a545db5

Please sign in to comment.