@uphold/fastify-openapi-router-plugin 0.4.0 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -137,7 +137,7 @@ await fastify.register(import('@fastify/fastify-openapi-router-plugin'), {
137
137
  ```
138
138
 
139
139
  > [!TIP]
140
- > 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:`.
140
+ > 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:`.
141
141
 
142
142
  > [!IMPORTANT]
143
143
  > If your specification uses `http` security schemes with `in: cookie`, you must register [@fastify/cookie](https://github.com/fastify/fastify-cookie) before this plugin.
@@ -246,6 +246,18 @@ fastify.oas.route({
246
246
  });
247
247
  ```
248
248
 
249
+ ### Other exports
250
+
251
+ #### `errors`
252
+
253
+ This object contains all error classes that can be thrown by the plugin. It contains the same errors as `fastify.oas.errors`.
254
+
255
+ #### `verifyScopes(providedScopes, requiredScopes)`
256
+
257
+ Checks if the `providedScopes` satisfy the `requiredScopes`. Returns an array of missing scopes or an empty array if all scopes are satisfied.
258
+
259
+ 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.
260
+
249
261
  ### Caveats
250
262
 
251
263
  #### Coercing of `parameters`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uphold/fastify-openapi-router-plugin",
3
- "version": "0.4.0",
3
+ "version": "0.5.1",
4
4
  "description": "A plugin for Fastify to connect routes with a OpenAPI 3.x specification",
5
5
  "main": "./src/index.js",
6
6
  "types": "types/index.d.ts",
package/src/index.js CHANGED
@@ -3,6 +3,7 @@ import fp from 'fastify-plugin';
3
3
  import plugin from './plugin.js';
4
4
 
5
5
  export * from './errors/index.js';
6
+ export { verifyScopes } from './utils/security.js';
6
7
 
7
8
  export default fp(plugin, {
8
9
  fastify: '4.x',
@@ -12,7 +12,7 @@ export const parseBody = (route, operation) => {
12
12
  addPropertyToSchema(route.schema.headers, { 'content-type': { const: contentType } }, true);
13
13
 
14
14
  // Sanitize schema.
15
- removeAttributesFromSchema(schema, ['xml', 'example']);
15
+ removeAttributesFromSchema(schema, ['xml', 'example', 'discriminator']);
16
16
 
17
17
  // Add request body schema.
18
18
  route.schema.body = schema;
@@ -71,6 +71,7 @@ describe('parseBody()', () => {
71
71
 
72
72
  it('should sanitize body schema', () => {
73
73
  const schema = {
74
+ bar: { discriminator: { propertyName: 'type' }, type: 'object' },
74
75
  foo: { example: 'baz', type: 'string', xml: { name: 'foo' } },
75
76
  required: ['foo'],
76
77
  xml: { name: 'Bar' }
@@ -84,6 +85,7 @@ describe('parseBody()', () => {
84
85
  parseBody(route, { requestBody });
85
86
 
86
87
  expect(route.schema.body).toStrictEqual({
88
+ bar: { type: 'object' },
87
89
  foo: { type: 'string' },
88
90
  required: ['foo']
89
91
  });
@@ -1,5 +1,5 @@
1
1
  import { DECORATOR_NAME } from '../utils/constants.js';
2
- import { createUnauthorizedError } from '../errors/index.js';
2
+ import { createScopesMismatchError, createUnauthorizedError } from '../errors/index.js';
3
3
  import { extractSecuritySchemeValueFromRequest, verifyScopes } from '../utils/security.js';
4
4
  import _ from 'lodash-es';
5
5
  import pProps from 'p-props';
@@ -66,10 +66,14 @@ export const applySecurity = (operation, spec, securityHandlers, securityErrorMa
66
66
  const blockResults = await pProps(block, async (requiredScopes, name) => {
67
67
  try {
68
68
  const resolved = await callSecurityHandler(name);
69
- const { data, scopes } = resolved ?? {};
69
+ const { data, scopes: providedScopes = [] } = resolved ?? {};
70
70
 
71
- // Verify scopes, which throws if scopes are missing.
72
- verifyScopes(scopes ?? [], requiredScopes);
71
+ // Verify scopes to check if any is missing.
72
+ const missingScopes = verifyScopes(providedScopes, requiredScopes);
73
+
74
+ if (missingScopes.length > 0) {
75
+ throw createScopesMismatchError(providedScopes, requiredScopes, missingScopes);
76
+ }
73
77
 
74
78
  return { data, ok: true };
75
79
  } catch (error) {
@@ -1,5 +1,3 @@
1
- import { createScopesMismatchError } from '../errors/index.js';
2
-
3
1
  const getValueForHttpSchemeType = (request, securityScheme) => {
4
2
  if (securityScheme.scheme === 'bearer') {
5
3
  const [, bearer] = request.headers.authorization?.match(/^Bearer (.+)$/i) ?? [];
@@ -74,7 +72,5 @@ export const verifyScopes = (providedScopes, requiredScopes) => {
74
72
  return !hasMatchingScope;
75
73
  });
76
74
 
77
- if (missingScopes.length > 0) {
78
- throw createScopesMismatchError(providedScopes, requiredScopes, missingScopes);
79
- }
75
+ return missingScopes;
80
76
  };
@@ -1,26 +1,12 @@
1
1
  import { describe, expect, it } from 'vitest';
2
- import { errors } from '../errors/index';
3
2
  import { extractSecuritySchemeValueFromRequest, verifyScopes } from './security';
4
3
  import _ from 'lodash-es';
5
4
 
6
5
  describe('verifyScopes()', () => {
7
6
  const runTest = ({ missing, provided, required }) => {
8
- try {
9
- verifyScopes(provided, required);
10
-
11
- if (missing.length > 0) {
12
- throw new Error('Expected an error to be thrown');
13
- }
14
- } catch (err) {
15
- expect(err).toBeInstanceOf(errors.ScopesMismatchError);
16
- expect(err).toMatchObject({
17
- scopes: {
18
- missing: missing,
19
- provided: provided,
20
- required: required
21
- }
22
- });
23
- }
7
+ const result = verifyScopes(provided, required);
8
+
9
+ expect(result).toStrictEqual(missing);
24
10
  };
25
11
 
26
12
  it('should verify regular scopes correctly against required scopes', async () => {