From 9e2c4ae416347434c48f6bbe20c56b9155da2024 Mon Sep 17 00:00:00 2001 From: Sergey Ukustov Date: Tue, 30 Jul 2024 22:32:26 +0300 Subject: [PATCH] fix: Bypass GET, as original (#1238) * fix: Bypass GET, as original * and error handling --- src/auth/__tests__/auth.middleware.test.ts | 7 ++ src/auth/auth.middleware.ts | 104 +++++++++++---------- 2 files changed, 60 insertions(+), 51 deletions(-) diff --git a/src/auth/__tests__/auth.middleware.test.ts b/src/auth/__tests__/auth.middleware.test.ts index e17967b6..1e6d1350 100644 --- a/src/auth/__tests__/auth.middleware.test.ts +++ b/src/auth/__tests__/auth.middleware.test.ts @@ -51,6 +51,9 @@ describe('Authorization header: strict', () => { app.post('/', (req, res) => { res.json({ hello: 'world' }) }) + app.get('/', (req, res) => { + res.json({ hello: 'world' }) + }) }) test('allowed DID, valid digest', async () => { @@ -74,6 +77,10 @@ describe('Authorization header: strict', () => { .send(Buffer.from(carFile.bytes)) // Supertest quirk expect(response.status).toBe(403) }) + test('GET is bypassed', async () => { + const response = await supertest(app).get('/') + expect(response.status).toEqual(200) + }) test('disallowed DID, valid digest', async () => { const carFile = carFactory.build() const cid = carFile.put({ hello: 'world' }, { isRoot: true }) diff --git a/src/auth/auth.middleware.ts b/src/auth/auth.middleware.ts index 81014702..32ef7ef2 100644 --- a/src/auth/auth.middleware.ts +++ b/src/auth/auth.middleware.ts @@ -22,12 +22,11 @@ CAR_FACTORY.codecs.add(DAG_JOSE) const VERIFIER = new DID({ resolver: KeyDIDResolver.getResolver() }) enum DISALLOW_REASON { - LAMBDA_INVALID_DIGEST = 'lambda-invalid-digest', + INVALID_DIGEST = 'invalid-digest', DID_ALLOWLIST_NO_HEADER = 'did-allowlist-no-header', DID_ALLOWLIST_NO_DID = 'did-allowlist-no-did', DID_ALLOWLIST_NO_FIELDS = 'did-allowlist-no-fields', DID_ALLOWLIST_REJECTED = 'did-allowlist-rejected', - DID_ALLOWLIST_INVALID_DIGEST = 'did-allowlist-invalid-digest', } export function parseAllowedDIDs(dids: string | undefined): Set { @@ -55,59 +54,62 @@ export function auth(opts: AuthOpts): Handler { * this app will still work if the logice above is not in place. */ return async function (req: Request, res: Response, next: NextFunction) { - // const logger = opts.logger - - // Use auth lambda - const didFromHeader = req.header('did') - if (didFromHeader && req.body && Object.keys(req.body).length > 0) { - const digest = buildBodyDigest(req.header('Content-Type'), req.body) - if (req.header('digest') === digest) { - ServiceMetrics.count(METRIC_NAMES.AUTH_ALLOWED, 1, { did: didFromHeader }) - console.log(`Allowed: Auth lambda: ${didFromHeader}`) - return next() - } else { - console.log(`Disallowed: Auth lambda: Invalid digest`) - return disallow(res, DISALLOW_REASON.LAMBDA_INVALID_DIGEST) - } - } - - // Authorization Header - if (hasAllowedDIDsList) { - const authorizationHeader = req.header('Authorization') || '' - const bearerTokenMatch = AUTH_BEARER_REGEXP.exec(authorizationHeader) - const jws = bearerTokenMatch?.[1] - if (!jws) { - console.log(`Disallowed: No authorization header`) - return disallow(res, DISALLOW_REASON.DID_ALLOWLIST_NO_HEADER) - } - const verifyJWSResult = await VERIFIER.verifyJWS(jws) - const did = verifyJWSResult.didResolutionResult.didDocument?.id - if (!did) { - console.log(`Disallowed: No DID`) - return disallow(res, DISALLOW_REASON.DID_ALLOWLIST_NO_DID) - } - const nonce = verifyJWSResult.payload?.['nonce'] - const digest = verifyJWSResult.payload?.['digest'] - if (!nonce || !digest) { - console.log(`Disallowed: No nonce or No digest`) - return disallow(res, DISALLOW_REASON.DID_ALLOWLIST_NO_FIELDS) - } - if (!isAllowedDID(did, opts)) { - console.log(`Disallowed: ${did}`) - return disallow(res, DISALLOW_REASON.DID_ALLOWLIST_REJECTED) + const logger = opts.logger + const hasBody = req.body && Object.keys(req.body).length > 0 + + try { + // Use auth lambda + const didFromHeader = req.header('did') + if (didFromHeader && hasBody) { + const digest = buildBodyDigest(req.header('Content-Type'), req.body) + if (req.header('digest') === digest) { + ServiceMetrics.count(METRIC_NAMES.AUTH_ALLOWED, 1, { did: didFromHeader }) + logger?.verbose(`Allowed: Auth lambda: ${didFromHeader}`) + return next() + } else { + logger?.verbose(`Disallowed: Auth lambda: Invalid digest`) + return disallow(res, DISALLOW_REASON.INVALID_DIGEST) + } } - const body = req.body - const contentType = req.header('Content-Type') - const digestCalculated = buildBodyDigest(contentType, body) - if (digestCalculated !== digest) { - console.log(`Disallowed: Incorrect digest for DID ${did}`) - return disallow(res, DISALLOW_REASON.DID_ALLOWLIST_INVALID_DIGEST) + // Authorization Header + if (hasAllowedDIDsList && hasBody) { + const authorizationHeader = req.header('Authorization') || '' + const bearerTokenMatch = AUTH_BEARER_REGEXP.exec(authorizationHeader) + const jws = bearerTokenMatch?.[1] + if (!jws) { + logger?.verbose(`Disallowed: No authorization header`) + return disallow(res, DISALLOW_REASON.DID_ALLOWLIST_NO_HEADER) + } + const verifyJWSResult = await VERIFIER.verifyJWS(jws) + const did = verifyJWSResult.didResolutionResult.didDocument?.id + if (!did) { + logger?.verbose(`Disallowed: No DID`) + return disallow(res, DISALLOW_REASON.DID_ALLOWLIST_NO_DID) + } + const nonce = verifyJWSResult.payload?.['nonce'] + const digest = verifyJWSResult.payload?.['digest'] + const expectedDigest = buildBodyDigest(req.header('Content-Type'), req.body) + if (!nonce || !digest) { + logger?.verbose(`Disallowed: No nonce or No digest`) + return disallow(res, DISALLOW_REASON.DID_ALLOWLIST_NO_FIELDS) + } + if (digest !== expectedDigest) { + logger?.verbose(`Disallowed: Invalid digest`) + return disallow(res, DISALLOW_REASON.INVALID_DIGEST) + } + if (!isAllowedDID(did, opts)) { + logger?.verbose(`Disallowed: ${did}`) + return disallow(res, DISALLOW_REASON.DID_ALLOWLIST_REJECTED) + } + const relaxedLabel = opts.isRelaxed ? 1 : 0 + ServiceMetrics.count(METRIC_NAMES.AUTH_ALLOWED, 1, { did: did, relaxed: relaxedLabel }) } - const relaxedLabel = opts.isRelaxed ? 1 : 0 - ServiceMetrics.count(METRIC_NAMES.AUTH_ALLOWED, 1, { did: did, relaxed: relaxedLabel }) + return next() + } catch (e: any) { + logger?.err(e.message) + return res.status(500).send('Internal Server Error') } - return next() } }