Skip to content

Commit 2e01746

Browse files
authored
Merge branch 'main' into docs/CM-1333-akrites-cdp-auth-adr
2 parents a8c185e + 6d840a9 commit 2e01746

107 files changed

Lines changed: 13209 additions & 309 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
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+
import { submitBlastRadiusJob } from '../packages/submitBlastRadiusJob'
15+
16+
const rateLimiter = createRateLimiter({ max: 60, windowMs: 60 * 1000 })
17+
18+
// Blast-radius jobs kick off a Temporal workflow per request, so they get their own,
19+
// much stricter limiter — configurable via env so it can be tuned without a redeploy
20+
// while the pipeline is still a no-op stub. Defaults to 5 requests/hour.
21+
const blastRadiusRateLimitMax = Number(process.env.AKRITES_BLAST_RADIUS_RATE_LIMIT_MAX)
22+
const blastRadiusRateLimitWindowMs = Number(process.env.AKRITES_BLAST_RADIUS_RATE_LIMIT_WINDOW_MS)
23+
24+
const blastRadiusRateLimiter = createRateLimiter({
25+
max:
26+
Number.isSafeInteger(blastRadiusRateLimitMax) && blastRadiusRateLimitMax > 0
27+
? blastRadiusRateLimitMax
28+
: 5,
29+
windowMs:
30+
Number.isSafeInteger(blastRadiusRateLimitWindowMs) && blastRadiusRateLimitWindowMs > 0
31+
? blastRadiusRateLimitWindowMs
32+
: 60 * 60 * 1000,
33+
})
34+
35+
export function akritesExternalRouter(): Router {
36+
const router = Router()
37+
38+
// TODO: swap for a dedicated cdp:packages:read scope once Akrites gets its own
39+
// Auth0 M2M scopes (per the akrites-external draft contract) — reusing the
40+
// internal CDP UI scopes for now since that's what's actually issued today.
41+
const packagesSubRouter = Router()
42+
packagesSubRouter.use(rateLimiter)
43+
packagesSubRouter.use(requireScopes([SCOPES.READ_PACKAGES, SCOPES.READ_STEWARDSHIPS], 'all'))
44+
packagesSubRouter.get('/detail', safeWrap(getAkritesExternalPackageDetail))
45+
packagesSubRouter.post(/^\/detail:batch\/?$/, safeWrap(getAkritesExternalPackageDetailBatch))
46+
router.use('/packages', packagesSubRouter)
47+
48+
// TODO: the contract gates advisories behind a dedicated read:advisories scope
49+
// (see the scope-naming note in the akrites-external OpenAPI). That scope isn't
50+
// issued by Auth0 yet, so reuse READ_PACKAGES for now — advisories are package
51+
// security data and, unlike the packages endpoints above, need no stewardship read.
52+
const advisoriesSubRouter = Router()
53+
advisoriesSubRouter.use(rateLimiter)
54+
advisoriesSubRouter.use(requireScopes([SCOPES.READ_PACKAGES]))
55+
advisoriesSubRouter.get('/detail', safeWrap(getAkritesExternalAdvisoryDetail))
56+
advisoriesSubRouter.post(/^\/detail:batch\/?$/, safeWrap(getAkritesExternalAdvisoryDetailBatch))
57+
router.use('/advisories', advisoriesSubRouter)
58+
59+
// Security contacts expose contact PII (e.g. reporter emails), so the contract gates
60+
// them behind a dedicated cdp:maintainers:read scope and explicitly forbids reaching
61+
// them via the packages scope. That scope isn't issued by Auth0 yet, so reuse the
62+
// closest issued one — READ_MAINTAINER_ROLES (maintainer data) — NOT READ_PACKAGES.
63+
// TODO: swap for cdp:maintainers:read once issued.
64+
const contactsSubRouter = Router()
65+
contactsSubRouter.use(rateLimiter)
66+
contactsSubRouter.use(requireScopes([SCOPES.READ_MAINTAINER_ROLES]))
67+
contactsSubRouter.get('/detail', safeWrap(getAkritesExternalContactDetail))
68+
contactsSubRouter.post(/^\/detail:batch\/?$/, safeWrap(getAkritesExternalContactDetailBatch))
69+
router.use('/contacts', contactsSubRouter)
70+
71+
// TODO: the contract gates blast-radius behind a dedicated read:advisories scope
72+
// (same as advisories above — see the scope-naming note in the akrites-external
73+
// OpenAPI). Not issued by Auth0 yet, so reuse READ_PACKAGES for now.
74+
const blastRadiusSubRouter = Router()
75+
blastRadiusSubRouter.use(blastRadiusRateLimiter)
76+
blastRadiusSubRouter.use(requireScopes([SCOPES.READ_PACKAGES]))
77+
blastRadiusSubRouter.post('/jobs', safeWrap(submitBlastRadiusJob))
78+
router.use('/blast-radius', blastRadiusSubRouter)
79+
80+
return router
81+
}

0 commit comments

Comments
 (0)