Skip to content

Commit

Permalink
feat: auth service-to-service api (#3148)
Browse files Browse the repository at this point in the history
* feat(auth): add service api with /healtz endpoint

* feat(auth): tenant routes

* feat(auth): service api error handling

* chore(auth): rm old todo

* fix(auth): how errors are set

* fix(auth): improve tenant tests, cleanup tenant get response,

* feat(backend): auth service api client

* fix(auth): change status codes to 204 where no body

* fix(backend): format

* feat(auth): add required deletedAt to DELETE /tenant body

* feat(backend): AUTH_SERVICE_API_URL env var

* fix(backend): auth service client tests to mock codes correctly

* feat(backend): add AuthServiceClient dep

* feat(backend): use auth service client in tenant service

* chore(auth): format

* chore(auth): format

* fix(integration,localenv): auth service api config

* fix(backend,auth): update tenant api to support deletedAt

* docs: update with env vars

* fix(backend): dep container type

* fix(localenv): docker compose config

* fix(backend): add default header to api client
  • Loading branch information
BlairCurrey authored Jan 14, 2025
1 parent a8b7ca4 commit fd8283b
Show file tree
Hide file tree
Showing 26 changed files with 835 additions and 211 deletions.
3 changes: 3 additions & 0 deletions localenv/cloud-nine-wallet/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ services:
AUTH_SERVER_INTROSPECTION_URL: http://cloud-nine-wallet-auth:3007
AUTH_ADMIN_API_URL: 'http://cloud-nine-wallet-auth:3003/graphql'
AUTH_ADMIN_API_SECRET: 'rPoZpe9tVyBNCigm05QDco7WLcYa0xMao7lO5KG1XG4='
AUTH_SERVICE_API_URL: 'http://cloud-nine-wallet-auth:3011'
ILP_ADDRESS: ${ILP_ADDRESS:-test.cloud-nine-wallet}
STREAM_SECRET: BjPXtnd00G2mRQwP/8ZpwyZASOch5sUXT5o0iR5b5wU=
API_SECRET: iyIgCprjb9uL8wFckR+pLEkJWMB7FJhgkvqhTQR/964=
Expand Down Expand Up @@ -108,6 +109,7 @@ services:
- '3006:3006'
- "9230:9229"
- '3009:3009'
- '3011:3011'
environment:
NODE_ENV: ${NODE_ENV:-development}
TRUST_PROXY: ${TRUST_PROXY}
Expand All @@ -119,6 +121,7 @@ services:
COOKIE_KEY: 42397d1f371dd4b8b7d0308a689a57c882effd4ea909d792302542af47e2cd37
ADMIN_API_SECRET: rPoZpe9tVyBNCigm05QDco7WLcYa0xMao7lO5KG1XG4=
OPERATOR_TENANT_ID: 438fa74a-fa7d-4317-9ced-dde32ece1787
SERVICE_API_PORT: 3011
depends_on:
- shared-database
- shared-redis
Expand Down
3 changes: 3 additions & 0 deletions localenv/happy-life-bank/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ services:
AUTH_SERVER_INTROSPECTION_URL: http://happy-life-bank-auth:3007
AUTH_ADMIN_API_URL: 'http://happy-life-bank-auth:4003/graphql'
AUTH_ADMIN_API_SECRET: 'rPoZpe9tVyBNCigm05QDco7WLcYa0xMao7lO5KG1XG4='
AUTH_SERVICE_API_URL: 'http://happy-life-bank-auth:4011'
ILP_ADDRESS: test.happy-life-bank
ILP_CONNECTOR_URL: http://happy-life-bank-backend:4002
STREAM_SECRET: BjPXtnd00G2mRQwP/8ZpwyZASOch5sUXT5o0iR5b5wU=
Expand Down Expand Up @@ -98,6 +99,7 @@ services:
- '4006:3006'
- '9232:9229'
- '4009:3009'
- '4011:4011'
environment:
NODE_ENV: development
AUTH_DATABASE_URL: postgresql://happy_life_bank_auth:happy_life_bank_auth@shared-database/happy_life_bank_auth
Expand All @@ -108,6 +110,7 @@ services:
COOKIE_KEY: 42397d1f371dd4b8b7d0308a689a57c882effd4ea909d792302542af47e2cd37
ADMIN_API_SECRET: rPoZpe9tVyBNCigm05QDco7WLcYa0xMao7lO5KG1XG4=
OPERATOR_TENANT_ID: cf5fd7d3-1eb1-4041-8e43-ba45747e9e5d
SERVICE_API_PORT: 4011
depends_on:
- cloud-nine-auth
happy-life-admin:
Expand Down
53 changes: 53 additions & 0 deletions packages/auth/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ export class App {
private interactionServer!: Server
private introspectionServer!: Server
private adminServer!: Server
private serviceAPIServer!: Server
private logger!: Logger
private config!: IAppConfig
private databaseCleanupRules!: {
Expand Down Expand Up @@ -455,6 +456,51 @@ export class App {
this.interactionServer = koa.listen(port)
}

public async startServiceAPIServer(port: number | string): Promise<void> {
const koa = await this.createKoaServer()

const router = new Router<DefaultState, AppContext>()
router.use(bodyParser())

const errorHandler = async (ctx: Koa.Context, next: Koa.Next) => {
try {
await next()
} catch (err) {
const logger = await ctx.container.use('logger')
logger.info(
{
method: ctx.method,
route: ctx.path,
headers: ctx.headers,
params: ctx.params,
requestBody: ctx.request.body,
err
},
'Service API Error'
)
}
}

koa.use(errorHandler)

router.get('/healthz', (ctx: AppContext): void => {
ctx.status = 200
})

const tenantRoutes = await this.container.use('tenantRoutes')

router.get('/tenant/:id', tenantRoutes.get)
router.post('/tenant', tenantRoutes.create)
router.patch('/tenant/:id', tenantRoutes.update)
router.delete('/tenant/:id', tenantRoutes.delete)

koa.use(cors())
koa.use(router.middleware())
koa.use(router.routes())

this.serviceAPIServer = koa.listen(port)
}

private async createKoaServer(): Promise<Koa<Koa.DefaultState, AppContext>> {
const koa = new Koa<DefaultState, AppContext>({
proxy: this.config.trustProxy
Expand Down Expand Up @@ -500,6 +546,9 @@ export class App {
if (this.introspectionServer) {
await this.stopServer(this.introspectionServer)
}
if (this.serviceAPIServer) {
await this.stopServer(this.serviceAPIServer)
}
}

private async stopServer(server: Server): Promise<void> {
Expand Down Expand Up @@ -530,6 +579,10 @@ export class App {
return this.getPort(this.introspectionServer)
}

public getServiceAPIPort(): number {
return this.getPort(this.serviceAPIServer)
}

private getPort(server: Server): number {
const address = server?.address()
if (address && !(typeof address == 'string')) {
Expand Down
1 change: 1 addition & 0 deletions packages/auth/src/config/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export const Config = {
authPort: envInt('AUTH_PORT', 3006),
interactionPort: envInt('INTERACTION_PORT', 3009),
introspectionPort: envInt('INTROSPECTION_PORT', 3007),
serviceAPIPort: envInt('SERVICE_API_PORT', 3011),
env: envString('NODE_ENV', 'development'),
trustProxy: envBool('TRUST_PROXY', false),
enableManualMigrations: envBool('ENABLE_MANUAL_MIGRATIONS', false),
Expand Down
14 changes: 14 additions & 0 deletions packages/auth/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { createInteractionService } from './interaction/service'
import { getTokenIntrospectionOpenAPI } from 'token-introspection'
import { Redis } from 'ioredis'
import { createTenantService } from './tenant/service'
import { createTenantRoutes } from './tenant/routes'

const container = initIocContainer(Config)
const app = new App(container)
Expand Down Expand Up @@ -163,6 +164,16 @@ export function initIocContainer(
}
)

container.singleton(
'tenantRoutes',
async (deps: IocContract<AppServices>) => {
return createTenantRoutes({
tenantService: await deps.use('tenantService'),
logger: await deps.use('logger')
})
}
)

container.singleton('openApi', async () => {
const authServerSpec = await getAuthServerOpenAPI()
const idpSpec = await createOpenAPI(
Expand Down Expand Up @@ -315,6 +326,9 @@ export const start = async (

await app.startIntrospectionServer(config.introspectionPort)
logger.info(`Introspection server listening on ${app.getIntrospectionPort()}`)

await app.startServiceAPIServer(config.serviceAPIPort)
logger.info(`Service API server listening on ${app.getServiceAPIPort()}`)
}

// If this script is run directly, start the server
Expand Down
17 changes: 16 additions & 1 deletion packages/auth/src/shared/utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { Config } from '../config/app'
import { createContext } from '../tests/context'
import { generateApiSignature } from '../tests/apiSignature'
import { initIocContainer } from '..'
import { verifyApiSignature } from './utils'
import { verifyApiSignature, isValidDateString } from './utils'
import { TestContainer, createTestApp } from '../tests/app'

describe('utils', (): void => {
Expand Down Expand Up @@ -145,4 +145,19 @@ describe('utils', (): void => {
expect(verified).toBe(false)
})
})

describe('isValidDateString', () => {
test.each([
['2024-12-05T15:10:09.545Z', true],
['2024-12-05', true],
['invalid-date', false], // Invalid date string
['2024-12-05T25:10:09.545Z', false], // Invalid date string (invalid hour)
['"2024-12-05T15:10:09.545Z"', false], // Improperly formatted string
['', false], // Empty string
[null, false], // Null value
[undefined, false] // Undefined value
])('should return %p for input %p', (input, expected) => {
expect(isValidDateString(input!)).toBe(expected)
})
})
})
5 changes: 5 additions & 0 deletions packages/auth/src/shared/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,3 +104,8 @@ export async function verifyApiSignature(

return verifyApiSignatureDigest(signature as string, ctx.request, config)
}

// Intended for Date strings like "2024-12-05T15:10:09.545Z" (e.g., from new Date().toISOString())
export function isValidDateString(date: string): boolean {
return !isNaN(Date.parse(date))
}
Loading

0 comments on commit fd8283b

Please sign in to comment.