@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 +13 -1
- package/package.json +1 -1
- package/src/index.js +1 -0
- package/src/parser/body.js +1 -1
- package/src/parser/body.test.js +2 -0
- package/src/parser/security.js +8 -4
- package/src/utils/security.js +1 -5
- package/src/utils/security.test.js +3 -17
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
package/src/index.js
CHANGED
package/src/parser/body.js
CHANGED
|
@@ -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;
|
package/src/parser/body.test.js
CHANGED
|
@@ -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
|
});
|
package/src/parser/security.js
CHANGED
|
@@ -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
|
|
72
|
-
verifyScopes(
|
|
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) {
|
package/src/utils/security.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
9
|
-
|
|
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 () => {
|