diff --git a/packages/auth/src/accessToken/routes.ts b/packages/auth/src/accessToken/routes.ts index ff7105584b..f7a3c102eb 100644 --- a/packages/auth/src/accessToken/routes.ts +++ b/packages/auth/src/accessToken/routes.ts @@ -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 = Omit & { + body?: BodyT +} + +type TokenContext = Omit & { + request: TokenRequest +} + +type ManagementRequest = Omit & { + params?: Record<'id', string> +} + +type ManagementContext = Omit & { + request: ManagementRequest +} + +interface IntrospectBody { + access_token: string +} +export type IntrospectContext = TokenContext +export type RevokeContext = ManagementContext +export type RotateContext = ManagementContext + interface ServiceDependencies { config: IAppConfig logger: Logger @@ -14,7 +37,7 @@ interface ServiceDependencies { } export interface AccessTokenRoutes { - introspect(ctx: IntrospectContext): Promise + introspect(ctx: IntrospectContext): Promise revoke(ctx: RevokeContext): Promise rotate(ctx: RotateContext): Promise } @@ -27,20 +50,15 @@ export function createAccessTokenRoutes( }) const deps = { ...deps_, logger } return { - introspect: (ctx: IntrospectContext) => - 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 + ctx: IntrospectContext ): Promise { const { body } = ctx.request const introspectionResult = await deps.accessTokenService.introspect( diff --git a/packages/auth/src/app.ts b/packages/auth/src/app.ts index f8b0d4d166..fd969e2521 100644 --- a/packages/auth/src/app.ts +++ b/packages/auth/src/app.ts @@ -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' @@ -37,66 +36,12 @@ export interface AppContextData extends DefaultContext { export type AppContext = Koa.ParameterizedContext -interface ClientKeyContext extends AppContext { - clientKeyId: string -} - -type GrantRequest = Omit< - ClientKeyContext['request'], - 'body' -> & { - body: BodyT - query: ParsedUrlQuery & QueryT -} - -type GrantContext = Omit< - ClientKeyContext, - 'request' -> & { - request: GrantRequest -} - -type InteractionRequest< - BodyT = never, - QueryT = ParsedUrlQuery, - ParamsT = { [key: string]: string } -> = Omit & { - body: BodyT - query: ParsedUrlQuery & QueryT - params: ParamsT -} - -type InteractionContext = Omit & { - request: InteractionRequest -} - -type TokenRequest = Omit & { - body?: BodyT -} - -type TokenContext = Omit & { - request: TokenRequest -} - -type ManagementRequest = Omit & { - params?: Record<'id', string> -} - -type ManagementContext = Omit & { - request: ManagementRequest -} - -export type CreateContext = GrantContext -export type ContinueContext = GrantContext - -export type StartContext = InteractionContext -export type GetContext = InteractionContext -export type ChooseContext = InteractionContext -export type FinishContext = InteractionContext - -export type IntrospectContext = TokenContext -export type RevokeContext = ManagementContext -export type RotateContext = ManagementContext +type ContextType = T extends ( + ctx: infer Context + // eslint-disable-next-line @typescript-eslint/no-explicit-any +) => any + ? Context + : never export interface DatabaseCleanupRule { /** @@ -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 + continue: (ctx: AppContext) => Promise + } = grantRoutes this.publicRouter.post( '/', - createValidatorMiddleware(openApi.authServerSpec, { - path: '/', - method: HttpMethod.POST - }), + createValidatorMiddleware>( + 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>( + 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 + revoke: (ctx: AppContext) => Promise + introspect: (ctx: AppContext) => Promise + } = accessTokenRoutes this.publicRouter.post( '/token/:id', - createValidatorMiddleware(openApi.authServerSpec, { - path: '/token/{id}', - method: HttpMethod.POST - }), + createValidatorMiddleware>( + 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>( + 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>( + 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 + finish: (ctx: AppContext) => Promise + details: (ctx: AppContext) => Promise + acceptOrReject: (ctx: AppContext) => Promise + } = grantRoutes.interaction + // Interaction start this.publicRouter.get( '/interact/:id/:nonce', - createValidatorMiddleware(openApi.idpSpec, { - path: '/interact/{id}/{nonce}', - method: HttpMethod.GET - }), - grantRoutes.interaction.start + createValidatorMiddleware>( + 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>( + 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>( + 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>( + openApi.idpSpec, + { + path: '/grant/{id}/{nonce}/{choice}', + method: HttpMethod.POST + } + ), + interactionAcceptOrReject ) this.koa.use(this.publicRouter.middleware()) diff --git a/packages/auth/src/grant/routes.ts b/packages/auth/src/grant/routes.ts index d724db8974..1dca0b7834 100644 --- a/packages/auth/src/grant/routes.ts +++ b/packages/auth/src/grant/routes.ts @@ -1,13 +1,8 @@ import * as crypto from 'crypto' import { URL } from 'url' -import { - CreateContext, - ContinueContext, - StartContext, - GetContext, - ChooseContext, - FinishContext -} from '../app' +import { ParsedUrlQuery } from 'querystring' + +import { AppContext } from '../app' import { GrantService, GrantRequest as GrantRequestBody } from './service' import { Grant, GrantState } from './model' import { Access } from '../access/model' @@ -31,18 +26,84 @@ interface ServiceDependencies extends BaseService { config: IAppConfig } +type GrantRequest = Omit< + AppContext['request'], + 'body' +> & { + body: BodyT + query: ParsedUrlQuery & QueryT +} + +type GrantContext = Omit< + AppContext, + 'request' +> & { + request: GrantRequest + clientKeyId: string +} + +export type CreateContext = GrantContext + +interface GrantContinueBody { + interact_ref: string +} + +interface GrantContinueParams { + id: string +} +export type ContinueContext = GrantContext< + GrantContinueBody, + GrantContinueParams +> + +type InteractionRequest< + BodyT = never, + QueryT = ParsedUrlQuery, + ParamsT = { [key: string]: string } +> = Omit & { + body: BodyT + query: ParsedUrlQuery & QueryT + params: ParamsT +} + +type InteractionContext = Omit & { + request: InteractionRequest +} + +interface StartQuery { + clientName: string + clientUri: string +} + +interface InteractionParams { + id: string + nonce: string +} +export type StartContext = InteractionContext + +export type GetContext = InteractionContext + +export enum GrantChoices { + Accept = 'accept', + Reject = 'reject' +} +interface ChooseParams extends InteractionParams { + choice: string +} +export type ChooseContext = InteractionContext + +export type FinishContext = InteractionContext + export interface GrantRoutes { - create(ctx: CreateContext): Promise + create(ctx: CreateContext): Promise // TODO: factor this out into separate routes service interaction: { - start(ctx: StartContext): Promise - finish(ctx: FinishContext): Promise - acceptOrReject(ctx: ChooseContext): Promise - details(ctx: GetContext): Promise + start(ctx: StartContext): Promise + finish(ctx: FinishContext): Promise + acceptOrReject(ctx: ChooseContext): Promise + details(ctx: GetContext): Promise } - continue( - ctx: ContinueContext - ): Promise + continue(ctx: ContinueContext): Promise } export function createGrantRoutes({ @@ -66,25 +127,20 @@ export function createGrantRoutes({ config } return { - create: (ctx: CreateContext) => - createGrantInitiation(deps, ctx), + create: (ctx: CreateContext) => createGrantInitiation(deps, ctx), interaction: { - start: (ctx: StartContext) => - startInteraction(deps, ctx), - finish: (ctx: FinishContext) => - finishInteraction(deps, ctx), - acceptOrReject: (ctx: ChooseContext) => - handleGrantChoice(deps, ctx), - details: (ctx: GetContext) => getGrantDetails(deps, ctx) + start: (ctx: StartContext) => startInteraction(deps, ctx), + finish: (ctx: FinishContext) => finishInteraction(deps, ctx), + acceptOrReject: (ctx: ChooseContext) => handleGrantChoice(deps, ctx), + details: (ctx: GetContext) => getGrantDetails(deps, ctx) }, - continue: (ctx: ContinueContext) => - continueGrant(deps, ctx) + continue: (ctx: ContinueContext) => continueGrant(deps, ctx) } } async function createGrantInitiation( deps: ServiceDependencies, - ctx: CreateContext + ctx: CreateContext ): Promise { if ( !ctx.accepts('application/json') || @@ -185,14 +241,9 @@ async function createGrantInitiation( } } -interface GetParams { - id: string - nonce: string -} - async function getGrantDetails( deps: ServiceDependencies, - ctx: GetContext + ctx: GetContext ): Promise { const secret = ctx.headers?.['x-idp-secret'] const { config, grantService } = deps @@ -222,19 +273,9 @@ async function getGrantDetails( ctx.body = { access: grant.access } } -interface StartQuery { - clientName: string - clientUri: string -} - -interface StartParams { - id: string - nonce: string -} - async function startInteraction( deps: ServiceDependencies, - ctx: StartContext + ctx: StartContext ): Promise { deps.logger.info( { @@ -269,21 +310,10 @@ async function startInteraction( ctx.redirect(interactionUrl.toString()) } -export enum GrantChoices { - Accept = 'accept', - Reject = 'reject' -} - -interface ChooseParams { - id: string - nonce: string - choice: string -} - // TODO: allow idp to specify the reason for rejection async function handleGrantChoice( deps: ServiceDependencies, - ctx: ChooseContext + ctx: ChooseContext ): Promise { // TODO: check redis for a session const { id: interactId, nonce, choice } = ctx.params @@ -344,14 +374,9 @@ async function handleGrantChoice( ctx.status = 202 } -interface FinishParams { - id: string - nonce: string -} - async function finishInteraction( deps: ServiceDependencies, - ctx: FinishContext + ctx: FinishContext ): Promise { const { id: interactId, nonce } = ctx.params const sessionNonce = ctx.session.nonce @@ -399,17 +424,9 @@ async function finishInteraction( } } -interface GrantContinueBody { - interact_ref: string -} - -interface GrantContinueParams { - id: string -} - async function continueGrant( deps: ServiceDependencies, - ctx: ContinueContext + ctx: ContinueContext ): Promise { const { id: continueId } = ctx.params const continueToken = (ctx.headers['authorization'] as string)?.split( diff --git a/packages/openapi/src/middleware.ts b/packages/openapi/src/middleware.ts index c90572b24d..8ce9fe404f 100644 --- a/packages/openapi/src/middleware.ts +++ b/packages/openapi/src/middleware.ts @@ -31,6 +31,7 @@ export function createValidatorMiddleware( } else if (isValidationError(err)) { ctx.throw(err.status ?? 500, err.errors[0]) } else { + console.log('err=', err) ctx.throw(500) } }