Skip to content

Commit

Permalink
Use @bedrock/[email protected].
Browse files Browse the repository at this point in the history
- Use `checkTargetScopedAccessToken()` for oauth2 access token
  verification.
  • Loading branch information
dlongley committed Jan 23, 2025
1 parent c6e8511 commit 71b034f
Show file tree
Hide file tree
Showing 3 changed files with 13 additions and 114 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
119 changes: 6 additions & 113 deletions lib/oauth2.js
Original file line number Diff line number Diff line change
@@ -1,129 +1,22 @@
/*!
* 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 {
authorization: {
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.');
}
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down

0 comments on commit 71b034f

Please sign in to comment.