|
| 1 | +import { Router } from 'express' |
| 2 | + |
| 3 | +import { createRateLimiter } from '@/api/apiRateLimiter' |
| 4 | +import { requireScopes } from '@/api/public/middlewares/requireScopes' |
| 5 | +import { safeWrap } from '@/middlewares/errorMiddleware' |
| 6 | +import { SCOPES } from '@/security/scopes' |
| 7 | + |
| 8 | +import { getAkritesExternalAdvisoryDetail } from '../packages/getAkritesExternalAdvisoryDetail' |
| 9 | +import { getAkritesExternalAdvisoryDetailBatch } from '../packages/getAkritesExternalAdvisoryDetailBatch' |
| 10 | +import { getAkritesExternalContactDetail } from '../packages/getAkritesExternalContactDetail' |
| 11 | +import { getAkritesExternalContactDetailBatch } from '../packages/getAkritesExternalContactDetailBatch' |
| 12 | +import { getAkritesExternalPackageDetail } from '../packages/getAkritesExternalPackageDetail' |
| 13 | +import { getAkritesExternalPackageDetailBatch } from '../packages/getAkritesExternalPackageDetailBatch' |
| 14 | + |
| 15 | +const rateLimiter = createRateLimiter({ max: 60, windowMs: 60 * 1000 }) |
| 16 | + |
| 17 | +export function akritesExternalRouter(): Router { |
| 18 | + const router = Router() |
| 19 | + |
| 20 | + // TODO: swap for a dedicated cdp:packages:read scope once Akrites gets its own |
| 21 | + // Auth0 M2M scopes (per the akrites-external draft contract) — reusing the |
| 22 | + // internal CDP UI scopes for now since that's what's actually issued today. |
| 23 | + const packagesSubRouter = Router() |
| 24 | + packagesSubRouter.use(rateLimiter) |
| 25 | + packagesSubRouter.use(requireScopes([SCOPES.READ_PACKAGES, SCOPES.READ_STEWARDSHIPS], 'all')) |
| 26 | + packagesSubRouter.get('/detail', safeWrap(getAkritesExternalPackageDetail)) |
| 27 | + packagesSubRouter.post(/^\/detail:batch\/?$/, safeWrap(getAkritesExternalPackageDetailBatch)) |
| 28 | + router.use('/packages', packagesSubRouter) |
| 29 | + |
| 30 | + // TODO: the contract gates advisories behind a dedicated read:advisories scope |
| 31 | + // (see the scope-naming note in the akrites-external OpenAPI). That scope isn't |
| 32 | + // issued by Auth0 yet, so reuse READ_PACKAGES for now — advisories are package |
| 33 | + // security data and, unlike the packages endpoints above, need no stewardship read. |
| 34 | + const advisoriesSubRouter = Router() |
| 35 | + advisoriesSubRouter.use(rateLimiter) |
| 36 | + advisoriesSubRouter.use(requireScopes([SCOPES.READ_PACKAGES])) |
| 37 | + advisoriesSubRouter.get('/detail', safeWrap(getAkritesExternalAdvisoryDetail)) |
| 38 | + advisoriesSubRouter.post(/^\/detail:batch\/?$/, safeWrap(getAkritesExternalAdvisoryDetailBatch)) |
| 39 | + router.use('/advisories', advisoriesSubRouter) |
| 40 | + |
| 41 | + // Security contacts expose contact PII (e.g. reporter emails), so the contract gates |
| 42 | + // them behind a dedicated cdp:maintainers:read scope and explicitly forbids reaching |
| 43 | + // them via the packages scope. That scope isn't issued by Auth0 yet, so reuse the |
| 44 | + // closest issued one — READ_MAINTAINER_ROLES (maintainer data) — NOT READ_PACKAGES. |
| 45 | + // TODO: swap for cdp:maintainers:read once issued. |
| 46 | + const contactsSubRouter = Router() |
| 47 | + contactsSubRouter.use(rateLimiter) |
| 48 | + contactsSubRouter.use(requireScopes([SCOPES.READ_MAINTAINER_ROLES])) |
| 49 | + contactsSubRouter.get('/detail', safeWrap(getAkritesExternalContactDetail)) |
| 50 | + contactsSubRouter.post(/^\/detail:batch\/?$/, safeWrap(getAkritesExternalContactDetailBatch)) |
| 51 | + router.use('/contacts', contactsSubRouter) |
| 52 | + |
| 53 | + return router |
| 54 | +} |
0 commit comments