Skip to content
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

chore(backend): update OpenAPI specs #799

Merged
merged 7 commits into from
Dec 12, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/workflows/lint_test_build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ jobs:
- uses: actions/checkout@v2
- uses: ./.github/workflows/rafiki/env-setup
- run: pnpm --filter backend build:deps
- run: pnpm --filter backend fetch-schemas
- run: pnpm --filter backend test

frontend:
Expand Down Expand Up @@ -53,7 +54,7 @@ jobs:
echo "{\"extends\":[\"spectral:oas\",\"spectral:asyncapi\"]}" >> .spectral.json
- name: Validate Open API specs
run: |
npx @stoplight/spectral-cli lint ./packages/auth/openapi/id-provider.yaml ./packages/auth/openapi/resource-server.yaml
npx @stoplight/spectral-cli lint ./packages/auth/openapi/*.yaml

openapi:
runs-on: ubuntu-latest
Expand Down
34 changes: 7 additions & 27 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,13 @@ pnpm clean
# install dependencies
pnpm i

# pull in latest openapi specs:
pnpm fetch-schemas
```

### Local Development

```sh
# build all the packages in the repo:
pnpm -r build
# build specific package (backend):
Expand All @@ -81,9 +88,6 @@ pnpm --filter open-api test
# run all tests
pnpm -r --workspace-concurrency=1 test

# pull in latest openapi specs for auth server:
pnpm --filter auth fetch-schemas

# format and lint code:
pnpm format

Expand All @@ -97,29 +101,5 @@ pnpm check:format
pnpm check:lint
```

### Local Development

The [infrastructure/local](infrastructure/local) directory contains resources for setting up Rafiki in
common configurations.

```sh
# set up two instances of Rafiki
pnpm localenv up -d

# seed the postgres databases with the auth data creating an admin token
pnpm localenv:seed:auth

# tear down
pnpm localenv down

# delete database volumes (containers must be removed first with e.g. pnpm localenv down)
pnpm localenv:dbvolumes:remove
```

The local environment consists of a primary Rafiki instance and a secondary Rafiki instance, each with
its own docker compose files ([primary](infrastructure/local/docker-compose.yml), [secondary](infrastructure/local/peer-docker-compose.yml)).
The primary `docker-compose.yml` includes the main Rafiki services `backend`, `auth`, and `rates`, as well
as the required data stores tigerbeetle, redis, and postgres, so it can be run on its own.
The `peer-docker-compose.yml` includes only the Rafiki services, not the data stores. It uses the
data stores created by the primary Rafiki instance so it can't be run by itself.
The `pnpm run localenv` command starts both the primary instance and the secondary.
15 changes: 15 additions & 0 deletions infrastructure/local/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ admin GraphQL.

Prerequisites:

- [Rafiki local environment setup](../../README.md#environment-setup)
- [docker](https://docs.docker.com/get-docker/)
- [compose plugin](https://docs.docker.com/compose/install/compose-plugin/)
- [postman](https://www.postman.com/downloads/)
Expand All @@ -21,8 +22,22 @@ pnpm localenv up -d --build

// Seed auth tokens
pnpm localenv:seed:auth

// tear down
pnpm localenv down

// delete database volumes (containers must be removed first with e.g. pnpm localenv down)
pnpm localenv:dbvolumes:remove
```

The local environment consists of a primary Rafiki instance and a secondary Rafiki instance, each with
its own docker compose files ([primary](./docker-compose.yml), [secondary](./peer-docker-compose.yml)).
The primary `docker-compose.yml` includes the main Rafiki services `backend`, `auth`, and `rates`, as well
as the required data stores tigerbeetle, redis, and postgres, so it can be run on its own.
The `peer-docker-compose.yml` includes only the Rafiki services, not the data stores. It uses the
data stores created by the primary Rafiki instance so it can't be run by itself.
The `pnpm run localenv` command starts both the primary instance and the secondary.

## P2P payment

This will demonstrate a P2P payment from Grace Franklin (Fynbos account) to Philip Fry (local bank account) using
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"check:prettier": "prettier --check .",
"clean": "find . -name 'node_modules' -type d -prune -exec rm -rf '{}' +",
"build": "tsc --build",
"fetch-schemas": "pnpm --filter auth --filter backend fetch-schemas",
"localenv": "docker compose -f ./infrastructure/local/docker-compose.yml -f ./infrastructure/local/peer-docker-compose.yml",
"localenv:build": "docker compose -f ./infrastructure/local/docker-compose.yml -f ./infrastructure/local/peer-docker-compose.yml -f ./infrastructure/local/build-override.yml build",
"localenv:seed:auth": "pnpm -C ./packages/auth knex seed:run --env=development && pnpm -C ./packages/auth knex seed:run --env=peerdevelopment",
Expand Down
4 changes: 2 additions & 2 deletions packages/auth/scripts/get-op-schema.sh
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@ while [ -L "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symli
done
OUTDIR=$( cd -P "$( dirname "$SOURCE" )/../src/openapi" >/dev/null 2>&1 && pwd )

curl -o "$OUTDIR/schemas.yaml" https://raw.githubusercontent.com/interledger/open-payments/b363d33038fe789e5388f04f80ddd06a4fa97093/openapi/schemas.yaml
curl -o "$OUTDIR/auth-server.yaml" https://raw.githubusercontent.com/interledger/open-payments/b363d33038fe789e5388f04f80ddd06a4fa97093/openapi/auth-server.yaml
curl -o "$OUTDIR/schemas.yaml" https://raw.githubusercontent.com/interledger/open-payments/ae0e924a56fe9f12834f65da109a1042760c1605/openapi/schemas.yaml
curl -o "$OUTDIR/auth-server.yaml" https://raw.githubusercontent.com/interledger/open-payments/ae0e924a56fe9f12834f65da109a1042760c1605/openapi/auth-server.yaml
2 changes: 1 addition & 1 deletion packages/auth/src/accessToken/routes.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ describe('Access Token Routes', (): void => {
})

const openApi = await deps.use('openApi')
jestOpenAPI(openApi.resourceServerSpec)
jestOpenAPI(openApi.tokenIntrospectionSpec)
})
test('Cannot introspect fake token', async (): Promise<void> => {
const ctx = createContext(
Expand Down
2 changes: 1 addition & 1 deletion packages/auth/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@ export class App {
// Token Introspection
this.publicRouter.post(
'/introspect',
createValidatorMiddleware(openApi.resourceServerSpec, {
createValidatorMiddleware(openApi.tokenIntrospectionSpec, {
path: '/introspect',
method: HttpMethod.POST
}),
Expand Down
36 changes: 22 additions & 14 deletions packages/auth/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,20 +111,28 @@ export function initIocContainer(
})
})

container.singleton('openApi', async () => {
const authServerSpec = await createOpenAPI(
path.resolve(__dirname, './openapi/auth-server.yaml')
)
const resourceServerSpec = await createOpenAPI(
path.resolve(__dirname, './openapi/resource-server.yaml')
)
const idpSpec = await createOpenAPI(
path.resolve(__dirname, './openapi/id-provider.yaml')
)
return {
authServerSpec,
resourceServerSpec,
idpSpec
container.singleton('openApi', async (deps: IocContract<AppServices>) => {
try {
const authServerSpec = await createOpenAPI(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what do you think of openPaymentsAuthServerSpec? Likewise with openPaymentsResourceServerSpec in the backend?

Trying to see if there is a better way of distinguishing the several different spec we have

Copy link
Contributor Author

@wilsonianb wilsonianb Dec 9, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one here is actually authServerSpec (from https://github.com/interledger/rafiki/blob/main/packages/auth/src/openapi/resource-server.yaml)
I think openPaymentsAuthServerSpec would be https://github.com/interledger/open-payments/blob/main/openapi/schemas.yaml

What if we just called the first one introspection/tokenIntrospection for both the file name (make it consistent in both auth and backend) and the variable name?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm gonna change https://github.com/interledger/rafiki/blob/main/packages/auth/src/openapi/resource-server.yaml to token-introspection.yaml with

Suggested change
const authServerSpec = await createOpenAPI(
const tokenIntrospectionSpec = await createOpenAPI(

and not

Suggested change
const authServerSpec = await createOpenAPI(
const tokenIntroSPECtion = await createOpenAPI(

and keep the other file and variable names as is.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The tokenIntrospectionSpec change makes sense, but it should be replacingresourceServerSpec variable (the one that contains the introspection route), right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ha, yeah. I was thinking these comments were for packages/backend/src/index.ts 🙃

path.resolve(__dirname, './openapi/auth-server.yaml')
)
const idpSpec = await createOpenAPI(
path.resolve(__dirname, './openapi/id-provider.yaml')
)
const tokenIntrospectionSpec = await createOpenAPI(
path.resolve(__dirname, './openapi/token-introspection.yaml')
)
return {
authServerSpec,
idpSpec,
tokenIntrospectionSpec
}
} catch (err) {
const logger = await deps.use('logger')
logger.error({ err }, 'error while loading OpenAPI files')
throw new Error(
'Could not load OpenAPI files. Did you run `pnpm fetch-schemas`?'
)
}
})

Expand Down
2 changes: 2 additions & 0 deletions packages/backend/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,5 @@ build
.env
src/coverage
tmp
/src/openapi/resource-server.yaml
/src/openapi/schemas.yaml
1 change: 1 addition & 0 deletions packages/backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"build": "pnpm build:deps && tsc --build tsconfig.json && pnpm copy-files",
"clean": "rm -fr dist/",
"copy-files": "cp src/graphql/schema.graphql dist/graphql/",
"fetch-schemas": "./scripts/get-op-schema.sh",
"prepack": "pnpm build"
},
"devDependencies": {
Expand Down
13 changes: 13 additions & 0 deletions packages/backend/scripts/get-op-schema.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#!/bin/bash

## https://stackoverflow.com/questions/59895/how-do-i-get-the-directory-where-a-bash-script-is-located-from-within-the-script
SOURCE=${BASH_SOURCE[0]}
while [ -L "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlink
DIR=$( cd -P "$( dirname "$SOURCE" )" >/dev/null 2>&1 && pwd )
SOURCE=$(readlink "$SOURCE")
[[ $SOURCE != /* ]] && SOURCE=$DIR/$SOURCE # if $SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was located
done
OUTDIR=$( cd -P "$( dirname "$SOURCE" )/../src/openapi" >/dev/null 2>&1 && pwd )

curl -o "$OUTDIR/resource-server.yaml" https://raw.githubusercontent.com/interledger/open-payments/ae0e924a56fe9f12834f65da109a1042760c1605/openapi/resource-server.yaml
curl -o "$OUTDIR/schemas.yaml" https://raw.githubusercontent.com/interledger/open-payments/ae0e924a56fe9f12834f65da109a1042760c1605/openapi/schemas.yaml
32 changes: 19 additions & 13 deletions packages/backend/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@ export class App {
)
const quoteRoutes = await this.container.use('quoteRoutes')
const connectionRoutes = await this.container.use('connectionRoutes')
const openApi = await this.container.use('openApi')
const { resourceServerSpec } = await this.container.use('openApi')
const toRouterPath = (path: string): string =>
path.replace(/{/g, ':').replace(/}/g, '')

Expand Down Expand Up @@ -291,8 +291,8 @@ export class App {
[AccessAction.ListAll]: 'list'
}

for (const path in openApi.paths) {
for (const method in openApi.paths[path]) {
for (const path in resourceServerSpec.paths) {
for (const method in resourceServerSpec.paths[path]) {
if (isHttpMethod(method)) {
const action = toAction({ path, method })
assert.ok(action)
Expand All @@ -313,10 +313,13 @@ export class App {
route = connectionRoutes.get
router[method](
toRouterPath(path),
createValidatorMiddleware<ContextType<typeof route>>(openApi, {
path,
method
}),
createValidatorMiddleware<ContextType<typeof route>>(
resourceServerSpec,
{
path,
method
}
),
route
)
} else if (path !== '/' || method !== HttpMethod.GET) {
Expand All @@ -328,10 +331,13 @@ export class App {
router[method](
PAYMENT_POINTER_PATH + toRouterPath(path),
createPaymentPointerMiddleware(),
createValidatorMiddleware<ContextType<typeof route>>(openApi, {
path,
method
}),
createValidatorMiddleware<ContextType<typeof route>>(
resourceServerSpec,
{
path,
method
}
),
createAuthMiddleware({
type,
action
Expand All @@ -347,7 +353,7 @@ export class App {
router.get(
PAYMENT_POINTER_PATH + '/jwks.json',
createPaymentPointerMiddleware(),
createValidatorMiddleware<PaymentPointerContext>(openApi, {
createValidatorMiddleware<PaymentPointerContext>(resourceServerSpec, {
path: '/jwks.json',
method: HttpMethod.GET
}),
Expand All @@ -362,7 +368,7 @@ export class App {
router.get(
PAYMENT_POINTER_PATH,
createPaymentPointerMiddleware(),
createValidatorMiddleware<PaymentPointerContext>(openApi, {
createValidatorMiddleware<PaymentPointerContext>(resourceServerSpec, {
path: '/',
method: HttpMethod.GET
}),
Expand Down
8 changes: 0 additions & 8 deletions packages/backend/src/config/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,14 +109,6 @@ export const Config = {
signatureVersion: envInt('SIGNATURE_VERSION', 1),
bypassSignatureValidation: envBool('BYPASS_SIGNATURE_VALIDATION', false),

openPaymentsSpec: envString(
'OPEN_PAYMENTS_SPEC',
'https://raw.githubusercontent.com/interledger/open-payments/f365dbec4b9dec98b9f622bc49a92aea9ee01568/openapi/RS/openapi.yaml'
),
authServerSpec: envString(
'AUTH_SERVER_SPEC',
'https://raw.githubusercontent.com/interledger/open-payments/77462cd0872be8d0fa487a4b233defe2897a7ee4/auth-server-open-api-spec.yaml'
),
keyId: envString('KEY_ID', 'rafiki'),
privateKey: parseOrProvisionKey(envString('PRIVATE_KEY_FILE', undefined)),

Expand Down
30 changes: 22 additions & 8 deletions packages/backend/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { EventEmitter } from 'events'
import { Server } from 'http'
import path from 'path'
import createLogger from 'pino'
import { knex } from 'knex'
import { Model } from 'objection'
Expand Down Expand Up @@ -108,13 +109,25 @@ export function initIocContainer(
replica_addresses: config.tigerbeetleReplicaAddresses
})
})
container.singleton('openApi', async (deps) => {
const config = await deps.use('config')
return await createOpenAPI(config.openPaymentsSpec)
})
container.singleton('authOpenApi', async (deps) => {
const config = await deps.use('config')
return await createOpenAPI(config.authServerSpec)
container.singleton('openApi', async (deps: IocContract<AppServices>) => {
try {
const tokenIntrospectionSpec = await createOpenAPI(
path.resolve(__dirname, './openapi/token-introspection.yaml')
)
const resourceServerSpec = await createOpenAPI(
path.resolve(__dirname, './openapi/resource-server.yaml')
)
return {
resourceServerSpec,
tokenIntrospectionSpec
}
} catch (err) {
const logger = await deps.use('logger')
logger.error({ err }, 'error while loading OpenAPI files')
throw new Error(
'Could not load OpenAPI files. Did you run `pnpm fetch-schemas`?'
)
}
})
container.singleton('openPaymentsClient', async (deps) => {
const config = await deps.use('config')
Expand Down Expand Up @@ -169,10 +182,11 @@ export function initIocContainer(
})
container.singleton('authService', async (deps) => {
const config = await deps.use('config')
const { tokenIntrospectionSpec } = await deps.use('openApi')
return await createAuthService({
logger: await deps.use('logger'),
authServerIntrospectionUrl: config.authServerIntrospectionUrl,
authOpenApi: await deps.use('authOpenApi')
tokenIntrospectionSpec
})
})
container.singleton('paymentPointerService', async (deps) => {
Expand Down
4 changes: 2 additions & 2 deletions packages/backend/src/open_payments/auth/middleware.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,9 +123,9 @@ describe('Auth Middleware', (): void => {
type: AccessType.IncomingPayment,
action: AccessAction.Read
})
const authOpenApi = await deps.use('authOpenApi')
const { tokenIntrospectionSpec } = await deps.use('openApi')
requestPath = '/introspect'
validateRequest = authOpenApi.createRequestValidator({
validateRequest = tokenIntrospectionSpec.createRequestValidator({
path: requestPath,
method: HttpMethod.POST
})
Expand Down
4 changes: 2 additions & 2 deletions packages/backend/src/open_payments/auth/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export interface AuthService {

interface ServiceDependencies {
authServerIntrospectionUrl: string
authOpenApi: OpenAPI
tokenIntrospectionSpec: OpenAPI
logger: Logger
validateResponse: ResponseValidator<TokenInfoJSON>
}
Expand All @@ -50,7 +50,7 @@ export async function createAuthService(
service: 'AuthService'
})
const validateResponse =
deps_.authOpenApi.createResponseValidator<TokenInfoJSON>({
deps_.tokenIntrospectionSpec.createResponseValidator<TokenInfoJSON>({
path: '/introspect',
method: HttpMethod.POST
})
Expand Down
3 changes: 2 additions & 1 deletion packages/backend/src/open_payments/connection/routes.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ describe('Connection Routes', (): void => {
deps = await initIocContainer(config)
appContainer = await createTestApp(deps)
knex = await deps.use('knex')
jestOpenAPI(await deps.use('openApi'))
const { resourceServerSpec } = await deps.use('openApi')
jestOpenAPI(resourceServerSpec)
})

const asset = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ describe('Incoming Payment Routes', (): void => {
deps = await initIocContainer(config)
appContainer = await createTestApp(deps)
knex = await deps.use('knex')
jestOpenAPI(await deps.use('openApi'))
const { resourceServerSpec } = await deps.use('openApi')
jestOpenAPI(resourceServerSpec)
})

const asset = {
Expand Down
Loading