Skip to content

feat(token-introspection): simplify token-introspection package #3348

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

Draft
wants to merge 15 commits into
base: main
Choose a base branch
from
Draft
2 changes: 1 addition & 1 deletion packages/auth/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"knex": "knex",
"generate": "graphql-codegen --config codegen.yml",
"build:deps": "pnpm --filter token-introspection build",
"build": "pnpm build:deps && pnpm clean && tsc --build tsconfig.json && pnpm copy-files",
"build": "pnpm build:deps && pnpm clean && pnpm build:deps && tsc --build tsconfig.json && pnpm copy-files",
"clean": "rm -fr dist/",
"test": "NODE_OPTIONS=--experimental-vm-modules jest --passWithNoTests --maxWorkers=50%",
"test:cov": "pnpm test -- --coverage",
Expand Down
10 changes: 0 additions & 10 deletions packages/auth/src/accessToken/routes.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,9 +105,6 @@ describe('Access Token Routes', (): void => {
grantId: grant.id,
...BASE_TOKEN
})

const openApi = await deps.use('openApi')
jestOpenAPI(openApi.tokenIntrospectionSpec)
})
test('Cannot introspect fake token', async (): Promise<void> => {
const ctx = createContext<IntrospectContext>(
Expand All @@ -124,7 +121,6 @@ describe('Access Token Routes', (): void => {
access_token: v4()
}
await expect(accessTokenRoutes.introspect(ctx)).resolves.toBeUndefined()
expect(ctx.response).toSatisfyApiSpec()
expect(ctx.status).toBe(200)
expect(ctx.response.get('Content-Type')).toBe(
'application/json; charset=utf-8'
Expand All @@ -151,7 +147,6 @@ describe('Access Token Routes', (): void => {
}

await expect(accessTokenRoutes.introspect(ctx)).resolves.toBeUndefined()
expect(ctx.response).toSatisfyApiSpec()
expect(ctx.status).toBe(200)
expect(ctx.response.get('Content-Type')).toBe(
'application/json; charset=utf-8'
Expand Down Expand Up @@ -187,7 +182,6 @@ describe('Access Token Routes', (): void => {
access_token: token.value
}
await expect(accessTokenRoutes.introspect(ctx)).resolves.toBeUndefined()
expect(ctx.response).toSatisfyApiSpec()
expect(ctx.status).toBe(200)
expect(ctx.response.get('Content-Type')).toBe(
'application/json; charset=utf-8'
Expand Down Expand Up @@ -215,7 +209,6 @@ describe('Access Token Routes', (): void => {
}

await expect(accessTokenRoutes.introspect(ctx)).resolves.toBeUndefined()
expect(ctx.response).toSatisfyApiSpec()
expect(ctx.status).toBe(200)
expect(ctx.response.get('Content-Type')).toBe(
'application/json; charset=utf-8'
Expand Down Expand Up @@ -259,7 +252,6 @@ describe('Access Token Routes', (): void => {
}

await expect(accessTokenRoutes.introspect(ctx)).resolves.toBeUndefined()
expect(ctx.response).toSatisfyApiSpec()
expect(ctx.status).toBe(200)
expect(ctx.response.get('Content-Type')).toBe(
'application/json; charset=utf-8'
Expand Down Expand Up @@ -307,7 +299,6 @@ describe('Access Token Routes', (): void => {
}

await expect(accessTokenRoutes.introspect(ctx)).resolves.toBeUndefined()
expect(ctx.response).toSatisfyApiSpec()
expect(ctx.status).toBe(200)
expect(ctx.response.get('Content-Type')).toBe(
'application/json; charset=utf-8'
Expand Down Expand Up @@ -350,7 +341,6 @@ describe('Access Token Routes', (): void => {
}

await expect(accessTokenRoutes.introspect(ctx)).resolves.toBeUndefined()
expect(ctx.response).toSatisfyApiSpec()
expect(ctx.status).toBe(200)
expect(ctx.response.get('Content-Type')).toBe(
'application/json; charset=utf-8'
Expand Down
8 changes: 0 additions & 8 deletions packages/auth/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -394,18 +394,10 @@ export class App {
})

const accessTokenRoutes = await this.container.use('accessTokenRoutes')
const openApi = await this.container.use('openApi')

// Token Introspection
router.post<DefaultState, IntrospectContext>(
'/',
createValidatorMiddleware<IntrospectContext>(
openApi.tokenIntrospectionSpec,
{
path: '/',
method: HttpMethod.POST
}
),
accessTokenRoutes.introspect
)

Expand Down
5 changes: 1 addition & 4 deletions packages/auth/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import {
getAuthServerOpenAPI
} from '@interledger/open-payments'
import { createInteractionService } from './interaction/service'
import { getTokenIntrospectionOpenAPI } from 'token-introspection'
import { Redis } from 'ioredis'

const container = initIocContainer(Config)
Expand Down Expand Up @@ -167,12 +166,10 @@ export function initIocContainer(
const idpSpec = await createOpenAPI(
path.resolve(__dirname, './openapi/specs/id-provider.yaml')
)
const tokenIntrospectionSpec = await getTokenIntrospectionOpenAPI()

return {
authServerSpec,
idpSpec,
tokenIntrospectionSpec
idpSpec
}
})

Expand Down
7 changes: 3 additions & 4 deletions packages/token-introspection/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,10 @@
"dist/**/*"
],
"scripts": {
"build": "pnpm clean && tsc --build tsconfig.json && pnpm copy-files",
"clean": "rm -fr dist/",
"copy-files": "mkdir -p ./dist/openapi/specs && cp -r ./src/openapi/specs/*.yaml ./dist/openapi/specs/",
"build": "pnpm clean && pnpm generate:types && tsc --build tsconfig.json",
"clean": "rm -fr dist/ tsconfig.tsbuildinfo",
"copy-op-schemas": "cp ./node_modules/@interledger/open-payments/dist/openapi/specs/auth-server.yaml ./node_modules/@interledger/open-payments/dist/openapi/specs/schemas.yaml ./src/openapi/specs/",
"generate:types": "openapi-typescript ../../openapi/token-introspection.yaml --output src/openapi/generated/types.ts -t",
"generate:types": "openapi-typescript ./src/openapi/specs/token-introspection.yaml --output src/openapi/generated/types.ts -t",
"postinstall": "pnpm copy-op-schemas",
"prepack": "pnpm build",
"test": "jest --passWithNoTests",
Expand Down
10 changes: 1 addition & 9 deletions packages/token-introspection/src/client/index.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,13 @@
import axios, { AxiosInstance } from 'axios'
import { OpenAPI } from '@interledger/openapi'
import createLogger, { Logger } from 'pino'
import config from '../config'
import { createIntrospectionRoutes, IntrospectionRoutes } from './introspection'
import { getTokenIntrospectionOpenAPI } from '../openapi'

export interface BaseDeps {
axiosInstance: AxiosInstance
logger: Logger
}

export interface RouteDeps extends BaseDeps {
openApi: OpenAPI
}

export interface CreateClientArgs {
logger?: Logger
requestTimeoutMs?: number
Expand All @@ -28,13 +22,11 @@ export const createClient = async (args: CreateClientArgs): Promise<Client> => {
requestTimeoutMs:
args?.requestTimeoutMs ?? config.DEFAULT_REQUEST_TIMEOUT_MS
})
const openApi = await getTokenIntrospectionOpenAPI()
const logger = args?.logger ?? createLogger()

return createIntrospectionRoutes({
axiosInstance,
logger,
openApi
logger
})
}

Expand Down
43 changes: 4 additions & 39 deletions packages/token-introspection/src/client/introspection.test.ts
Original file line number Diff line number Diff line change
@@ -1,39 +1,22 @@
import { createIntrospectionRoutes, introspectToken } from './introspection'
import { OpenAPI, HttpMethod } from '@interledger/openapi'
import {
defaultAxiosInstance,
mockOpenApiResponseValidators,
mockTokenInfo,
silentLogger
} from '../test/helpers'
import nock from 'nock'
import { getTokenIntrospectionOpenAPI } from '../openapi'

describe('introspection', (): void => {
let openApi: OpenAPI

beforeAll(async () => {
openApi = await getTokenIntrospectionOpenAPI()
})

const axiosInstance = defaultAxiosInstance
const logger = silentLogger
const baseUrl = 'http://localhost:1000'
const openApiValidators = mockOpenApiResponseValidators()

describe('createIntrospectionRoutes', (): void => {
test('creates introspectOpenApiValidator properly', async (): Promise<void> => {
jest.spyOn(openApi, 'createResponseValidator')

test('creates introspection client properly', async (): Promise<void> => {
createIntrospectionRoutes({
axiosInstance,
openApi,
logger
})
expect(openApi.createResponseValidator).toHaveBeenCalledWith({
path: '/',
method: HttpMethod.POST
})
})
})

Expand All @@ -42,7 +25,7 @@ describe('introspection', (): void => {
access_token: 'OS9M2PMHKUR64TB8N6BW7OZB8CDFONP219RP1LT0'
}

test('returns token info if passes validation', async (): Promise<void> => {
test('returns token info', async (): Promise<void> => {
const tokenInfo = mockTokenInfo()
const scope = nock(baseUrl).post('/', body).reply(200, tokenInfo)

Expand All @@ -52,8 +35,7 @@ describe('introspection', (): void => {
axiosInstance,
logger
},
body,
openApiValidators.successfulValidator
body
)
).resolves.toStrictEqual(tokenInfo)
scope.done()
Expand All @@ -70,24 +52,7 @@ describe('introspection', (): void => {
axiosInstance,
logger
},
body,
openApiValidators.successfulValidator
)
).rejects.toThrowError()
scope.done()
})

test('throws if token info does not pass open api validation', async (): Promise<void> => {
const scope = nock(baseUrl).post('/', body).reply(200, mockTokenInfo())

await expect(() =>
introspectToken(
{
axiosInstance,
logger
},
body,
openApiValidators.failedValidator
body
)
).rejects.toThrowError()
scope.done()
Expand Down
46 changes: 6 additions & 40 deletions packages/token-introspection/src/client/introspection.ts
Original file line number Diff line number Diff line change
@@ -1,63 +1,29 @@
import { HttpMethod, ResponseValidator } from '@interledger/openapi'
import { BaseDeps, RouteDeps } from '.'
import { BaseDeps } from '.'
import { IntrospectArgs, TokenInfo } from '../types'

export interface IntrospectionRoutes {
introspect(args: IntrospectArgs): Promise<TokenInfo>
}

export const createIntrospectionRoutes = (
deps: RouteDeps
deps: BaseDeps
): IntrospectionRoutes => {
const { axiosInstance, openApi, logger } = deps

const introspectOpenApiValidator = openApi.createResponseValidator<TokenInfo>(
{
path: '/',
method: HttpMethod.POST
}
)
const { axiosInstance, logger } = deps

return {
introspect: (args: IntrospectArgs) =>
introspectToken(
{ axiosInstance, logger },
args,
introspectOpenApiValidator
)
introspectToken({ axiosInstance, logger }, args)
}
}

export const introspectToken = async (
deps: BaseDeps,
args: IntrospectArgs,
validateOpenApiResponse: ResponseValidator<TokenInfo>
) => {
export const introspectToken = async (deps: BaseDeps, args: IntrospectArgs) => {
const { axiosInstance, logger } = deps

try {
const { data, status } = await axiosInstance.request<TokenInfo>({
const { data } = await axiosInstance.request<TokenInfo>({
data: args
})

try {
validateOpenApiResponse({
status,
body: data
})
} catch (error) {
const errorMessage = 'Failed to validate OpenApi response'
logger.error(
{
data: JSON.stringify(data),
validationError: error instanceof Error && error.message
},
errorMessage
)

throw new Error(errorMessage)
}

return data
} catch (error) {
const errorMessage = `Error when making introspection request: ${
Expand Down
1 change: 0 additions & 1 deletion packages/token-introspection/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
export { TokenInfo, ActiveTokenInfo, isActiveTokenInfo } from './types'
export { getTokenIntrospectionOpenAPI } from './openapi'
export { createClient, Client } from './client'
10 changes: 8 additions & 2 deletions packages/token-introspection/src/openapi/generated/types.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

24 changes: 0 additions & 24 deletions packages/token-introspection/src/openapi/index.test.ts

This file was deleted.

13 changes: 0 additions & 13 deletions packages/token-introspection/src/openapi/index.ts

This file was deleted.

Loading
Loading