@scalar/workspace-store 0.55.3 → 0.55.5
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/CHANGELOG.md +8 -0
- package/README.md +14 -0
- package/dist/channel-example/dedupe-requirements.d.ts +9 -0
- package/dist/channel-example/dedupe-requirements.d.ts.map +1 -0
- package/dist/channel-example/dedupe-requirements.js +17 -0
- package/dist/channel-example/get-asyncapi-security-requirements.d.ts +8 -0
- package/dist/channel-example/get-asyncapi-security-requirements.d.ts.map +1 -1
- package/dist/channel-example/get-asyncapi-security-requirements.js +29 -9
- package/dist/channel-example/get-channel-connection-context.d.ts.map +1 -1
- package/dist/channel-example/get-channel-connection-context.js +1 -1
- package/dist/channel-example/get-channel-connection-security.d.ts.map +1 -1
- package/dist/channel-example/get-channel-connection-security.js +1 -11
- package/dist/channel-example/index.d.ts +1 -1
- package/dist/channel-example/index.d.ts.map +1 -1
- package/dist/channel-example/index.js +1 -1
- package/dist/mutators/auth.d.ts +3 -3
- package/dist/mutators/auth.d.ts.map +1 -1
- package/dist/mutators/auth.js +112 -81
- package/dist/mutators/index.d.ts +2 -2
- package/dist/request-example/context/security/merge-security.d.ts +10 -2
- package/dist/request-example/context/security/merge-security.d.ts.map +1 -1
- package/dist/request-example/context/security/merge-security.js +69 -3
- package/dist/schemas/type-guards.d.ts +7 -0
- package/dist/schemas/type-guards.d.ts.map +1 -1
- package/dist/schemas/type-guards.js +7 -0
- package/package.json +9 -9
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# @scalar/workspace-store
|
|
2
2
|
|
|
3
|
+
## 0.55.5
|
|
4
|
+
|
|
5
|
+
## 0.55.4
|
|
6
|
+
|
|
7
|
+
### Patch Changes
|
|
8
|
+
|
|
9
|
+
- [#9618](https://github.com/scalar/scalar/pull/9618): Render document-wide authentication for AsyncAPI documents. The introduction now shows the same Authentication selector used for OpenAPI, populated from `components.securitySchemes`, with requirements derived from the union of every server's `security` (AsyncAPI has no root-level `security`). When some servers require auth and others accept unauthenticated connections, a no-auth option is offered too. Schemes shared with OpenAPI (`http`, `oauth2`, `openIdConnect`, `apiKey`) get full input UI, and AsyncAPI OAuth2 `availableScopes` are mapped onto OpenAPI `scopes` so the scope list renders. Broker-specific types still appear in the selector but have no dedicated input yet. The selector is now fully interactive for AsyncAPI (selecting schemes, entering credentials, editing scopes) because the auth mutators accept AsyncAPI documents. Operation/channel-level auth is intentionally left for a follow-up.
|
|
10
|
+
|
|
3
11
|
## 0.55.3
|
|
4
12
|
|
|
5
13
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -2,6 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
A powerful data store for managing OpenAPI documents. This package provides a flexible solution for handling multiple OpenAPI documents in a workspace environment
|
|
4
4
|
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
Scalar is an open-source API platform for teams who want beautiful developer interfaces without vendor lock-in.
|
|
8
|
+
|
|
9
|
+
- **[API References](https://scalar.com/products/api-references/getting-started)** — Interactive API documentation from OpenAPI and AsyncAPI specs.
|
|
10
|
+
- **[Docs](https://scalar.com/products/docs/getting-started)** — Write in Markdown/MDX, generate API references, sync with two-way Git.
|
|
11
|
+
- **[SDKs](https://scalar.com/products/sdks/getting-started)** — Type-safe client libraries in TypeScript, Python, Go, PHP, Java, and Ruby.
|
|
12
|
+
- **[MCP Servers](https://scalar.com/products/agent/getting-started)** — Generate secure MCP servers from your API spec.
|
|
13
|
+
- **[API Client](https://scalar.com/products/api-client/getting-started)** — Open-source, offline-first Postman alternative built on OpenAPI.
|
|
14
|
+
|
|
15
|
+
20M+ monthly npm installs · 15,500+ GitHub stars · MIT licensed · [scalar.com](https://scalar.com)
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
5
19
|
## Server-Side workspace store
|
|
6
20
|
|
|
7
21
|
Server side data store which enables document chunking to reduce initial loading time specially when working with large openapi documents
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { SecurityRequirementObject } from '../schemas/v3.1/strict/security-requirement.js';
|
|
2
|
+
/**
|
|
3
|
+
* Remove duplicate security requirements, comparing by their JSON shape.
|
|
4
|
+
*
|
|
5
|
+
* Shared by the channel-connection and document-wide requirement builders, which both union
|
|
6
|
+
* requirements from several sources (servers, operations) and need to collapse identical entries.
|
|
7
|
+
*/
|
|
8
|
+
export declare const dedupeRequirements: (requirements: SecurityRequirementObject[]) => SecurityRequirementObject[];
|
|
9
|
+
//# sourceMappingURL=dedupe-requirements.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dedupe-requirements.d.ts","sourceRoot":"","sources":["../../src/channel-example/dedupe-requirements.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,4CAA4C,CAAA;AAE3F;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,GAAI,cAAc,yBAAyB,EAAE,KAAG,yBAAyB,EAWvG,CAAA"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Remove duplicate security requirements, comparing by their JSON shape.
|
|
3
|
+
*
|
|
4
|
+
* Shared by the channel-connection and document-wide requirement builders, which both union
|
|
5
|
+
* requirements from several sources (servers, operations) and need to collapse identical entries.
|
|
6
|
+
*/
|
|
7
|
+
export const dedupeRequirements = (requirements) => {
|
|
8
|
+
const seen = new Set();
|
|
9
|
+
return requirements.filter((requirement) => {
|
|
10
|
+
const key = JSON.stringify(requirement);
|
|
11
|
+
if (seen.has(key)) {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
seen.add(key);
|
|
15
|
+
return true;
|
|
16
|
+
});
|
|
17
|
+
};
|
|
@@ -4,4 +4,12 @@ import type { SecurityRequirementObject } from '../schemas/v3.1/strict/security-
|
|
|
4
4
|
* Converts AsyncAPI security arrays (operation, traits, server) into OpenAPI-style requirement objects.
|
|
5
5
|
*/
|
|
6
6
|
export declare const getAsyncApiSecurityRequirements: (document: AsyncApiDocument, operation?: AsyncApiOperationObject | null, server?: AsyncApiServerObject | null) => SecurityRequirementObject[];
|
|
7
|
+
/**
|
|
8
|
+
* Document-wide security requirements for an AsyncAPI document.
|
|
9
|
+
*
|
|
10
|
+
* AsyncAPI has no root-level `security`; the closest document-wide scope is the union of every
|
|
11
|
+
* server's security (a server applies to the whole connection). Operation-level security is
|
|
12
|
+
* intentionally excluded here — that is per-channel and handled separately.
|
|
13
|
+
*/
|
|
14
|
+
export declare const getAsyncApiDocumentSecurityRequirements: (document: AsyncApiDocument) => SecurityRequirementObject[];
|
|
7
15
|
//# sourceMappingURL=get-asyncapi-security-requirements.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"get-asyncapi-security-requirements.d.ts","sourceRoot":"","sources":["../../src/channel-example/get-asyncapi-security-requirements.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,gBAAgB,EAChB,uBAAuB,EAEvB,oBAAoB,EACrB,MAAM,4BAA4B,CAAA;AAInC,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,4CAA4C,CAAA;
|
|
1
|
+
{"version":3,"file":"get-asyncapi-security-requirements.d.ts","sourceRoot":"","sources":["../../src/channel-example/get-asyncapi-security-requirements.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,gBAAgB,EAChB,uBAAuB,EAEvB,oBAAoB,EACrB,MAAM,4BAA4B,CAAA;AAInC,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,4CAA4C,CAAA;AA+E3F;;GAEG;AACH,eAAO,MAAM,+BAA+B,GAC1C,UAAU,gBAAgB,EAC1B,YAAY,uBAAuB,GAAG,IAAI,EAC1C,SAAS,oBAAoB,GAAG,IAAI,KACnC,yBAAyB,EAY3B,CAAA;AAED;;;;;;GAMG;AACH,eAAO,MAAM,uCAAuC,GAAI,UAAU,gBAAgB,KAAG,yBAAyB,EAwB7G,CAAA"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { isObjectEqual } from '@scalar/helpers/object/is-object-equal';
|
|
2
2
|
import { getNameFromRef } from '../helpers/get-name-from-ref.js';
|
|
3
3
|
import { getResolvedRef } from '../helpers/get-resolved-ref.js';
|
|
4
|
+
import { dedupeRequirements } from './dedupe-requirements.js';
|
|
4
5
|
const getSecuritySchemeNameFromRef = (ref) => getNameFromRef(ref, ['components', 'securitySchemes']);
|
|
5
6
|
/** Strips requirement-only `scopes` so inline entries can match component scheme definitions. */
|
|
6
7
|
const getSecuritySchemeDefinition = (entry) => {
|
|
@@ -64,13 +65,32 @@ export const getAsyncApiSecurityRequirements = (document, operation, server) =>
|
|
|
64
65
|
: serverRequirements.length === 0
|
|
65
66
|
? operationRequirements
|
|
66
67
|
: [...operationRequirements, ...serverRequirements];
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
68
|
+
return dedupeRequirements(combined);
|
|
69
|
+
};
|
|
70
|
+
/**
|
|
71
|
+
* Document-wide security requirements for an AsyncAPI document.
|
|
72
|
+
*
|
|
73
|
+
* AsyncAPI has no root-level `security`; the closest document-wide scope is the union of every
|
|
74
|
+
* server's security (a server applies to the whole connection). Operation-level security is
|
|
75
|
+
* intentionally excluded here — that is per-channel and handled separately.
|
|
76
|
+
*/
|
|
77
|
+
export const getAsyncApiDocumentSecurityRequirements = (document) => {
|
|
78
|
+
const servers = document.servers ? getResolvedRef(document.servers) : undefined;
|
|
79
|
+
if (!servers) {
|
|
80
|
+
return [];
|
|
81
|
+
}
|
|
82
|
+
const resolvedServers = Object.values(servers).map((serverRef) => getResolvedRef(serverRef));
|
|
83
|
+
const perServerRequirements = resolvedServers.map((server) => getAsyncApiSecurityRequirements(document, null, server));
|
|
84
|
+
const combined = perServerRequirements.flat();
|
|
85
|
+
// When some servers require auth while others accept unauthenticated connections, surface the
|
|
86
|
+
// no-auth path as an optional `{}` requirement so those servers stay selectable. "No auth" is
|
|
87
|
+
// keyed off the declared `security` array (absent or empty), not off a server whose declared
|
|
88
|
+
// security failed to resolve to a scheme — that server still requires auth. If no server
|
|
89
|
+
// requires auth at all, we return `[]` and let the selector treat every scheme as optional.
|
|
90
|
+
const someRequireAuth = perServerRequirements.some((requirements) => requirements.length > 0);
|
|
91
|
+
const someDeclareNoAuth = resolvedServers.some((server) => !server?.security?.length);
|
|
92
|
+
if (someRequireAuth && someDeclareNoAuth) {
|
|
93
|
+
combined.push({});
|
|
94
|
+
}
|
|
95
|
+
return dedupeRequirements(combined);
|
|
76
96
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"get-channel-connection-context.d.ts","sourceRoot":"","sources":["../../src/channel-example/get-channel-connection-context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,6BAA6B,CAAA;AAS9E,OAAO,KAAK,EAAE,6BAA6B,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAA;AACnG,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,UAAU,CAAA;AAQ9C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAA;
|
|
1
|
+
{"version":3,"file":"get-channel-connection-context.d.ts","sourceRoot":"","sources":["../../src/channel-example/get-channel-connection-context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,6BAA6B,CAAA;AAS9E,OAAO,KAAK,EAAE,6BAA6B,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAA;AACnG,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,UAAU,CAAA;AAQ9C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAA;AAErD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAA;AAK5D;;;GAGG;AACH,eAAO,MAAM,2BAA2B,GACtC,gBAAgB,cAAc,EAC9B,cAAc,MAAM,EACpB,uBAAuB,qBAAqB,EAC5C,UAAS,OAAO,CAAC;IACf,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACtC,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACvC,cAAc,EAAE,2BAA2B,CAAA;IAC3C,gBAAgB,EAAE,iBAAiB,GAAG,IAAI,CAAA;CAC3C,CAAM,KACN,MAAM,CAAC,6BAA6B,CAwItC,CAAA"}
|
|
@@ -85,7 +85,7 @@ export const getChannelConnectionContext = (workspaceStore, documentName, channe
|
|
|
85
85
|
method: ASYNCAPI_CHANNEL_AUTH_METHOD,
|
|
86
86
|
});
|
|
87
87
|
const components = document.components ? getResolvedRef(document.components) : undefined;
|
|
88
|
-
const securitySchemes = mergeSecurity(
|
|
88
|
+
const securitySchemes = mergeSecurity(components?.securitySchemes ?? {}, options.authentication?.securitySchemes ?? {}, workspaceStore.auth, documentName);
|
|
89
89
|
const securityRequirements = getChannelConnectionSecurityRequirements(document, channel, selectedServer?.server ?? null, channelOperations);
|
|
90
90
|
const selectedSecurity = getSelectedSecurity(documentSelectedSecurity, channelSelectedSecurity, securityRequirements, securitySchemes, options.authentication?.preferredSecurityScheme);
|
|
91
91
|
const selectedSecuritySchemes = getSecuritySchemes(securitySchemes, selectedSecurity.selectedSchemes[selectedSecurity.selectedIndex] ?? {});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"get-channel-connection-security.d.ts","sourceRoot":"","sources":["../../src/channel-example/get-channel-connection-security.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,4BAA4B,CAAA;AAG/G,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,4CAA4C,CAAA;
|
|
1
|
+
{"version":3,"file":"get-channel-connection-security.d.ts","sourceRoot":"","sources":["../../src/channel-example/get-channel-connection-security.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,4BAA4B,CAAA;AAG/G,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,4CAA4C,CAAA;AAG3F,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,0BAA0B,CAAA;AAEvE;;GAEG;AACH,eAAO,MAAM,wCAAwC,GACnD,UAAU,gBAAgB,EAC1B,UAAU,qBAAqB,EAC/B,QAAQ,oBAAoB,GAAG,IAAI,EACnC,mBAAmB,uBAAuB,EAAE,KAC3C,yBAAyB,EAQ3B,CAAA"}
|
|
@@ -1,15 +1,5 @@
|
|
|
1
1
|
import { getAsyncApiSecurityRequirements } from '../channel-example/get-asyncapi-security-requirements.js';
|
|
2
|
-
|
|
3
|
-
const seen = new Set();
|
|
4
|
-
return requirements.filter((requirement) => {
|
|
5
|
-
const key = JSON.stringify(requirement);
|
|
6
|
-
if (seen.has(key)) {
|
|
7
|
-
return false;
|
|
8
|
-
}
|
|
9
|
-
seen.add(key);
|
|
10
|
-
return true;
|
|
11
|
-
});
|
|
12
|
-
};
|
|
2
|
+
import { dedupeRequirements } from './dedupe-requirements.js';
|
|
13
3
|
/**
|
|
14
4
|
* Merges security requirements from the selected server and all operations on a channel.
|
|
15
5
|
*/
|
|
@@ -4,7 +4,7 @@ export type { ChannelMessageEntry } from './get-all-channel-messages.js';
|
|
|
4
4
|
export { getAllChannelMessages } from './get-all-channel-messages.js';
|
|
5
5
|
export type { AsyncApiProtocolOption, AsyncApiReachabilityContext, AsyncApiServerOption, OperationReachability, } from './get-async-api-protocols.js';
|
|
6
6
|
export { ALL, createReachabilityContext, getAsyncApiProtocols, getAsyncApiServerOptions, getOperationProtocols, getOperationReachability, getOperationServerNames, operationMatchesProtocol, operationMatchesServer, } from './get-async-api-protocols.js';
|
|
7
|
-
export { getAsyncApiSecurityRequirements } from './get-asyncapi-security-requirements.js';
|
|
7
|
+
export { getAsyncApiDocumentSecurityRequirements, getAsyncApiSecurityRequirements, } from './get-asyncapi-security-requirements.js';
|
|
8
8
|
export { getChannelConnectionContext } from './get-channel-connection-context.js';
|
|
9
9
|
export { getChannelConnectionSecurityRequirements } from './get-channel-connection-security.js';
|
|
10
10
|
export type { ChannelOperationSummary } from './get-channel-operations.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/channel-example/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,yBAAyB,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAA;AAChG,OAAO,EACL,4BAA4B,EAC5B,0BAA0B,EAC1B,kBAAkB,EAClB,kBAAkB,EAClB,0BAA0B,EAC1B,wBAAwB,EACxB,mBAAmB,EACnB,eAAe,GAChB,MAAM,wBAAwB,CAAA;AAC/B,YAAY,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAA;AACrE,OAAO,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAA;AAClE,YAAY,EACV,sBAAsB,EACtB,2BAA2B,EAC3B,oBAAoB,EACpB,qBAAqB,GACtB,MAAM,2BAA2B,CAAA;AAClC,OAAO,EACL,GAAG,EACH,yBAAyB,EACzB,oBAAoB,EACpB,wBAAwB,EACxB,qBAAqB,EACrB,wBAAwB,EACxB,uBAAuB,EACvB,wBAAwB,EACxB,sBAAsB,GACvB,MAAM,2BAA2B,CAAA;AAClC,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/channel-example/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,yBAAyB,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAA;AAChG,OAAO,EACL,4BAA4B,EAC5B,0BAA0B,EAC1B,kBAAkB,EAClB,kBAAkB,EAClB,0BAA0B,EAC1B,wBAAwB,EACxB,mBAAmB,EACnB,eAAe,GAChB,MAAM,wBAAwB,CAAA;AAC/B,YAAY,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAA;AACrE,OAAO,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAA;AAClE,YAAY,EACV,sBAAsB,EACtB,2BAA2B,EAC3B,oBAAoB,EACpB,qBAAqB,GACtB,MAAM,2BAA2B,CAAA;AAClC,OAAO,EACL,GAAG,EACH,yBAAyB,EACzB,oBAAoB,EACpB,wBAAwB,EACxB,qBAAqB,EACrB,wBAAwB,EACxB,uBAAuB,EACvB,wBAAwB,EACxB,sBAAsB,GACvB,MAAM,2BAA2B,CAAA;AAClC,OAAO,EACL,uCAAuC,EACvC,+BAA+B,GAChC,MAAM,sCAAsC,CAAA;AAC7C,OAAO,EAAE,2BAA2B,EAAE,MAAM,kCAAkC,CAAA;AAC9E,OAAO,EAAE,wCAAwC,EAAE,MAAM,mCAAmC,CAAA;AAC5F,YAAY,EAAE,uBAAuB,EAAE,MAAM,0BAA0B,CAAA;AACvE,OAAO,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAA;AAC/D,YAAY,EAAE,wBAAwB,EAAE,MAAM,0BAA0B,CAAA;AACxE,OAAO,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAA;AAC/D,YAAY,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AACxD,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAA;AAClD,YAAY,EAAE,wBAAwB,EAAE,MAAM,6BAA6B,CAAA;AAC3E,OAAO,EAAE,uBAAuB,EAAE,MAAM,6BAA6B,CAAA;AACrE,OAAO,EAAE,0BAA0B,EAAE,MAAM,iCAAiC,CAAA;AAC5E,YAAY,EAAE,mBAAmB,EAAE,yBAAyB,EAAE,MAAM,WAAW,CAAA;AAC/E,OAAO,EAAE,kBAAkB,EAAE,yBAAyB,EAAE,MAAM,WAAW,CAAA;AACzE,YAAY,EAAE,6BAA6B,EAAE,qBAAqB,EAAE,MAAM,SAAS,CAAA"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export { ASYNCAPI_WEBSOCKET_PROTOCOLS, buildAsyncApiServerBaseUrl, buildConnectionUrl, buildWsQueryParams, getAsyncApiServerVariables, getUrlSchemeFromProtocol, isWebSocketProtocol, mergeWsBindings, } from './build-connection-url.js';
|
|
2
2
|
export { getAllChannelMessages } from './get-all-channel-messages.js';
|
|
3
3
|
export { ALL, createReachabilityContext, getAsyncApiProtocols, getAsyncApiServerOptions, getOperationProtocols, getOperationReachability, getOperationServerNames, operationMatchesProtocol, operationMatchesServer, } from './get-async-api-protocols.js';
|
|
4
|
-
export { getAsyncApiSecurityRequirements } from './get-asyncapi-security-requirements.js';
|
|
4
|
+
export { getAsyncApiDocumentSecurityRequirements, getAsyncApiSecurityRequirements, } from './get-asyncapi-security-requirements.js';
|
|
5
5
|
export { getChannelConnectionContext } from './get-channel-connection-context.js';
|
|
6
6
|
export { getChannelConnectionSecurityRequirements } from './get-channel-connection-security.js';
|
|
7
7
|
export { getChannelOperations } from './get-channel-operations.js';
|
package/dist/mutators/auth.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { WorkspaceStore } from '../client.js';
|
|
2
2
|
import type { AuthEvents } from '../events/definitions/auth.js';
|
|
3
3
|
import type { WorkspaceDocument } from '../schemas.js';
|
|
4
|
-
import type {
|
|
4
|
+
import type { SecuritySchemeObject } from '../schemas/v3.1/strict/security-scheme.js';
|
|
5
5
|
/**
|
|
6
6
|
* Updates the selected security schemes for either the entire document or a specific operation.
|
|
7
7
|
* - Adds newly created security schemes (if any) to the workspace document's components.
|
|
@@ -50,7 +50,7 @@ export declare const updateSelectedSecuritySchemes: (store: WorkspaceStore | nul
|
|
|
50
50
|
* name: 'MyHttpAuth',
|
|
51
51
|
* })
|
|
52
52
|
*/
|
|
53
|
-
export declare const updateSecurityScheme: (document: WorkspaceDocument | null, { payload, name }: AuthEvents["auth:update:security-scheme"]) =>
|
|
53
|
+
export declare const updateSecurityScheme: (document: WorkspaceDocument | null, { payload, name }: AuthEvents["auth:update:security-scheme"]) => SecuritySchemeObject | undefined;
|
|
54
54
|
/**
|
|
55
55
|
* Sets the selected authentication tab (scheme) index for the given OpenAPI document or operation.
|
|
56
56
|
* - When on the document level, updates the 'selectedIndex' on the document's x-scalar-selected-security extension.
|
|
@@ -155,7 +155,7 @@ export declare const authMutatorsFactory: ({ document, store, }: {
|
|
|
155
155
|
}) => {
|
|
156
156
|
updateSelectedSecuritySchemes: (payload: AuthEvents["auth:update:selected-security-schemes"]) => Promise<void>;
|
|
157
157
|
clearSelectedSecuritySchemes: (payload: AuthEvents["auth:clear:selected-security-schemes"]) => void;
|
|
158
|
-
updateSecurityScheme: (payload: AuthEvents["auth:update:security-scheme"]) =>
|
|
158
|
+
updateSecurityScheme: (payload: AuthEvents["auth:update:security-scheme"]) => SecuritySchemeObject | undefined;
|
|
159
159
|
updateSecuritySchemeSecrets: (payload: AuthEvents["auth:update:security-scheme-secrets"]) => void;
|
|
160
160
|
clearSecuritySchemeSecrets: (payload: AuthEvents["auth:clear:security-scheme-secrets"]) => void;
|
|
161
161
|
updateSelectedAuthTab: (payload: AuthEvents["auth:update:active-index"]) => void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/mutators/auth.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,UAAU,CAAA;AAC9C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAA;AAQ3D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAA;AAGlD,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/mutators/auth.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,UAAU,CAAA;AAC9C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAA;AAQ3D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAA;AAGlD,OAAO,KAAK,EAAgB,oBAAoB,EAAE,MAAM,uCAAuC,CAAA;AAuC/F;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,eAAO,MAAM,6BAA6B,GACxC,OAAO,cAAc,GAAG,IAAI,EAC5B,UAAU,iBAAiB,GAAG,IAAI,EAClC,4CAA4C,UAAU,CAAC,uCAAuC,CAAC,kBAgFhG,CAAA;AAwBD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,eAAO,MAAM,oBAAoB,GAC/B,UAAU,iBAAiB,GAAG,IAAI,EAClC,mBAAmB,UAAU,CAAC,6BAA6B,CAAC,qCAwB7D,CAAA;AAuCD;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,eAAO,MAAM,qBAAqB,GAChC,OAAO,cAAc,GAAG,IAAI,EAC5B,UAAU,iBAAiB,GAAG,IAAI,EAClC,iBAAiB,UAAU,CAAC,0BAA0B,CAAC,SAgDxD,CAAA;AAaD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,eAAO,MAAM,oBAAoB,GAC/B,OAAO,cAAc,GAAG,IAAI,EAC5B,UAAU,iBAAiB,GAAG,IAAI,EAClC,4BAA4B,UAAU,CAAC,6BAA6B,CAAC,SAgDtE,CAAA;AAqED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,WAAW,GACtB,OAAO,cAAc,GAAG,IAAI,EAC5B,UAAU,iBAAiB,GAAG,IAAI,EAClC,0DAA0D,UAAU,CAAC,oBAAoB,CAAC,SA2D3F,CAAA;AAED;;;GAGG;AACH,eAAO,MAAM,WAAW,GACtB,OAAO,cAAc,GAAG,IAAI,EAC5B,UAAU,iBAAiB,GAAG,IAAI,EAClC,2BAA2B,UAAU,CAAC,oBAAoB,CAAC,SAwB5D,CAAA;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,oBAAoB,GAC/B,OAAO,cAAc,GAAG,IAAI,EAC5B,UAAU,iBAAiB,GAAG,IAAI,EAClC,WAAW,UAAU,CAAC,6BAA6B,CAAC,SAmFrD,CAAA;AAED,eAAO,MAAM,mBAAmB,GAAI,sBAGjC;IACD,QAAQ,EAAE,iBAAiB,GAAG,IAAI,CAAA;IAClC,KAAK,EAAE,cAAc,GAAG,IAAI,CAAA;CAC7B;6CAE4C,UAAU,CAAC,uCAAuC,CAAC;4CAEpD,UAAU,CAAC,sCAAsC,CAAC;oCAE1D,UAAU,CAAC,6BAA6B,CAAC;2CAElC,UAAU,CAAC,qCAAqC,CAAC;0CAElD,UAAU,CAAC,oCAAoC,CAAC;qCAErD,UAAU,CAAC,0BAA0B,CAAC;oCAEvC,UAAU,CAAC,6BAA6B,CAAC;2BAElD,UAAU,CAAC,oBAAoB,CAAC;2BAChC,UAAU,CAAC,oBAAoB,CAAC;oCACvB,UAAU,CAAC,6BAA6B,CAAC;CAG5E,CAAA"}
|
package/dist/mutators/auth.js
CHANGED
|
@@ -1,11 +1,44 @@
|
|
|
1
|
-
import { generateUniqueValue } from '../helpers/generate-unique-value.js';
|
|
2
1
|
import { forEachPathItemOperation, getPathItemOperation } from '../helpers/for-each-path-item-operation.js';
|
|
2
|
+
import { generateUniqueValue } from '../helpers/generate-unique-value.js';
|
|
3
3
|
import { getResolvedRef } from '../helpers/get-resolved-ref.js';
|
|
4
4
|
import { isNonOptionalSecurityRequirement } from '../helpers/is-non-optional-security-requirement.js';
|
|
5
5
|
import { mergeObjects } from '../helpers/merge-object.js';
|
|
6
6
|
import { unpackProxyObject } from '../helpers/unpack-proxy.js';
|
|
7
7
|
import { getSelectedSecurity } from '../request-example/context/security/get-selected-security.js';
|
|
8
|
-
import { isOpenApiDocument } from '../schemas/type-guards.js';
|
|
8
|
+
import { isAsyncApiDocument, isOpenApiDocument } from '../schemas/type-guards.js';
|
|
9
|
+
/**
|
|
10
|
+
* The auth-store key for a document's selection and secret state.
|
|
11
|
+
*
|
|
12
|
+
* Both OpenAPI and AsyncAPI documents persist it on `x-scalar-navigation.name`, so auth selection
|
|
13
|
+
* and credential entry work for either spec. Returns `undefined` for anything else (or a document
|
|
14
|
+
* without a navigation name), in which case the caller does nothing.
|
|
15
|
+
*/
|
|
16
|
+
const getAuthDocumentName = (document) => {
|
|
17
|
+
if (isOpenApiDocument(document) || isAsyncApiDocument(document)) {
|
|
18
|
+
return document['x-scalar-navigation']?.name || undefined;
|
|
19
|
+
}
|
|
20
|
+
return undefined;
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* The document's `components.securitySchemes`, resolving the AsyncAPI `components` `$ref` wrapper.
|
|
24
|
+
* Used when building a fallback selection, so scope selection works for both spec types.
|
|
25
|
+
*/
|
|
26
|
+
const getDocumentSecuritySchemes = (document) => {
|
|
27
|
+
if (isOpenApiDocument(document)) {
|
|
28
|
+
return (document.components?.securitySchemes ?? {});
|
|
29
|
+
}
|
|
30
|
+
if (isAsyncApiDocument(document)) {
|
|
31
|
+
const components = document.components ? getResolvedRef(document.components) : undefined;
|
|
32
|
+
return (components?.securitySchemes ?? {});
|
|
33
|
+
}
|
|
34
|
+
return {};
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* The key an OAuth2 flow stores its scope map under. OpenAPI uses `scopes`; AsyncAPI uses
|
|
38
|
+
* `availableScopes` (which `mergeSecurity` renames to `scopes` for rendering), so scope edits
|
|
39
|
+
* must target the right key to round-trip.
|
|
40
|
+
*/
|
|
41
|
+
const getFlowScopeKey = (document) => isAsyncApiDocument(document) ? 'availableScopes' : 'scopes';
|
|
9
42
|
/**
|
|
10
43
|
* Updates the selected security schemes for either the entire document or a specific operation.
|
|
11
44
|
* - Adds newly created security schemes (if any) to the workspace document's components.
|
|
@@ -31,10 +64,7 @@ import { isOpenApiDocument } from '../schemas/type-guards.js';
|
|
|
31
64
|
* ```
|
|
32
65
|
*/
|
|
33
66
|
export const updateSelectedSecuritySchemes = async (store, document, { selectedRequirements, newSchemes, meta }) => {
|
|
34
|
-
|
|
35
|
-
return;
|
|
36
|
-
}
|
|
37
|
-
const documentName = document['x-scalar-navigation']?.name;
|
|
67
|
+
const documentName = getAuthDocumentName(document);
|
|
38
68
|
if (!documentName) {
|
|
39
69
|
return;
|
|
40
70
|
}
|
|
@@ -45,31 +75,35 @@ export const updateSelectedSecuritySchemes = async (store, document, { selectedR
|
|
|
45
75
|
}
|
|
46
76
|
return store?.auth.getAuthSelectedSchemas({ type: 'operation', documentName, path: meta.path, method: meta.method });
|
|
47
77
|
};
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
document.components
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
78
|
+
// New scheme definitions can only be materialised into an OpenAPI `components` block. AsyncAPI
|
|
79
|
+
// document-level auth only ever selects existing schemes, so there is nothing to create there.
|
|
80
|
+
const createdSchemes = [];
|
|
81
|
+
if (isOpenApiDocument(document)) {
|
|
82
|
+
const createdSecurityRequirements = await Promise.all(newSchemes.map(async (newScheme) => {
|
|
83
|
+
const uniqueSchemeName = await generateUniqueValue({
|
|
84
|
+
defaultValue: newScheme.name,
|
|
85
|
+
validation: (value) => !document.components?.securitySchemes?.[value],
|
|
86
|
+
maxRetries: 100,
|
|
87
|
+
});
|
|
88
|
+
if (!uniqueSchemeName) {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
// Ensure components and securitySchemes exist
|
|
92
|
+
if (!document.components) {
|
|
93
|
+
document.components = {};
|
|
94
|
+
}
|
|
95
|
+
if (!document.components.securitySchemes) {
|
|
96
|
+
document.components.securitySchemes = {};
|
|
97
|
+
}
|
|
98
|
+
// Add the new security scheme definition
|
|
99
|
+
document.components.securitySchemes[uniqueSchemeName] = newScheme.scheme;
|
|
100
|
+
// Return an OpenAPI Security Requirement Object for this new scheme (empty scope array)
|
|
101
|
+
return {
|
|
102
|
+
[uniqueSchemeName]: [],
|
|
103
|
+
};
|
|
104
|
+
}));
|
|
105
|
+
createdSchemes.push(...createdSecurityRequirements.filter(Boolean));
|
|
106
|
+
}
|
|
73
107
|
const target = getTarget();
|
|
74
108
|
const newSelectedSecuritySchemes = [...selectedRequirements, ...createdSchemes];
|
|
75
109
|
const getSelectedIndex = () => {
|
|
@@ -94,10 +128,7 @@ export const updateSelectedSecuritySchemes = async (store, document, { selectedR
|
|
|
94
128
|
* If the document name cannot be determined, nothing happens.
|
|
95
129
|
*/
|
|
96
130
|
const clearSelectedSecuritySchemes = (store, document, { meta }) => {
|
|
97
|
-
|
|
98
|
-
return;
|
|
99
|
-
}
|
|
100
|
-
const documentName = document['x-scalar-navigation']?.name;
|
|
131
|
+
const documentName = getAuthDocumentName(document);
|
|
101
132
|
if (!documentName) {
|
|
102
133
|
return;
|
|
103
134
|
}
|
|
@@ -130,25 +161,29 @@ const clearSelectedSecuritySchemes = (store, document, { meta }) => {
|
|
|
130
161
|
* })
|
|
131
162
|
*/
|
|
132
163
|
export const updateSecurityScheme = (document, { payload, name }) => {
|
|
133
|
-
if (!isOpenApiDocument(document)) {
|
|
164
|
+
if (!isOpenApiDocument(document) && !isAsyncApiDocument(document)) {
|
|
134
165
|
return;
|
|
135
166
|
}
|
|
136
|
-
const target = getResolvedRef(document
|
|
167
|
+
const target = getResolvedRef(getDocumentSecuritySchemes(document)[name]);
|
|
137
168
|
if (!target) {
|
|
138
169
|
console.error(`Security scheme ${name} not found`);
|
|
139
170
|
return;
|
|
140
171
|
}
|
|
141
|
-
//
|
|
142
|
-
|
|
172
|
+
// AsyncAPI's `httpApiKey` is presented as `apiKey` in the UI, so treat that as an equivalent
|
|
173
|
+
// update. Handle HTTP (basic, bearer, etc.) and the shared types the same way. The stored type is
|
|
174
|
+
// read as a string because AsyncAPI broker types are outside the OpenAPI `SecuritySchemeObject` union.
|
|
175
|
+
const storedType = target.type;
|
|
176
|
+
const isEquivalentType = storedType === payload.type || (storedType === 'httpApiKey' && payload.type === 'apiKey');
|
|
177
|
+
if (isEquivalentType) {
|
|
178
|
+
// Preserve the document's original spec type — never let the UI's normalized type overwrite it.
|
|
179
|
+
const originalType = target.type;
|
|
143
180
|
mergeObjects(target, payload);
|
|
181
|
+
target.type = originalType;
|
|
144
182
|
}
|
|
145
183
|
return target;
|
|
146
184
|
};
|
|
147
185
|
const updateSecuritySchemeSecrets = (store, document, { payload, name, overwrite = false }) => {
|
|
148
|
-
|
|
149
|
-
return;
|
|
150
|
-
}
|
|
151
|
-
const documentName = document['x-scalar-navigation']?.name;
|
|
186
|
+
const documentName = getAuthDocumentName(document);
|
|
152
187
|
if (!documentName) {
|
|
153
188
|
return;
|
|
154
189
|
}
|
|
@@ -162,10 +197,7 @@ const updateSecuritySchemeSecrets = (store, document, { payload, name, overwrite
|
|
|
162
197
|
store?.auth.setAuthSecrets(documentName, name, result);
|
|
163
198
|
};
|
|
164
199
|
const clearSecuritySchemeSecrets = (store, document, { name }) => {
|
|
165
|
-
|
|
166
|
-
return;
|
|
167
|
-
}
|
|
168
|
-
const documentName = document['x-scalar-navigation']?.name;
|
|
200
|
+
const documentName = getAuthDocumentName(document);
|
|
169
201
|
if (!documentName) {
|
|
170
202
|
return;
|
|
171
203
|
}
|
|
@@ -198,16 +230,15 @@ const clearSecuritySchemeSecrets = (store, document, { name }) => {
|
|
|
198
230
|
* });
|
|
199
231
|
*/
|
|
200
232
|
export const updateSelectedAuthTab = (store, document, { index, meta }) => {
|
|
201
|
-
|
|
202
|
-
return;
|
|
203
|
-
}
|
|
204
|
-
const documentName = document['x-scalar-navigation']?.name;
|
|
233
|
+
const documentName = getAuthDocumentName(document);
|
|
205
234
|
if (!documentName) {
|
|
206
235
|
return;
|
|
207
236
|
}
|
|
208
|
-
//
|
|
209
|
-
if (meta.type === 'operation'
|
|
210
|
-
|
|
237
|
+
// Operation-level auth only applies to OpenAPI documents; ensure the path/method exists.
|
|
238
|
+
if (meta.type === 'operation') {
|
|
239
|
+
if (!isOpenApiDocument(document) || getPathItemOperation(document.paths?.[meta.path], meta.method) === undefined) {
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
211
242
|
}
|
|
212
243
|
// Determine the target object for setting the auth tab index:
|
|
213
244
|
// - Document/root level
|
|
@@ -276,10 +307,7 @@ const securityRequirementIdsMatch = (requirement, id) => {
|
|
|
276
307
|
* ```
|
|
277
308
|
*/
|
|
278
309
|
export const updateSelectedScopes = (store, document, { id, name, scopes, meta }) => {
|
|
279
|
-
|
|
280
|
-
return;
|
|
281
|
-
}
|
|
282
|
-
const documentName = document['x-scalar-navigation']?.name;
|
|
310
|
+
const documentName = getAuthDocumentName(document);
|
|
283
311
|
if (!documentName) {
|
|
284
312
|
return;
|
|
285
313
|
}
|
|
@@ -296,7 +324,7 @@ export const updateSelectedScopes = (store, document, { id, name, scopes, meta }
|
|
|
296
324
|
// supplied (derived from `id`), which makes `getSelectedSecurity` build the requirement
|
|
297
325
|
// from that scheme alone and never read the requirements array.
|
|
298
326
|
const target = getTarget() ??
|
|
299
|
-
getSelectedSecurity(undefined, undefined, [], (document
|
|
327
|
+
getSelectedSecurity(undefined, undefined, [], getDocumentSecuritySchemes(document), id.length === 1 ? id[0] : id);
|
|
300
328
|
const nextSelectedSchemes = unpackProxyObject(target.selectedSchemes, { depth: 1 }) ?? [];
|
|
301
329
|
// Match the security requirement by scheme key names (order-insensitive: Object.keys order
|
|
302
330
|
// can differ between the store copy and the UI payload for the same requirement object).
|
|
@@ -314,17 +342,15 @@ export const updateSelectedScopes = (store, document, { id, name, scopes, meta }
|
|
|
314
342
|
* Returns `null` when the scheme or flow cannot be found, or the scheme is not an OAuth2 / OpenID Connect scheme.
|
|
315
343
|
*/
|
|
316
344
|
const resolveOAuthFlow = (document, name, flowType) => {
|
|
317
|
-
|
|
318
|
-
return null;
|
|
319
|
-
}
|
|
320
|
-
const securityScheme = getResolvedRef(document.components?.securitySchemes?.[name]);
|
|
345
|
+
const securityScheme = getResolvedRef(getDocumentSecuritySchemes(document)[name]);
|
|
321
346
|
if (!securityScheme) {
|
|
322
347
|
return null;
|
|
323
348
|
}
|
|
324
349
|
if (securityScheme.type !== 'oauth2' && securityScheme.type !== 'openIdConnect') {
|
|
325
350
|
return null;
|
|
326
351
|
}
|
|
327
|
-
|
|
352
|
+
const flow = securityScheme.flows?.[flowType];
|
|
353
|
+
return flow ? getResolvedRef(flow) : null;
|
|
328
354
|
};
|
|
329
355
|
/**
|
|
330
356
|
* Walks every selection container that lives under a document (the document-level
|
|
@@ -333,11 +359,8 @@ const resolveOAuthFlow = (document, name, flowType) => {
|
|
|
333
359
|
* `setAuthSelectedSchemas` so persistence hooks run.
|
|
334
360
|
*/
|
|
335
361
|
const walkSelectedSchemes = (store, document, transform) => {
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
}
|
|
339
|
-
const documentName = document['x-scalar-navigation']?.name;
|
|
340
|
-
if (!documentName) {
|
|
362
|
+
const documentName = getAuthDocumentName(document);
|
|
363
|
+
if (!store || !documentName) {
|
|
341
364
|
return;
|
|
342
365
|
}
|
|
343
366
|
const apply = (payload) => {
|
|
@@ -353,11 +376,14 @@ const walkSelectedSchemes = (store, document, transform) => {
|
|
|
353
376
|
});
|
|
354
377
|
};
|
|
355
378
|
apply({ type: 'document', documentName });
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
379
|
+
// Operation-level selections only exist for OpenAPI documents (path / method).
|
|
380
|
+
if (isOpenApiDocument(document)) {
|
|
381
|
+
Object.entries(document.paths ?? {}).forEach(([path, pathItemRef]) => {
|
|
382
|
+
forEachPathItemOperation(pathItemRef, (method) => {
|
|
383
|
+
apply({ type: 'operation', documentName, path, method });
|
|
384
|
+
});
|
|
359
385
|
});
|
|
360
|
-
}
|
|
386
|
+
}
|
|
361
387
|
};
|
|
362
388
|
/**
|
|
363
389
|
* Adds a new scope to an OAuth flow, or renames / updates the description of an existing one.
|
|
@@ -371,23 +397,27 @@ const walkSelectedSchemes = (store, document, transform) => {
|
|
|
371
397
|
* flow without a separate selection mutation.
|
|
372
398
|
*/
|
|
373
399
|
export const upsertScope = (store, document, { name, flowType, scope, description, oldScope, enable }) => {
|
|
374
|
-
if (!isOpenApiDocument(document)) {
|
|
400
|
+
if (!isOpenApiDocument(document) && !isAsyncApiDocument(document)) {
|
|
375
401
|
return;
|
|
376
402
|
}
|
|
377
403
|
const flow = resolveOAuthFlow(document, name, flowType);
|
|
378
404
|
if (!flow) {
|
|
379
405
|
return;
|
|
380
406
|
}
|
|
381
|
-
|
|
407
|
+
const scopeKey = getFlowScopeKey(document);
|
|
408
|
+
if (!flow[scopeKey]) {
|
|
409
|
+
flow[scopeKey] = {};
|
|
410
|
+
}
|
|
411
|
+
const scopes = flow[scopeKey];
|
|
382
412
|
const isRename = Boolean(oldScope) && oldScope !== scope;
|
|
383
413
|
// Rename: drop the previous key so iteration order stays predictable.
|
|
384
414
|
if (isRename) {
|
|
385
|
-
if (!(oldScope in
|
|
415
|
+
if (!(oldScope in scopes)) {
|
|
386
416
|
return;
|
|
387
417
|
}
|
|
388
|
-
delete
|
|
418
|
+
delete scopes[oldScope];
|
|
389
419
|
}
|
|
390
|
-
|
|
420
|
+
scopes[scope] = description;
|
|
391
421
|
if (!isRename && !enable) {
|
|
392
422
|
return;
|
|
393
423
|
}
|
|
@@ -421,14 +451,15 @@ export const upsertScope = (store, document, { name, flowType, scope, descriptio
|
|
|
421
451
|
* the matching security scheme.
|
|
422
452
|
*/
|
|
423
453
|
export const deleteScope = (store, document, { name, flowType, scope }) => {
|
|
424
|
-
if (!isOpenApiDocument(document)) {
|
|
454
|
+
if (!isOpenApiDocument(document) && !isAsyncApiDocument(document)) {
|
|
425
455
|
return;
|
|
426
456
|
}
|
|
427
457
|
const flow = resolveOAuthFlow(document, name, flowType);
|
|
428
|
-
|
|
458
|
+
const scopes = flow?.[getFlowScopeKey(document)];
|
|
459
|
+
if (!scopes) {
|
|
429
460
|
return;
|
|
430
461
|
}
|
|
431
|
-
delete
|
|
462
|
+
delete scopes[scope];
|
|
432
463
|
walkSelectedSchemes(store, document, (selectedSchemes) => {
|
|
433
464
|
selectedSchemes.forEach((requirement) => {
|
|
434
465
|
if (!isNonOptionalSecurityRequirement(requirement)) {
|
package/dist/mutators/index.d.ts
CHANGED
|
@@ -66,7 +66,7 @@ export declare function generateClientMutators(store: WorkspaceStore | null): {
|
|
|
66
66
|
auth: {
|
|
67
67
|
updateSelectedSecuritySchemes: (payload: import("../events/definitions/auth.js").AuthEvents["auth:update:selected-security-schemes"]) => Promise<void>;
|
|
68
68
|
clearSelectedSecuritySchemes: (payload: import("../events/definitions/auth.js").AuthEvents["auth:clear:selected-security-schemes"]) => void;
|
|
69
|
-
updateSecurityScheme: (payload: import("../events/definitions/auth.js").AuthEvents["auth:update:security-scheme"]) => import("../schemas/v3.1/strict/security-scheme.js").
|
|
69
|
+
updateSecurityScheme: (payload: import("../events/definitions/auth.js").AuthEvents["auth:update:security-scheme"]) => import("../schemas/v3.1/strict/security-scheme.js").SecuritySchemeObject | undefined;
|
|
70
70
|
updateSecuritySchemeSecrets: (payload: import("../events/definitions/auth.js").AuthEvents["auth:update:security-scheme-secrets"]) => void;
|
|
71
71
|
clearSecuritySchemeSecrets: (payload: import("../events/definitions/auth.js").AuthEvents["auth:clear:security-scheme-secrets"]) => void;
|
|
72
72
|
updateSelectedAuthTab: (payload: import("../events/definitions/auth.js").AuthEvents["auth:update:active-index"]) => void;
|
|
@@ -157,7 +157,7 @@ export declare function generateClientMutators(store: WorkspaceStore | null): {
|
|
|
157
157
|
auth: {
|
|
158
158
|
updateSelectedSecuritySchemes: (payload: import("../events/definitions/auth.js").AuthEvents["auth:update:selected-security-schemes"]) => Promise<void>;
|
|
159
159
|
clearSelectedSecuritySchemes: (payload: import("../events/definitions/auth.js").AuthEvents["auth:clear:selected-security-schemes"]) => void;
|
|
160
|
-
updateSecurityScheme: (payload: import("../events/definitions/auth.js").AuthEvents["auth:update:security-scheme"]) => import("../schemas/v3.1/strict/security-scheme.js").
|
|
160
|
+
updateSecurityScheme: (payload: import("../events/definitions/auth.js").AuthEvents["auth:update:security-scheme"]) => import("../schemas/v3.1/strict/security-scheme.js").SecuritySchemeObject | undefined;
|
|
161
161
|
updateSecuritySchemeSecrets: (payload: import("../events/definitions/auth.js").AuthEvents["auth:update:security-scheme-secrets"]) => void;
|
|
162
162
|
clearSecuritySchemeSecrets: (payload: import("../events/definitions/auth.js").AuthEvents["auth:clear:security-scheme-secrets"]) => void;
|
|
163
163
|
updateSelectedAuthTab: (payload: import("../events/definitions/auth.js").AuthEvents["auth:update:active-index"]) => void;
|
|
@@ -1,9 +1,17 @@
|
|
|
1
1
|
import type { AuthenticationConfiguration } from '@scalar/types/api-reference';
|
|
2
|
+
import type { AsyncApiComponentsObject } from '@scalar/types/asyncapi/3.1';
|
|
2
3
|
import type { AuthStore } from '@scalar/workspace-store/entities/auth';
|
|
3
4
|
import { type ComponentsObject } from '@scalar/workspace-store/schemas/v3.1/strict/openapi-document';
|
|
4
5
|
import type { SecuritySchemeObjectSecret } from '../../../request-example/builder/security/secret-types.js';
|
|
5
6
|
/** Document security merged with the config security schemes */
|
|
6
7
|
export type MergedSecuritySchemes = Record<string, SecuritySchemeObjectSecret>;
|
|
7
|
-
/**
|
|
8
|
-
|
|
8
|
+
/**
|
|
9
|
+
* Merge the authentication config with the document security schemes + the auth store secrets.
|
|
10
|
+
*
|
|
11
|
+
* AsyncAPI keeps its security schemes in the same `components.securitySchemes` slot and shares the
|
|
12
|
+
* `http`/`apiKey`/`oauth2`/`openIdConnect` shapes with OpenAPI, so we accept either spec's schemes
|
|
13
|
+
* here. Every value is coerced into the OpenAPI `SecuritySchemeObject` shape below, so broker-specific
|
|
14
|
+
* AsyncAPI types still flow through and degrade gracefully downstream.
|
|
15
|
+
*/
|
|
16
|
+
export declare const mergeSecurity: (documentSecuritySchemes: ComponentsObject["securitySchemes"] | NonNullable<AsyncApiComponentsObject["securitySchemes"]>, configSecuritySchemes: AuthenticationConfiguration["securitySchemes"], authStore: AuthStore, documentName: string, oauth2RedirectUri?: string) => MergedSecuritySchemes;
|
|
9
17
|
//# sourceMappingURL=merge-security.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"merge-security.d.ts","sourceRoot":"","sources":["../../../../src/request-example/context/security/merge-security.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,6BAA6B,CAAA;AAC9E,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uCAAuC,CAAA;AAKtE,OAAO,EACL,KAAK,gBAAgB,EAGtB,MAAM,8DAA8D,CAAA;AAErE,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,iDAAiD,CAAA;AAIjG,gEAAgE;AAChE,MAAM,MAAM,qBAAqB,GAAG,MAAM,CAAC,MAAM,EAAE,0BAA0B,CAAC,CAAA;
|
|
1
|
+
{"version":3,"file":"merge-security.d.ts","sourceRoot":"","sources":["../../../../src/request-example/context/security/merge-security.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,6BAA6B,CAAA;AAC9E,OAAO,KAAK,EAAE,wBAAwB,EAAgC,MAAM,4BAA4B,CAAA;AACxG,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uCAAuC,CAAA;AAKtE,OAAO,EACL,KAAK,gBAAgB,EAGtB,MAAM,8DAA8D,CAAA;AAErE,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,iDAAiD,CAAA;AAIjG,gEAAgE;AAChE,MAAM,MAAM,qBAAqB,GAAG,MAAM,CAAC,MAAM,EAAE,0BAA0B,CAAC,CAAA;AAqE9E;;;;;;;GAOG;AACH,eAAO,MAAM,aAAa,GACxB,yBACI,gBAAgB,CAAC,iBAAiB,CAAC,GACnC,WAAW,CAAC,wBAAwB,CAAC,iBAAiB,CAAC,CAAM,EACjE,uBAAuB,2BAA2B,CAAC,iBAAiB,CAAM,EAC1E,WAAW,SAAS,EACpB,cAAc,MAAM,EACpB,oBAAoB,MAAM,KACzB,qBAmCF,CAAA"}
|
|
@@ -5,7 +5,68 @@ import { mergeObjects } from '@scalar/workspace-store/helpers/merge-object';
|
|
|
5
5
|
import { coerceValue } from '@scalar/workspace-store/schemas/typebox-coerce';
|
|
6
6
|
import { SecuritySchemeObjectSchema, } from '@scalar/workspace-store/schemas/v3.1/strict/openapi-document';
|
|
7
7
|
import { extractSecuritySchemeSecrets } from './extract-security-scheme-secrets.js';
|
|
8
|
-
|
|
8
|
+
const hasAvailableScopes = (flow) => {
|
|
9
|
+
const resolved = getResolvedRef(flow);
|
|
10
|
+
return Boolean(resolved) && typeof resolved === 'object' && 'availableScopes' in resolved;
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Rename each AsyncAPI OAuth2 flow's `availableScopes` map onto OpenAPI's `scopes`.
|
|
14
|
+
*
|
|
15
|
+
* AsyncAPI and OpenAPI use the same scope-name → description map, but under different keys. The
|
|
16
|
+
* auth UI reads `flow.scopes`, so without this rename an AsyncAPI OAuth2 scheme would render with
|
|
17
|
+
* no selectable scopes. Flows that already use `scopes` (OpenAPI) are returned untouched, so this
|
|
18
|
+
* is a no-op for OpenAPI schemes.
|
|
19
|
+
*/
|
|
20
|
+
const normalizeAsyncApiOAuthFlows = (flows) => {
|
|
21
|
+
const resolvedFlows = getResolvedRef(flows);
|
|
22
|
+
if (!resolvedFlows || typeof resolvedFlows !== 'object') {
|
|
23
|
+
return flows;
|
|
24
|
+
}
|
|
25
|
+
const entries = Object.entries(resolvedFlows);
|
|
26
|
+
if (!entries.some(([, flowValue]) => hasAvailableScopes(flowValue))) {
|
|
27
|
+
return flows;
|
|
28
|
+
}
|
|
29
|
+
return Object.fromEntries(entries.map(([flowKey, flowValue]) => {
|
|
30
|
+
if (!hasAvailableScopes(flowValue)) {
|
|
31
|
+
return [flowKey, flowValue];
|
|
32
|
+
}
|
|
33
|
+
// Prefer an OpenAPI-native `scopes` map if one is somehow already present, so we never drop it.
|
|
34
|
+
const { availableScopes, scopes: existingScopes, ...rest } = getResolvedRef(flowValue);
|
|
35
|
+
return [flowKey, { ...rest, scopes: existingScopes ?? availableScopes ?? {} }];
|
|
36
|
+
}));
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Map AsyncAPI-only security scheme shapes onto their OpenAPI equivalents where one exists.
|
|
40
|
+
*
|
|
41
|
+
* AsyncAPI's `httpApiKey` (a named key in `query`/`header`/`cookie`) is structurally identical to
|
|
42
|
+
* OpenAPI's `apiKey`, so we rename the type and let the shared apiKey path handle rendering and
|
|
43
|
+
* request injection. AsyncAPI OAuth2 flows carry their scope map under `availableScopes`, which we
|
|
44
|
+
* rename to OpenAPI's `scopes`. Everything else is returned unchanged — including AsyncAPI's own
|
|
45
|
+
* `apiKey` (`in: user | password`, no name), which has no OpenAPI counterpart and is value-only.
|
|
46
|
+
*/
|
|
47
|
+
const normalizeAsyncApiSecurityScheme = (scheme) => {
|
|
48
|
+
if (!(scheme && typeof scheme === 'object' && 'type' in scheme)) {
|
|
49
|
+
return scheme;
|
|
50
|
+
}
|
|
51
|
+
if (scheme.type === 'httpApiKey') {
|
|
52
|
+
return { ...scheme, type: 'apiKey' };
|
|
53
|
+
}
|
|
54
|
+
if (scheme.type === 'oauth2' && 'flows' in scheme && scheme.flows) {
|
|
55
|
+
const normalizedFlows = normalizeAsyncApiOAuthFlows(scheme.flows);
|
|
56
|
+
if (normalizedFlows !== scheme.flows) {
|
|
57
|
+
return { ...scheme, flows: normalizedFlows };
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return scheme;
|
|
61
|
+
};
|
|
62
|
+
/**
|
|
63
|
+
* Merge the authentication config with the document security schemes + the auth store secrets.
|
|
64
|
+
*
|
|
65
|
+
* AsyncAPI keeps its security schemes in the same `components.securitySchemes` slot and shares the
|
|
66
|
+
* `http`/`apiKey`/`oauth2`/`openIdConnect` shapes with OpenAPI, so we accept either spec's schemes
|
|
67
|
+
* here. Every value is coerced into the OpenAPI `SecuritySchemeObject` shape below, so broker-specific
|
|
68
|
+
* AsyncAPI types still flow through and degrade gracefully downstream.
|
|
69
|
+
*/
|
|
9
70
|
export const mergeSecurity = (documentSecuritySchemes = {}, configSecuritySchemes = {}, authStore, documentName, oauth2RedirectUri) => {
|
|
10
71
|
/** Resolve any refs in the document security schemes */
|
|
11
72
|
const resolvedDocumentSecuritySchemes = objectEntries(documentSecuritySchemes).reduce((acc, [key, value]) => {
|
|
@@ -19,10 +80,15 @@ export const mergeSecurity = (documentSecuritySchemes = {}, configSecurityScheme
|
|
|
19
80
|
const mergedSchemes = mergeObjects(resolvedDocumentSecuritySchemes, configSecuritySchemes) ?? {};
|
|
20
81
|
/** Convert the config secrets to the new secret extensions */
|
|
21
82
|
return objectEntries(mergedSchemes).reduce((acc, [name, value]) => {
|
|
83
|
+
// Fold AsyncAPI-only types (e.g. `httpApiKey`) onto their OpenAPI equivalents before coercing,
|
|
84
|
+
// so the downstream apiKey/http/oauth2 machinery recognises them instead of rejecting the type.
|
|
85
|
+
const scheme = normalizeAsyncApiSecurityScheme(value);
|
|
22
86
|
// We coerce in case the scheme is missing any key fields like type
|
|
23
|
-
const coerced = coerceValue(SecuritySchemeObjectSchema,
|
|
87
|
+
const coerced = coerceValue(SecuritySchemeObjectSchema, scheme);
|
|
24
88
|
// We then overwrite it back with the original value to keep any other fields like description, etc.
|
|
25
|
-
|
|
89
|
+
// `coerced` has already laundered the value into the OpenAPI shape (including any AsyncAPI scheme),
|
|
90
|
+
// so we narrow here to restore the extra fields without re-widening the type.
|
|
91
|
+
const merged = { ...coerced, ...scheme };
|
|
26
92
|
acc[name] = extractSecuritySchemeSecrets(merged, authStore, name, documentName, oauth2RedirectUri);
|
|
27
93
|
return acc;
|
|
28
94
|
}, {});
|
|
@@ -21,4 +21,11 @@ export declare const isAsyncApiDocument: (value: unknown) => value is AsyncApiDo
|
|
|
21
21
|
* document shapes, or `undefined` when it matches neither.
|
|
22
22
|
*/
|
|
23
23
|
export declare const getDocumentType: (value: unknown) => "openapi" | "asyncapi" | undefined;
|
|
24
|
+
/**
|
|
25
|
+
* Human-readable label for a document type, e.g. for badges and warnings.
|
|
26
|
+
*
|
|
27
|
+
* Defaults to `'OpenAPI'` when the type is unknown, matching the OpenAPI-native surfaces that
|
|
28
|
+
* only distinguish AsyncAPI as the exception.
|
|
29
|
+
*/
|
|
30
|
+
export declare const getDocumentTypeLabel: (documentType: "openapi" | "asyncapi" | undefined) => string;
|
|
24
31
|
//# sourceMappingURL=type-guards.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"type-guards.d.ts","sourceRoot":"","sources":["../../src/schemas/type-guards.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAA;AAElE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAA;AAErE;;;;;;GAMG;AACH,eAAO,MAAM,iBAAiB,GAAI,OAAO,OAAO,KAAG,KAAK,IAAI,eACgB,CAAA;AAE5E;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,GAAI,OAAO,OAAO,KAAG,KAAK,IAAI,gBACiB,CAAA;AAE9E;;;;;GAKG;AACH,eAAO,MAAM,eAAe,GAAI,OAAO,OAAO,KAAG,SAAS,GAAG,UAAU,GAAG,SAUzE,CAAA"}
|
|
1
|
+
{"version":3,"file":"type-guards.d.ts","sourceRoot":"","sources":["../../src/schemas/type-guards.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAA;AAElE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAA;AAErE;;;;;;GAMG;AACH,eAAO,MAAM,iBAAiB,GAAI,OAAO,OAAO,KAAG,KAAK,IAAI,eACgB,CAAA;AAE5E;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,GAAI,OAAO,OAAO,KAAG,KAAK,IAAI,gBACiB,CAAA;AAE9E;;;;;GAKG;AACH,eAAO,MAAM,eAAe,GAAI,OAAO,OAAO,KAAG,SAAS,GAAG,UAAU,GAAG,SAUzE,CAAA;AAED;;;;;GAKG;AACH,eAAO,MAAM,oBAAoB,GAAI,cAAc,SAAS,GAAG,UAAU,GAAG,SAAS,KAAG,MAClC,CAAA"}
|
|
@@ -28,3 +28,10 @@ export const getDocumentType = (value) => {
|
|
|
28
28
|
}
|
|
29
29
|
return undefined;
|
|
30
30
|
};
|
|
31
|
+
/**
|
|
32
|
+
* Human-readable label for a document type, e.g. for badges and warnings.
|
|
33
|
+
*
|
|
34
|
+
* Defaults to `'OpenAPI'` when the type is unknown, matching the OpenAPI-native surfaces that
|
|
35
|
+
* only distinguish AsyncAPI as the exception.
|
|
36
|
+
*/
|
|
37
|
+
export const getDocumentTypeLabel = (documentType) => documentType === 'asyncapi' ? 'AsyncAPI' : 'OpenAPI';
|
package/package.json
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"openapi",
|
|
17
17
|
"scalar"
|
|
18
18
|
],
|
|
19
|
-
"version": "0.55.
|
|
19
|
+
"version": "0.55.5",
|
|
20
20
|
"engines": {
|
|
21
21
|
"node": ">=22"
|
|
22
22
|
},
|
|
@@ -148,14 +148,14 @@
|
|
|
148
148
|
"type-fest": "^5.3.1",
|
|
149
149
|
"vue": "^3.5.30",
|
|
150
150
|
"yaml": "^2.8.3",
|
|
151
|
-
"@scalar/
|
|
152
|
-
"@scalar/
|
|
153
|
-
"@scalar/json-magic": "0.12.
|
|
154
|
-
"@scalar/
|
|
155
|
-
"@scalar/
|
|
156
|
-
"@scalar/types": "0.16.
|
|
157
|
-
"@scalar/
|
|
158
|
-
"@scalar/
|
|
151
|
+
"@scalar/asyncapi-upgrader": "0.1.3",
|
|
152
|
+
"@scalar/helpers": "0.9.1",
|
|
153
|
+
"@scalar/json-magic": "0.12.18",
|
|
154
|
+
"@scalar/schemas": "0.7.3",
|
|
155
|
+
"@scalar/openapi-upgrader": "0.2.10",
|
|
156
|
+
"@scalar/types": "0.16.3",
|
|
157
|
+
"@scalar/validation": "0.6.1",
|
|
158
|
+
"@scalar/snippetz": "0.9.22"
|
|
159
159
|
},
|
|
160
160
|
"devDependencies": {
|
|
161
161
|
"@google-cloud/storage": "7.16.0",
|