Skip to content

Commit

Permalink
Expose verifyScopes
Browse files Browse the repository at this point in the history
  • Loading branch information
satazor authored and andreffvalente committed Oct 15, 2024
1 parent 4daa4a9 commit fabd06d
Show file tree
Hide file tree
Showing 5 changed files with 26 additions and 27 deletions.
14 changes: 13 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ await fastify.register(import('@fastify/fastify-openapi-router-plugin'), {
```

> [!TIP]
> The `scopes` returned by the security handler can contain **wildcards**. For example, if the security handler returns `{ scopes: ['pets:*'] }`, the route will be authorized for any security scope that starts with `pets:`.
> The `scopes` returned by the security handler can contain trailing **wildcards**. For example, if the security handler returns `{ scopes: ['pets:*'] }`, the route will be authorized for any security scope that starts with `pets:`.
> [!IMPORTANT]
> If your specification uses `http` security schemes with `in: cookie`, you must register [@fastify/cookie](https://github.com/fastify/fastify-cookie) before this plugin.
Expand Down Expand Up @@ -246,6 +246,18 @@ fastify.oas.route({
});
```

### Other exports

#### `errors`

This object contains all error classes that can be thrown by the plugin. It contains the same errors as `fastify.oas.errors`.

#### `verifyScopes(providedScopes, requiredScopes)`

Checks if the `providedScopes` satisfy the `requiredScopes`. Returns an array of missing scopes or an empty array if all scopes are satisfied.

This functions supports trailing **wildcards** on `providedScopes`. For example, if the provided scopes is `['pets:*']` and the required scopes is `['pets:read']`, the function will return an empty array.

### Caveats

#### Coercing of `parameters`
Expand Down
1 change: 1 addition & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import fp from 'fastify-plugin';
import plugin from './plugin.js';

export * from './errors/index.js';
export { verifyScopes } from './utils/security.js';

export default fp(plugin, {
fastify: '4.x',
Expand Down
12 changes: 8 additions & 4 deletions src/parser/security.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { DECORATOR_NAME } from '../utils/constants.js';
import { createUnauthorizedError } from '../errors/index.js';
import { createScopesMismatchError, createUnauthorizedError } from '../errors/index.js';
import { extractSecuritySchemeValueFromRequest, verifyScopes } from '../utils/security.js';
import _ from 'lodash-es';
import pProps from 'p-props';
Expand Down Expand Up @@ -66,10 +66,14 @@ export const applySecurity = (operation, spec, securityHandlers, securityErrorMa
const blockResults = await pProps(block, async (requiredScopes, name) => {
try {
const resolved = await callSecurityHandler(name);
const { data, scopes } = resolved ?? {};
const { data, scopes: providedScopes = [] } = resolved ?? {};

// Verify scopes, which throws if scopes are missing.
verifyScopes(scopes ?? [], requiredScopes);
// Verify scopes to check if any is missing.
const missingScopes = verifyScopes(providedScopes, requiredScopes);

if (missingScopes.length > 0) {
throw createScopesMismatchError(providedScopes, requiredScopes, missingScopes);
}

return { data, ok: true };
} catch (error) {
Expand Down
6 changes: 1 addition & 5 deletions src/utils/security.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import { createScopesMismatchError } from '../errors/index.js';

const getValueForHttpSchemeType = (request, securityScheme) => {
if (securityScheme.scheme === 'bearer') {
const [, bearer] = request.headers.authorization?.match(/^Bearer (.+)$/i) ?? [];
Expand Down Expand Up @@ -74,7 +72,5 @@ export const verifyScopes = (providedScopes, requiredScopes) => {
return !hasMatchingScope;
});

if (missingScopes.length > 0) {
throw createScopesMismatchError(providedScopes, requiredScopes, missingScopes);
}
return missingScopes;
};
20 changes: 3 additions & 17 deletions src/utils/security.test.js
Original file line number Diff line number Diff line change
@@ -1,26 +1,12 @@
import { describe, expect, it } from 'vitest';
import { errors } from '../errors/index';
import { extractSecuritySchemeValueFromRequest, verifyScopes } from './security';
import _ from 'lodash-es';

describe('verifyScopes()', () => {
const runTest = ({ missing, provided, required }) => {
try {
verifyScopes(provided, required);

if (missing.length > 0) {
throw new Error('Expected an error to be thrown');
}
} catch (err) {
expect(err).toBeInstanceOf(errors.ScopesMismatchError);
expect(err).toMatchObject({
scopes: {
missing: missing,
provided: provided,
required: required
}
});
}
const result = verifyScopes(provided, required);

expect(result).toStrictEqual(missing);
};

it('should verify regular scopes correctly against required scopes', async () => {
Expand Down

0 comments on commit fabd06d

Please sign in to comment.