diff --git a/packages/auth/package.json b/packages/auth/package.json index 23ba457dd6..143cc6bdf3 100644 --- a/packages/auth/package.json +++ b/packages/auth/package.json @@ -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", diff --git a/packages/auth/src/accessToken/routes.test.ts b/packages/auth/src/accessToken/routes.test.ts index ac9fb14964..e76735a77c 100644 --- a/packages/auth/src/accessToken/routes.test.ts +++ b/packages/auth/src/accessToken/routes.test.ts @@ -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 => { const ctx = createContext( @@ -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' @@ -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' @@ -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' @@ -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' @@ -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' @@ -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' @@ -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' diff --git a/packages/auth/src/app.ts b/packages/auth/src/app.ts index 3e8bb9d0f7..2f173eab79 100644 --- a/packages/auth/src/app.ts +++ b/packages/auth/src/app.ts @@ -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( '/', - createValidatorMiddleware( - openApi.tokenIntrospectionSpec, - { - path: '/', - method: HttpMethod.POST - } - ), accessTokenRoutes.introspect ) diff --git a/packages/auth/src/index.ts b/packages/auth/src/index.ts index 2315fe083b..a2d0a7bf57 100644 --- a/packages/auth/src/index.ts +++ b/packages/auth/src/index.ts @@ -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) @@ -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 } }) diff --git a/packages/token-introspection/package.json b/packages/token-introspection/package.json index be5ad8b715..6c153f5dc5 100644 --- a/packages/token-introspection/package.json +++ b/packages/token-introspection/package.json @@ -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", diff --git a/packages/token-introspection/src/client/index.ts b/packages/token-introspection/src/client/index.ts index 608a7b6dfd..ed59fc5b29 100644 --- a/packages/token-introspection/src/client/index.ts +++ b/packages/token-introspection/src/client/index.ts @@ -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 @@ -28,13 +22,11 @@ export const createClient = async (args: CreateClientArgs): Promise => { requestTimeoutMs: args?.requestTimeoutMs ?? config.DEFAULT_REQUEST_TIMEOUT_MS }) - const openApi = await getTokenIntrospectionOpenAPI() const logger = args?.logger ?? createLogger() return createIntrospectionRoutes({ axiosInstance, - logger, - openApi + logger }) } diff --git a/packages/token-introspection/src/client/introspection.test.ts b/packages/token-introspection/src/client/introspection.test.ts index 422ccb20ea..f1681edc0d 100644 --- a/packages/token-introspection/src/client/introspection.test.ts +++ b/packages/token-introspection/src/client/introspection.test.ts @@ -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 => { - jest.spyOn(openApi, 'createResponseValidator') - + test('creates introspection client properly', async (): Promise => { createIntrospectionRoutes({ axiosInstance, - openApi, logger }) - expect(openApi.createResponseValidator).toHaveBeenCalledWith({ - path: '/', - method: HttpMethod.POST - }) }) }) @@ -42,7 +25,7 @@ describe('introspection', (): void => { access_token: 'OS9M2PMHKUR64TB8N6BW7OZB8CDFONP219RP1LT0' } - test('returns token info if passes validation', async (): Promise => { + test('returns token info', async (): Promise => { const tokenInfo = mockTokenInfo() const scope = nock(baseUrl).post('/', body).reply(200, tokenInfo) @@ -52,8 +35,7 @@ describe('introspection', (): void => { axiosInstance, logger }, - body, - openApiValidators.successfulValidator + body ) ).resolves.toStrictEqual(tokenInfo) scope.done() @@ -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 => { - const scope = nock(baseUrl).post('/', body).reply(200, mockTokenInfo()) - - await expect(() => - introspectToken( - { - axiosInstance, - logger - }, - body, - openApiValidators.failedValidator + body ) ).rejects.toThrowError() scope.done() diff --git a/packages/token-introspection/src/client/introspection.ts b/packages/token-introspection/src/client/introspection.ts index 76e35c45e8..dc0ca2a1fd 100644 --- a/packages/token-introspection/src/client/introspection.ts +++ b/packages/token-introspection/src/client/introspection.ts @@ -1,5 +1,4 @@ -import { HttpMethod, ResponseValidator } from '@interledger/openapi' -import { BaseDeps, RouteDeps } from '.' +import { BaseDeps } from '.' import { IntrospectArgs, TokenInfo } from '../types' export interface IntrospectionRoutes { @@ -7,57 +6,24 @@ export interface IntrospectionRoutes { } export const createIntrospectionRoutes = ( - deps: RouteDeps + deps: BaseDeps ): IntrospectionRoutes => { - const { axiosInstance, openApi, logger } = deps - - const introspectOpenApiValidator = openApi.createResponseValidator( - { - 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 -) => { +export const introspectToken = async (deps: BaseDeps, args: IntrospectArgs) => { const { axiosInstance, logger } = deps try { - const { data, status } = await axiosInstance.request({ + const { data } = await axiosInstance.request({ 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: ${ diff --git a/packages/token-introspection/src/index.ts b/packages/token-introspection/src/index.ts index 9532c480fb..35b91efc69 100644 --- a/packages/token-introspection/src/index.ts +++ b/packages/token-introspection/src/index.ts @@ -1,3 +1,2 @@ export { TokenInfo, ActiveTokenInfo, isActiveTokenInfo } from './types' -export { getTokenIntrospectionOpenAPI } from './openapi' export { createClient, Client } from './client' diff --git a/packages/token-introspection/src/openapi/generated/types.ts b/packages/token-introspection/src/openapi/generated/types.ts index fb10a08903..725df2be7d 100644 --- a/packages/token-introspection/src/openapi/generated/types.ts +++ b/packages/token-introspection/src/openapi/generated/types.ts @@ -275,9 +275,15 @@ export type external = { /** * Receiver * Format: uri - * @description The URL of the incoming payment or ILP STREAM connection that is being paid. + * @description The URL of the incoming payment that is being paid. */ receiver: string; + /** + * Wallet Address + * Format: uri + * @description URL of a wallet address hosted by a Rafiki instance. + */ + walletAddress: string; }; responses: never; parameters: never; @@ -381,7 +387,7 @@ export type operations = { * @description The interaction reference generated for this * interaction by the AS. */ - interact_ref: string; + interact_ref?: string; }; }; }; diff --git a/packages/token-introspection/src/openapi/index.test.ts b/packages/token-introspection/src/openapi/index.test.ts deleted file mode 100644 index 1c71600303..0000000000 --- a/packages/token-introspection/src/openapi/index.test.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { getTokenIntrospectionOpenAPI } from '.' - -describe('OpenAPI', (): void => { - describe('getTokenIntrospectionOpenAPI', () => { - test('properly generates API paths', async () => { - const openApi = await getTokenIntrospectionOpenAPI() - - expect(openApi).toBeDefined() - expect(Object.keys(openApi.paths)).toEqual(expect.arrayContaining(['/'])) - }) - - test('properly references $ref to external ./schemas.yaml', async () => { - const openApi = await getTokenIntrospectionOpenAPI() - - expect( - openApi.paths?.['/']?.['post']?.['requestBody']?.['content'][ - 'application/json' - ]['schema']['properties']['access']['items']['oneOf'] - .map((access) => access.properties.type.enum[0]) - .sort() - ).toEqual(['incoming-payment', 'outgoing-payment', 'quote'].sort()) - }) - }) -}) diff --git a/packages/token-introspection/src/openapi/index.ts b/packages/token-introspection/src/openapi/index.ts deleted file mode 100644 index aa987cfad0..0000000000 --- a/packages/token-introspection/src/openapi/index.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { createOpenAPI } from '@interledger/openapi' -import path from 'path' - -/** - * Returns the OpenAPI object for the Token Introspection OpenAPI spec. - * This object allows validating requests and responses against the spec. - * See more: https://github.com/interledger/open-payments/blob/main/packages/openapi/README.md - */ -export async function getTokenIntrospectionOpenAPI() { - return createOpenAPI( - path.resolve(__dirname, './specs/token-introspection.yaml') - ) -} diff --git a/packages/token-introspection/src/openapi/token-introspection.yaml b/packages/token-introspection/src/openapi/token-introspection.yaml deleted file mode 100644 index 566ee2a9a1..0000000000 --- a/packages/token-introspection/src/openapi/token-introspection.yaml +++ /dev/null @@ -1,116 +0,0 @@ -openapi: 3.1.0 -info: - title: Rafiki Authorization Server - Resource Server Connection - version: '1.0' - license: - name: Apache 2.0 - identifier: Apache-2.0 - summary: Rafiki Authorization Server - Resource Server Connection - description: 'The Open Payments API is secured via [GNAP](https://datatracker.ietf.org/doc/html/draft-ietf-gnap-core-protocol). This specification describes the connection between the Rafiki Open Payments Authorization Server and the Rafiki Open Payments Resource Server, which is an opinionated version of the [Grant Negotiation and Authorization Protocol Resource Server Connections](https://datatracker.ietf.org/doc/html/draft-ietf-gnap-resource-servers).' - contact: - email: tech@interledger.org -servers: - - url: 'https://openpayments.guide/auth' -tags: - - name: introspection - description: Token introspection -paths: - /: - parameters: [] - post: - summary: Introspect Access Token - operationId: post-introspect - responses: - '200': - description: OK - content: - application/json: - schema: - oneOf: - - properties: - active: - type: boolean - enum: - - false - required: - - active - - $ref: '#/components/schemas/token-info' - examples: - Token Introspection: - value: - active: true - grant: 1ee48d97-8fa1-4ab4-b89d-95284b665517 - access: - - type: outgoing-payment - actions: - - create - - read - identifier: 'https://openpayments.guide/alice' - limits: - interval: 'R12/2019-08-24T14:15:22Z/P1M' - receiver: 'https://openpayments.guide/bob/incoming-payments/48884225-b393-4872-90de-1b737e2491c2' - debitAmount: - value: '500' - assetCode: USD - assetScale: 2 - client: 'https://webmonize.com/.well-known/pay' - '404': - description: Not Found - description: Introspect an access token to get grant details. - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - access_token: - type: string - description: The access token value presented to the RS by the client instance. - access: - $ref: ./auth-server.yaml#/components/schemas/access - required: - - access_token - examples: - Introspect token: - value: - access_token: OS9M2PMHKUR64TB8N6BW7OZB8CDFONP219RP1LT0 - tags: - - introspection -components: - schemas: - token-info: - title: token-info - type: object - properties: - active: - type: boolean - enum: - - true - grant: - type: string - access: - $ref: ./auth-server.yaml#/components/schemas/access - client: - title: client - type: string - description: |- - Wallet address of the client instance that is making this request. - - When sending a non-continuation request to the AS, the client instance MUST identify itself by including the client field of the request and by signing the request. - - A JSON Web Key Set document, including the public key that the client instance will use to protect this request and any continuation requests at the AS and any user-facing information about the client instance used in interactions, MUST be available at the wallet address + `/jwks.json` url. - - If sending a grant initiation request that requires RO interaction, the wallet address MUST serve necessary client display information. - required: - - active - - grant - - access - - client - securitySchemes: - GNAP: - name: Authorization - type: apiKey - in: header -security: - - GNAP: [] diff --git a/packages/token-introspection/src/test/helpers.ts b/packages/token-introspection/src/test/helpers.ts index 44b189d8a4..7f2adf16cc 100644 --- a/packages/token-introspection/src/test/helpers.ts +++ b/packages/token-introspection/src/test/helpers.ts @@ -1,7 +1,6 @@ import createLogger from 'pino' import { createAxiosInstance } from '../client' import { TokenInfo } from '../types' -import { ResponseValidator } from '@interledger/openapi' export const silentLogger = createLogger({ level: 'silent' @@ -12,16 +11,6 @@ export const defaultAxiosInstance = createAxiosInstance({ requestTimeoutMs: 0 }) -export const mockOpenApiResponseValidators = () => ({ - successfulValidator: ((data: unknown): data is unknown => - // eslint-disable-next-line @typescript-eslint/no-explicit-any - true) as ResponseValidator, - failedValidator: ((data: unknown): data is unknown => { - throw new Error('Failed to validate response') - // eslint-disable-next-line @typescript-eslint/no-explicit-any - }) as ResponseValidator -}) - export const mockTokenInfo = (overrides?: Partial): TokenInfo => ({ active: true, access: [ diff --git a/packages/token-introspection/tsconfig.json b/packages/token-introspection/tsconfig.json index bc4252bfc2..bd9105e444 100644 --- a/packages/token-introspection/tsconfig.json +++ b/packages/token-introspection/tsconfig.json @@ -1,10 +1,10 @@ { "extends": "../../tsconfig.build.json", "compilerOptions": { + "composite": true, "lib": ["ES2020"], "outDir": "./dist", - "rootDir": "./src", - "declaration": true + "rootDir": "./src" }, "include": ["src/**/*"], "exclude": ["**/*.test.ts", "src/test/*"]