From 71b034f1957674b8bb40b2b6000a57750d129bed Mon Sep 17 00:00:00 2001 From: Dave Longley Date: Thu, 23 Jan 2025 17:34:12 -0500 Subject: [PATCH] Use `@bedrock/oauth2-verifier@2.2`. - Use `checkTargetScopedAccessToken()` for oauth2 access token verification. --- CHANGELOG.md | 6 +++ lib/oauth2.js | 119 +++----------------------------------------------- package.json | 2 +- 3 files changed, 13 insertions(+), 114 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a997c7b..c3eff6b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # bedrock-service-core ChangeLog +## 10.0.1 - 2025-mm-dd + +### Changed +- Use `@bedrock/oauth2-verifier` to get `checkTargetScopedAccessToken()` + feature for verifying oauth2 access tokens. + ## 10.0.0 - 2024-08-01 ### Changed diff --git a/lib/oauth2.js b/lib/oauth2.js index 03c8c48..c4117e7 100644 --- a/lib/oauth2.js +++ b/lib/oauth2.js @@ -1,55 +1,12 @@ /*! - * Copyright (c) 2021-2022 Digital Bazaar, Inc. All rights reserved. + * Copyright (c) 2021-2025 Digital Bazaar, Inc. All rights reserved. */ import * as bedrock from '@bedrock/core'; -import {checkAccessToken as _checkAccessToken} from '@bedrock/oauth2-verifier'; - -const {util: {BedrockError}} = bedrock; - -// these default actions match ezcap-express but are needed here for oauth2 -const DEFAULT_ACTION_FOR_METHOD = new Map([ - ['GET', 'read'], - ['HEAD', 'read'], - ['OPTIONS', 'read'], - ['POST', 'write'], - ['PUT', 'write'], - ['PATCH', 'write'], - ['DELETE', 'write'], - ['CONNECT', 'write'], - ['TRACE', 'write'], - ['PATCH', 'write'] -]); +import {checkTargetScopedAccessToken} from '@bedrock/oauth2-verifier'; export async function checkAccessToken({ req, issuerConfigUrl, getExpectedValues } = {}) { - // get expected values - const expected = await getExpectedValues({req}); - _checkExpectedValues({req, expected}); - - // set expected defaults - expected.action = - expected.action ?? DEFAULT_ACTION_FOR_METHOD.get(req.method); - if(expected.action === undefined) { - const error = new Error( - `The HTTP method ${req.method} has no expected capability action.`); - error.name = 'NotSupportedError'; - error.httpStatusCode = 400; - throw error; - } - if(expected.target === undefined) { - // default expected target is always the full request URL - expected.target = `https://${expected.host}${req.originalUrl}`; - } - - // do not allow a custom target to be outside of the scope of the - // target service object (its oauth2 rules only apply to targets within - // its scope) - const {id: configId} = req.serviceObject.config; - if(!expected.target?.startsWith(configId)) { - throw new Error(`Expected "target" must start with "${configId}".`); - } - // pass optional system-wide supported algorithms as allow list ... note // that `none` algorithm is always prohibited const { @@ -57,73 +14,9 @@ export async function checkAccessToken({ oauth2: {maxClockSkew, allowedAlgorithms} } } = bedrock.config['service-core']; - - const {payload} = await _checkAccessToken({ - req, issuerConfigUrl, audience: configId, allowedAlgorithms, maxClockSkew + const {id: configId} = req.serviceObject.config; + return checkTargetScopedAccessToken({ + req, issuerConfigUrl, getExpectedValues, + audience: configId, allowedAlgorithms, maxClockSkew }); - - // generate required action scope and relative path from action and target - const requiredActionScope = `${expected.action}:`; - const path = expected.target.slice(configId.length) || '/'; - - // ensure scope matches... - const scopes = payload.scope?.split(' ') || []; - for(const scope of scopes) { - // require exact `action` match - if(!scope.startsWith(requiredActionScope)) { - continue; - } - // allow hierarchical, HTTP path- or query- based attenuation - const pathScope = scope.slice(requiredActionScope.length); - if(pathScope === '/') { - // full path access granted - return true; - } - // `pathScope` must terminate just before a path or query delimiter - if(path.startsWith(pathScope)) { - const rest = path.slice(pathScope.length); - if(rest.length === 0 || rest.startsWith('/') || rest.startsWith('?') || - rest.startsWith('&') || rest.startsWith('#')) { - return true; - } - } - } - - throw new BedrockError( - 'Access token validation failed.', { - name: 'NotAllowedError', - details: { - httpStatusCode: 403, - public: true, - code: 'ERR_JWT_CLAIM_VALIDATION_FAILED', - reason: `Access token "scope" is insufficient.`, - claim: 'scope' - } - }); -} - -function _checkExpectedValues({expected}) { - if(!(expected && typeof expected === 'object')) { - throw new TypeError('"getExpectedValues" must return an object.'); - } - - const {action, host, target} = expected; - - // expected `action` is optional - if(!(action === undefined || typeof action === 'string')) { - throw new TypeError('Expected "action" must be a string.'); - } - - // expected `host` is required - if(typeof host !== 'string') { - throw new TypeError('Expected "host" must be a string.'); - } - - // expected `target` is optional - if(target !== undefined && - !(typeof target === 'string' && target.includes(':'))) { - throw new Error( - 'Expected "target" must be a string that expresses an absolute ' + - 'URI.'); - } } diff --git a/package.json b/package.json index 977ed95..0c82fb1 100644 --- a/package.json +++ b/package.json @@ -48,7 +48,7 @@ "@bedrock/jsonld-document-loader": "^5.1.0", "@bedrock/meter-usage-reporter": "^9.0.1", "@bedrock/mongodb": "^10.2.0", - "@bedrock/oauth2-verifier": "^2.1.0", + "@bedrock/oauth2-verifier": "^2.2.0", "@bedrock/security-context": "^9.0.0", "@bedrock/validation": "^7.1.0", "@bedrock/veres-one-context": "^16.0.0",