Skip to content

Commit

Permalink
feat: add context types to openapi validator middlewares
Browse files Browse the repository at this point in the history
  • Loading branch information
njlie committed Dec 12, 2022
1 parent 4844195 commit b54bcba
Show file tree
Hide file tree
Showing 4 changed files with 223 additions and 187 deletions.
36 changes: 27 additions & 9 deletions packages/auth/src/accessToken/routes.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,34 @@
import { Logger } from 'pino'
import { Access } from '../access/model'
import { IntrospectContext, RevokeContext, RotateContext } from '../app'
import { AppContext } from '../app'
import { IAppConfig } from '../config/app'
import { AccessTokenService, Introspection } from './service'
import { accessToBody } from '../shared/utils'
import { ClientService } from '../client/service'

type TokenRequest<BodyT = never> = Omit<AppContext['request'], 'body'> & {
body?: BodyT
}

type TokenContext<BodyT = never> = Omit<AppContext, 'request'> & {
request: TokenRequest<BodyT>
}

type ManagementRequest = Omit<AppContext['request'], 'params'> & {
params?: Record<'id', string>
}

type ManagementContext = Omit<AppContext, 'request'> & {
request: ManagementRequest
}

interface IntrospectBody {
access_token: string
}
export type IntrospectContext = TokenContext<IntrospectBody>
export type RevokeContext = ManagementContext
export type RotateContext = ManagementContext

interface ServiceDependencies {
config: IAppConfig
logger: Logger
Expand All @@ -14,7 +37,7 @@ interface ServiceDependencies {
}

export interface AccessTokenRoutes {
introspect(ctx: IntrospectContext<IntrospectBody>): Promise<void>
introspect(ctx: IntrospectContext): Promise<void>
revoke(ctx: RevokeContext): Promise<void>
rotate(ctx: RotateContext): Promise<void>
}
Expand All @@ -27,20 +50,15 @@ export function createAccessTokenRoutes(
})
const deps = { ...deps_, logger }
return {
introspect: (ctx: IntrospectContext<IntrospectBody>) =>
introspectToken(deps, ctx),
introspect: (ctx: IntrospectContext) => introspectToken(deps, ctx),
revoke: (ctx: RevokeContext) => revokeToken(deps, ctx),
rotate: (ctx: RotateContext) => rotateToken(deps, ctx)
}
}

interface IntrospectBody {
access_token: string
}

async function introspectToken(
deps: ServiceDependencies,
ctx: IntrospectContext<IntrospectBody>
ctx: IntrospectContext
): Promise<void> {
const { body } = ctx.request
const introspectionResult = await deps.accessTokenService.introspect(
Expand Down
212 changes: 106 additions & 106 deletions packages/auth/src/app.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { Server } from 'http'
import { EventEmitter } from 'events'
import { ParsedUrlQuery } from 'querystring'

import { IocContract } from '@adonisjs/fold'
import { Knex } from 'knex'
Expand Down Expand Up @@ -37,66 +36,12 @@ export interface AppContextData extends DefaultContext {

export type AppContext = Koa.ParameterizedContext<DefaultState, AppContextData>

interface ClientKeyContext extends AppContext {
clientKeyId: string
}

type GrantRequest<BodyT = never, QueryT = ParsedUrlQuery> = Omit<
ClientKeyContext['request'],
'body'
> & {
body: BodyT
query: ParsedUrlQuery & QueryT
}

type GrantContext<BodyT = never, QueryT = ParsedUrlQuery> = Omit<
ClientKeyContext,
'request'
> & {
request: GrantRequest<BodyT, QueryT>
}

type InteractionRequest<
BodyT = never,
QueryT = ParsedUrlQuery,
ParamsT = { [key: string]: string }
> = Omit<AppContext['request'], 'body'> & {
body: BodyT
query: ParsedUrlQuery & QueryT
params: ParamsT
}

type InteractionContext<QueryT, ParamsT> = Omit<AppContext, 'request'> & {
request: InteractionRequest<QueryT, ParamsT>
}

type TokenRequest<BodyT = never> = Omit<AppContext['request'], 'body'> & {
body?: BodyT
}

type TokenContext<BodyT = never> = Omit<AppContext, 'request'> & {
request: TokenRequest<BodyT>
}

type ManagementRequest = Omit<AppContext['request'], 'params'> & {
params?: Record<'id', string>
}

type ManagementContext = Omit<AppContext, 'request'> & {
request: ManagementRequest
}

export type CreateContext<BodyT> = GrantContext<BodyT>
export type ContinueContext<BodyT, QueryT> = GrantContext<BodyT, QueryT>

export type StartContext<QueryT, ParamsT> = InteractionContext<QueryT, ParamsT>
export type GetContext<ParamsT> = InteractionContext<never, ParamsT>
export type ChooseContext<ParamsT> = InteractionContext<never, ParamsT>
export type FinishContext<ParamsT> = InteractionContext<never, ParamsT>

export type IntrospectContext<BodyT> = TokenContext<BodyT>
export type RevokeContext = ManagementContext
export type RotateContext = ManagementContext
type ContextType<T> = T extends (
ctx: infer Context
// eslint-disable-next-line @typescript-eslint/no-explicit-any
) => any
? Context
: never

export interface DatabaseCleanupRule {
/**
Expand Down Expand Up @@ -255,109 +200,164 @@ export class App {
const openApi = await this.container.use('openApi')
/* Back-channel GNAP Routes */
// Grant Initiation
const {
create: grantCreate,
continue: grantContinue
}: {
create: (ctx: AppContext) => Promise<void>
continue: (ctx: AppContext) => Promise<void>
} = grantRoutes
this.publicRouter.post(
'/',
createValidatorMiddleware(openApi.authServerSpec, {
path: '/',
method: HttpMethod.POST
}),
createValidatorMiddleware<ContextType<typeof grantCreate>>(
openApi.authServerSpec,
{
path: '/',
method: HttpMethod.POST
}
),
this.config.bypassSignatureValidation
? (ctx, next) => next()
: grantInitiationHttpsigMiddleware,
grantRoutes.create
grantCreate
)

// Grant Continue
this.publicRouter.post(
'/continue/:id',
createValidatorMiddleware(openApi.authServerSpec, {
path: '/continue/{id}',
method: HttpMethod.POST
}),
createValidatorMiddleware<ContextType<typeof grantContinue>>(
openApi.authServerSpec,
{
path: '/continue/{id}',
method: HttpMethod.POST
}
),
this.config.bypassSignatureValidation
? (ctx, next) => next()
: grantContinueHttpsigMiddleware,
grantRoutes.continue
grantContinue
)

// Token Rotation
const {
rotate: tokenRotate,
revoke: tokenRevoke,
introspect: tokenIntrospect
}: {
rotate: (ctx: AppContext) => Promise<void>
revoke: (ctx: AppContext) => Promise<void>
introspect: (ctx: AppContext) => Promise<void>
} = accessTokenRoutes
this.publicRouter.post(
'/token/:id',
createValidatorMiddleware(openApi.authServerSpec, {
path: '/token/{id}',
method: HttpMethod.POST
}),
createValidatorMiddleware<ContextType<typeof tokenRotate>>(
openApi.authServerSpec,
{
path: '/token/{id}',
method: HttpMethod.POST
}
),
this.config.bypassSignatureValidation
? (ctx, next) => next()
: tokenHttpsigMiddleware,
accessTokenRoutes.rotate
tokenRotate
)

// Token Revocation
this.publicRouter.delete(
'/token/:id',
createValidatorMiddleware(openApi.authServerSpec, {
path: '/token/{id}',
method: HttpMethod.DELETE
}),
createValidatorMiddleware<ContextType<typeof tokenRevoke>>(
openApi.authServerSpec,
{
path: '/token/{id}',
method: HttpMethod.DELETE
}
),
this.config.bypassSignatureValidation
? (ctx, next) => next()
: tokenHttpsigMiddleware,
accessTokenRoutes.revoke
tokenRevoke
)

/* AS <-> RS Routes */
// Token Introspection
this.publicRouter.post(
'/introspect',
createValidatorMiddleware(openApi.tokenIntrospectionSpec, {
path: '/introspect',
method: HttpMethod.POST
}),
accessTokenRoutes.introspect
createValidatorMiddleware<ContextType<typeof tokenIntrospect>>(
openApi.tokenIntrospectionSpec,
{
path: '/introspect',
method: HttpMethod.POST
}
),
tokenIntrospect
)

/* Front Channel Routes */
// TODO: update front-channel routes to have /frontend prefix here and in openapi spec

const {
start: interactionStart,
finish: interactionFinish,
details: interactionDetails,
acceptOrReject: interactionAcceptOrReject
}: {
start: (ctx: AppContext) => Promise<void>
finish: (ctx: AppContext) => Promise<void>
details: (ctx: AppContext) => Promise<void>
acceptOrReject: (ctx: AppContext) => Promise<void>
} = grantRoutes.interaction

// Interaction start
this.publicRouter.get(
'/interact/:id/:nonce',
createValidatorMiddleware(openApi.idpSpec, {
path: '/interact/{id}/{nonce}',
method: HttpMethod.GET
}),
grantRoutes.interaction.start
createValidatorMiddleware<ContextType<typeof interactionStart>>(
openApi.idpSpec,
{
path: '/interact/{id}/{nonce}',
method: HttpMethod.GET
}
),
interactionStart
)

// Interaction finish
this.publicRouter.get(
'/interact/:id/:nonce/finish',
createValidatorMiddleware(openApi.idpSpec, {
path: '/interact/{id}/{nonce}/finish',
method: HttpMethod.GET
}),
grantRoutes.interaction.finish
createValidatorMiddleware<ContextType<typeof interactionFinish>>(
openApi.idpSpec,
{
path: '/interact/{id}/{nonce}/finish',
method: HttpMethod.GET
}
),
interactionFinish
)

// Grant lookup
this.publicRouter.get(
'/grant/:id/:nonce',
createValidatorMiddleware(openApi.idpSpec, {
path: '/grant/{id}/{nonce}',
method: HttpMethod.GET
}),
grantRoutes.interaction.details
createValidatorMiddleware<ContextType<typeof interactionDetails>>(
openApi.idpSpec,
{
path: '/grant/{id}/{nonce}',
method: HttpMethod.GET
}
),
interactionDetails
)

// Grant accept/reject
this.publicRouter.post(
'/grant/:id/:nonce/:choice',
createValidatorMiddleware(openApi.idpSpec, {
path: '/grant/{id}/{nonce}/{choice}',
method: HttpMethod.POST
}),
grantRoutes.interaction.acceptOrReject
createValidatorMiddleware<ContextType<typeof interactionAcceptOrReject>>(
openApi.idpSpec,
{
path: '/grant/{id}/{nonce}/{choice}',
method: HttpMethod.POST
}
),
interactionAcceptOrReject
)

this.koa.use(this.publicRouter.middleware())
Expand Down
Loading

0 comments on commit b54bcba

Please sign in to comment.