@scalar/mock-server 0.2.63 → 0.2.65

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.
Files changed (55) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/README.md +1 -1
  3. package/dist/createMockServer.d.ts +3 -9
  4. package/dist/createMockServer.d.ts.map +1 -1
  5. package/dist/createMockServer.js +18 -114
  6. package/dist/routes/mockAnyResponse.d.ts +8 -0
  7. package/dist/routes/mockAnyResponse.d.ts.map +1 -0
  8. package/dist/routes/mockAnyResponse.js +72 -0
  9. package/dist/routes/respondWithAuthorizePage.d.ts +6 -0
  10. package/dist/routes/respondWithAuthorizePage.d.ts.map +1 -0
  11. package/dist/routes/respondWithAuthorizePage.js +105 -0
  12. package/dist/routes/respondWithOpenApiDocument.d.ts +14 -0
  13. package/dist/routes/respondWithOpenApiDocument.d.ts.map +1 -0
  14. package/dist/routes/respondWithOpenApiDocument.js +38 -0
  15. package/dist/routes/respondWithToken.d.ts +15 -0
  16. package/dist/routes/respondWithToken.d.ts.map +1 -0
  17. package/dist/routes/respondWithToken.js +47 -0
  18. package/dist/types.d.ts +16 -0
  19. package/dist/types.d.ts.map +1 -1
  20. package/dist/utils/createOpenAPIDocument.d.ts +4 -0
  21. package/dist/utils/createOpenAPIDocument.d.ts.map +1 -0
  22. package/dist/utils/getOpenAuthTokenUrls.d.ts +7 -0
  23. package/dist/utils/getOpenAuthTokenUrls.d.ts.map +1 -0
  24. package/dist/utils/getOpenAuthTokenUrls.js +55 -0
  25. package/dist/utils/handleAuthentication.d.ts +10 -0
  26. package/dist/utils/handleAuthentication.d.ts.map +1 -0
  27. package/dist/utils/handleAuthentication.js +103 -0
  28. package/dist/utils/index.d.ts +1 -7
  29. package/dist/utils/index.d.ts.map +1 -1
  30. package/dist/utils/logAuthenticationInstructions.d.ts +6 -0
  31. package/dist/utils/logAuthenticationInstructions.d.ts.map +1 -0
  32. package/dist/utils/logAuthenticationInstructions.js +112 -0
  33. package/dist/utils/setupAuthenticationRoutes.d.ts +7 -0
  34. package/dist/utils/setupAuthenticationRoutes.d.ts.map +1 -0
  35. package/dist/utils/setupAuthenticationRoutes.js +64 -0
  36. package/package.json +6 -6
  37. package/dist/utils/anyBasicAuthentication.d.ts +0 -6
  38. package/dist/utils/anyBasicAuthentication.d.ts.map +0 -1
  39. package/dist/utils/anyBasicAuthentication.js +0 -25
  40. package/dist/utils/anyOpenAuthPasswordGrantAuthentication.d.ts +0 -6
  41. package/dist/utils/anyOpenAuthPasswordGrantAuthentication.d.ts.map +0 -1
  42. package/dist/utils/anyOpenAuthPasswordGrantAuthentication.js +0 -22
  43. package/dist/utils/findPreferredResponseKey.test.d.ts +0 -2
  44. package/dist/utils/findPreferredResponseKey.test.d.ts.map +0 -1
  45. package/dist/utils/getOpenAuthTokenUrl.d.ts +0 -3
  46. package/dist/utils/getOpenAuthTokenUrl.d.ts.map +0 -1
  47. package/dist/utils/getOpenAuthTokenUrl.js +0 -21
  48. package/dist/utils/honoRouteFromPath.test.d.ts +0 -2
  49. package/dist/utils/honoRouteFromPath.test.d.ts.map +0 -1
  50. package/dist/utils/isBasicAuthenticationRequired.d.ts +0 -3
  51. package/dist/utils/isBasicAuthenticationRequired.d.ts.map +0 -1
  52. package/dist/utils/isBasicAuthenticationRequired.js +0 -13
  53. package/dist/utils/isOpenAuthPasswordGrantRequired.d.ts +0 -3
  54. package/dist/utils/isOpenAuthPasswordGrantRequired.d.ts.map +0 -1
  55. package/dist/utils/isOpenAuthPasswordGrantRequired.js +0 -14
package/CHANGELOG.md CHANGED
@@ -1,5 +1,25 @@
1
1
  # @scalar/mock-server
2
2
 
3
+ ## 0.2.65
4
+
5
+ ### Patch Changes
6
+
7
+ - 4cf8c52: feat: mock the OAuth code flow
8
+ - Updated dependencies [7323370]
9
+ - Updated dependencies [d7a6c55]
10
+ - Updated dependencies [69bda25]
11
+ - @scalar/openapi-parser@0.8.8
12
+ - @scalar/oas-utils@0.2.61
13
+
14
+ ## 0.2.64
15
+
16
+ ### Patch Changes
17
+
18
+ - Updated dependencies [2b540b9]
19
+ - @scalar/openapi-types@0.1.4
20
+ - @scalar/oas-utils@0.2.60
21
+ - @scalar/openapi-parser@0.8.7
22
+
3
23
  ## 0.2.63
4
24
 
5
25
  ### Patch Changes
package/README.md CHANGED
@@ -19,7 +19,7 @@ A powerful Node.js server that generates and returns realistic mock data based o
19
19
 
20
20
  ## Quickstart
21
21
 
22
- It’s part of [our Scalar CLI](https://github.com/scalar/scalar/tree/main/packages/cli), sou can boot it literllay in seconds:
22
+ It’s part of [our Scalar CLI](https://github.com/scalar/scalar/tree/main/packages/cli), you can boot it literllay in seconds:
23
23
 
24
24
  ```bash
25
25
  npx @scalar/cli mock openapi.json --watch
@@ -1,13 +1,7 @@
1
- import type { OpenAPI } from '@scalar/openapi-types';
2
- import { type Context, Hono } from 'hono';
1
+ import { Hono } from 'hono';
2
+ import type { MockServerOptions } from './types.js';
3
3
  /**
4
4
  * Create a mock server instance
5
5
  */
6
- export declare function createMockServer(options?: {
7
- specification: string | Record<string, any>;
8
- onRequest?: (data: {
9
- context: Context;
10
- operation: OpenAPI.Operation;
11
- }) => void;
12
- }): Promise<Hono<import("hono/types").BlankEnv, import("hono/types").BlankSchema, "/">>;
6
+ export declare function createMockServer(options: MockServerOptions): Promise<Hono<import("hono/types").BlankEnv, import("hono/types").BlankSchema, "/">>;
13
7
  //# sourceMappingURL=createMockServer.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"createMockServer.d.ts","sourceRoot":"","sources":["../src/createMockServer.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAA;AACpD,OAAO,EAAE,KAAK,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAkBzC;;GAEG;AACH,wBAAsB,gBAAgB,CAAC,OAAO,CAAC,EAAE;IAC/C,aAAa,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IAC3C,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,SAAS,EAAE,OAAO,CAAC,SAAS,CAAA;KAAE,KAAK,IAAI,CAAA;CAC/E,uFAqLA"}
1
+ {"version":3,"file":"createMockServer.d.ts","sourceRoot":"","sources":["../src/createMockServer.ts"],"names":[],"mappings":"AAEA,OAAO,EAAgB,IAAI,EAAE,MAAM,MAAM,CAAA;AAKzC,OAAO,KAAK,EAAc,iBAAiB,EAAE,MAAM,SAAS,CAAA;AAQ5D;;GAEG;AACH,wBAAsB,gBAAgB,CAAC,OAAO,EAAE,iBAAiB,uFAoDhE"}
@@ -1,18 +1,14 @@
1
- import { getExampleFromSchema } from '@scalar/oas-utils/spec-getters';
2
1
  import { openapi } from '@scalar/openapi-parser';
3
2
  import { Hono } from 'hono';
4
- import { accepts } from 'hono/accepts';
5
3
  import { cors } from 'hono/cors';
6
- import objectToXML from 'object-to-xml';
7
- import { anyBasicAuthentication } from './utils/anyBasicAuthentication.js';
8
- import { anyOpenAuthPasswordGrantAuthentication } from './utils/anyOpenAuthPasswordGrantAuthentication.js';
9
- import { findPreferredResponseKey } from './utils/findPreferredResponseKey.js';
10
- import { getOpenAuthTokenUrl } from './utils/getOpenAuthTokenUrl.js';
4
+ import { mockAnyResponse } from './routes/mockAnyResponse.js';
5
+ import { respondWithOpenApiDocument } from './routes/respondWithOpenApiDocument.js';
11
6
  import { getOperations } from './utils/getOperations.js';
7
+ import { handleAuthentication } from './utils/handleAuthentication.js';
12
8
  import { honoRouteFromPath } from './utils/honoRouteFromPath.js';
13
9
  import { isAuthenticationRequired } from './utils/isAuthenticationRequired.js';
14
- import { isBasicAuthenticationRequired } from './utils/isBasicAuthenticationRequired.js';
15
- import { isOpenAuthPasswordGrantRequired } from './utils/isOpenAuthPasswordGrantRequired.js';
10
+ import { logAuthenticationInstructions } from './utils/logAuthenticationInstructions.js';
11
+ import { setupAuthenticationRoutes } from './utils/setupAuthenticationRoutes.js';
16
12
 
17
13
  /**
18
14
  * Create a mock server instance
@@ -26,42 +22,10 @@ async function createMockServer(options) {
26
22
  .get();
27
23
  // CORS headers
28
24
  app.use(cors());
29
- // OpenAPI JSON file
30
- app.get('/openapi.json', async (c) => {
31
- if (!options?.specification) {
32
- return c.text('Not found', 404);
33
- }
34
- const { specification } = await openapi().load(options.specification).get();
35
- return c.json(specification);
36
- });
37
- // OpenAPI YAML file
38
- app.get('/openapi.yaml', async (c) => {
39
- if (!options?.specification) {
40
- return c.text('Not found', 404);
41
- }
42
- const specification = await openapi().load(options.specification).toYaml();
43
- return c.text(specification);
44
- });
45
- // OpenAuth2 token endpoint
46
- const tokenUrl = getOpenAuthTokenUrl(schema);
47
- if (typeof tokenUrl === 'string') {
48
- app.post(tokenUrl, async (c) => {
49
- return c.json({
50
- access_token: 'super-secret-token',
51
- token_type: 'Bearer',
52
- expires_in: 3600,
53
- refresh_token: 'secret-refresh-token',
54
- }, 200,
55
- /**
56
- * When responding with an access token, the server must also include the additional Cache-Control: no-store
57
- * HTTP header to ensure clients do not cache this request.
58
- * @see https://www.oauth.com/oauth2-servers/access-tokens/access-token-response/
59
- */
60
- {
61
- 'Cache-Control': 'no-store',
62
- });
63
- });
64
- }
25
+ /** Authentication methods defined in the OpenAPI document */
26
+ setupAuthenticationRoutes(app, schema);
27
+ logAuthenticationInstructions(schema?.components?.securitySchemes ||
28
+ {});
65
29
  /** Paths specified in the OpenAPI document */
66
30
  const paths = schema?.paths ?? {};
67
31
  Object.keys(paths).forEach((path) => {
@@ -70,78 +34,18 @@ async function createMockServer(options) {
70
34
  methods.forEach((method) => {
71
35
  const route = honoRouteFromPath(path);
72
36
  const operation = schema?.paths?.[path]?.[method];
73
- // Check if authentication is required
74
- const requiresAuthentication = isAuthenticationRequired(operation.security);
75
- // Check whether we need basic authentication
76
- const requiresBasicAuthentication = isBasicAuthenticationRequired(operation, schema);
77
- // Add HTTP basic authentication
78
- if (requiresAuthentication && requiresBasicAuthentication) {
79
- app[method](route, anyBasicAuthentication());
80
- }
81
- // Check whether we need OpenAuth password grant authentication
82
- const requiresOpenAuthPasswordGrant = isOpenAuthPasswordGrantRequired(operation, schema);
83
- // Add HTTP basic authentication
84
- if (requiresAuthentication && requiresOpenAuthPasswordGrant) {
85
- app[method](route, anyOpenAuthPasswordGrantAuthentication());
37
+ // Check if authentication is required for this operation
38
+ if (isAuthenticationRequired(operation.security)) {
39
+ app[method](route, handleAuthentication(schema, operation));
86
40
  }
87
- // Route
88
- app[method](route, (c) => {
89
- // Call onRequest callback
90
- if (options?.onRequest) {
91
- options.onRequest({
92
- context: c,
93
- operation,
94
- });
95
- }
96
- // Response
97
- // default, 200, 201 …
98
- const preferredResponseKey = findPreferredResponseKey(Object.keys(operation.responses ?? {}));
99
- const preferredResponse = preferredResponseKey
100
- ? operation.responses?.[preferredResponseKey]
101
- : null;
102
- const supportedContentTypes = Object.keys(preferredResponse?.content ?? {});
103
- // Headers
104
- const headers = preferredResponse?.headers ?? {};
105
- Object.keys(headers).forEach((header) => {
106
- c.header(header, headers[header].schema
107
- ? getExampleFromSchema(headers[header].schema)
108
- : null);
109
- });
110
- // Content-Type
111
- const acceptedContentType = accepts(c, {
112
- header: 'Accept',
113
- supports: supportedContentTypes,
114
- default: supportedContentTypes.includes('application/json')
115
- ? 'application/json'
116
- : supportedContentTypes[0],
117
- });
118
- c.header('Content-Type', acceptedContentType);
119
- const acceptedResponse = preferredResponse?.content?.[acceptedContentType];
120
- // Body
121
- const body = acceptedResponse?.example
122
- ? acceptedResponse.example
123
- : acceptedResponse?.schema
124
- ? getExampleFromSchema(acceptedResponse.schema, {
125
- emptyString: '…',
126
- variables: c.req.param(),
127
- })
128
- : null;
129
- // Status code
130
- const statusCode = parseInt(preferredResponseKey === 'default'
131
- ? '200'
132
- : (preferredResponseKey ?? '200'), 10);
133
- c.status(statusCode);
134
- return c.body(typeof body === 'object'
135
- ? // XML
136
- acceptedContentType?.includes('xml')
137
- ? `<?xml version="1.0" encoding="UTF-8"?>${objectToXML(body)}`
138
- : // JSON
139
- JSON.stringify(body, null, 2)
140
- : // String
141
- body);
142
- });
41
+ // Actual route
42
+ app[method](route, (c) => mockAnyResponse(c, operation, options));
143
43
  });
144
44
  });
45
+ // OpenAPI JSON file
46
+ app.get('/openapi.json', (c) => respondWithOpenApiDocument(c, options?.specification, 'json'));
47
+ // OpenAPI YAML file
48
+ app.get('/openapi.yaml', (c) => respondWithOpenApiDocument(c, options?.specification, 'yaml'));
145
49
  return app;
146
50
  }
147
51
 
@@ -0,0 +1,8 @@
1
+ import type { OpenAPI } from '@scalar/openapi-types';
2
+ import type { Context } from 'hono';
3
+ import type { MockServerOptions } from '../types.js';
4
+ /**
5
+ * Mock any response
6
+ */
7
+ export declare function mockAnyResponse(c: Context, operation: OpenAPI.Operation, options: MockServerOptions): Response;
8
+ //# sourceMappingURL=mockAnyResponse.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mockAnyResponse.d.ts","sourceRoot":"","sources":["../../src/routes/mockAnyResponse.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAA;AACpD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAA;AAMnC,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAA;AAGjD;;GAEG;AACH,wBAAgB,eAAe,CAC7B,CAAC,EAAE,OAAO,EACV,SAAS,EAAE,OAAO,CAAC,SAAS,EAC5B,OAAO,EAAE,iBAAiB,YAiF3B"}
@@ -0,0 +1,72 @@
1
+ import { getExampleFromSchema } from '@scalar/oas-utils/spec-getters';
2
+ import { accepts } from 'hono/accepts';
3
+ import objectToXML from 'object-to-xml';
4
+ import { findPreferredResponseKey } from '../utils/findPreferredResponseKey.js';
5
+
6
+ /**
7
+ * Mock any response
8
+ */
9
+ function mockAnyResponse(c, operation, options) {
10
+ // Call onRequest callback
11
+ if (options?.onRequest) {
12
+ options.onRequest({
13
+ context: c,
14
+ operation,
15
+ });
16
+ }
17
+ // Response
18
+ // default, 200, 201 …
19
+ const preferredResponseKey = findPreferredResponseKey(Object.keys(operation.responses ?? {}));
20
+ const preferredResponse = preferredResponseKey
21
+ ? operation.responses?.[preferredResponseKey]
22
+ : null;
23
+ if (!preferredResponse) {
24
+ c.status(500);
25
+ return c.json({ error: 'No response defined for this operation.' });
26
+ }
27
+ const supportedContentTypes = Object.keys(preferredResponse?.content ?? {});
28
+ // Headers
29
+ const headers = preferredResponse?.headers ?? {};
30
+ Object.keys(headers).forEach((header) => {
31
+ const value = headers[header].schema
32
+ ? getExampleFromSchema(headers[header].schema)
33
+ : null;
34
+ if (value !== null) {
35
+ c.header(header, value);
36
+ }
37
+ });
38
+ // Content-Type
39
+ const acceptedContentType = accepts(c, {
40
+ header: 'Accept',
41
+ supports: supportedContentTypes,
42
+ default: supportedContentTypes.includes('application/json')
43
+ ? 'application/json'
44
+ : supportedContentTypes[0],
45
+ });
46
+ c.header('Content-Type', acceptedContentType);
47
+ const acceptedResponse = preferredResponse?.content?.[acceptedContentType];
48
+ // Body
49
+ const body = acceptedResponse?.example
50
+ ? acceptedResponse.example
51
+ : acceptedResponse?.schema
52
+ ? getExampleFromSchema(acceptedResponse.schema, {
53
+ emptyString: '…',
54
+ variables: c.req.param(),
55
+ })
56
+ : null;
57
+ // Status code
58
+ const statusCode = parseInt(preferredResponseKey === 'default'
59
+ ? '200'
60
+ : (preferredResponseKey ?? '200'), 10);
61
+ c.status(statusCode);
62
+ return c.body(typeof body === 'object'
63
+ ? // XML
64
+ acceptedContentType?.includes('xml')
65
+ ? `<?xml version="1.0" encoding="UTF-8"?>${objectToXML(body)}`
66
+ : // JSON
67
+ JSON.stringify(body, null, 2)
68
+ : // String
69
+ body);
70
+ }
71
+
72
+ export { mockAnyResponse };
@@ -0,0 +1,6 @@
1
+ import type { Context } from 'hono';
2
+ /**
3
+ * Responds with an HTML page that simulates an OAuth 2.0 authorization page.
4
+ */
5
+ export declare function respondWithAuthorizePage(c: Context, title?: string): Response;
6
+ //# sourceMappingURL=respondWithAuthorizePage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"respondWithAuthorizePage.d.ts","sourceRoot":"","sources":["../../src/routes/respondWithAuthorizePage.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAA;AAKnC;;GAEG;AACH,wBAAgB,wBAAwB,CAAC,CAAC,EAAE,OAAO,EAAE,KAAK,GAAE,MAAW,YAoCtE"}
@@ -0,0 +1,105 @@
1
+ /** Always responds with this code */
2
+ const EXAMPLE_AUTHORIZATION_CODE = 'super-secret-token';
3
+ /**
4
+ * Responds with an HTML page that simulates an OAuth 2.0 authorization page.
5
+ */
6
+ function respondWithAuthorizePage(c, title = '') {
7
+ const redirectUri = c.req.query('redirect_uri');
8
+ const state = c.req.query('state');
9
+ if (!redirectUri) {
10
+ return c.html(generateErrorHtml('Missing redirect_uri parameter', 'This parameter is required for the OAuth 2.0 authorization flow to function correctly. Please provide a valid redirect URI in your request.'), 400);
11
+ }
12
+ try {
13
+ // Validate redirect URI against allowed domains
14
+ const redirectUrl = new URL(redirectUri);
15
+ redirectUrl.searchParams.set('code', EXAMPLE_AUTHORIZATION_CODE);
16
+ if (state) {
17
+ redirectUrl.searchParams.set('state', state);
18
+ }
19
+ const htmlContent = generateAuthorizationHtml(redirectUrl.toString(), title);
20
+ return c.html(htmlContent);
21
+ }
22
+ catch (error) {
23
+ return c.html(generateErrorHtml('Invalid redirect_uri format', 'Please provide a valid URL. The redirect_uri parameter must be a properly formatted URL that includes the protocol (e.g., https://) and a valid domain. This is essential for the OAuth 2.0 flow to securely redirect after authorization.'), 400);
24
+ }
25
+ }
26
+ function generateAuthorizationHtml(redirectUrl, title = '') {
27
+ return `
28
+ <!DOCTYPE html>
29
+ <html lang="en">
30
+ <head>
31
+ <meta charset="UTF-8">
32
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
33
+ <title>OAuth 2.0 Authorization</title>
34
+ <script src="https://cdn.tailwindcss.com"></script>
35
+ </head>
36
+ <body class="flex justify-center items-center h-screen bg-gray-100">
37
+
38
+ <div class="flex flex-col">
39
+ <div class="mb-5 flex justify-center items-center gap-2">
40
+ <img src="https://scalar.com/logo-dark.svg" class="w-6 inline-block" />
41
+ <div class="font-medium truncate max-w-[26ch] text-lg">
42
+ ${title}
43
+ </div>
44
+ </div>
45
+ <div class="bg-gray-50 rounded-lg p-1 rounded-lg w-[28rem] shadow">
46
+ <div class="">
47
+ <h1 class="text font-medium text-gray-800 px-6 pt-2 pb-3 flex gap-3 rounded-t-lg">
48
+ OAuth 2.0 Authorization
49
+ </h1>
50
+ <div class="bg-white rounded">
51
+ <div class="text-gray-600 text-base px-6 py-5 flex flex-col gap-3">
52
+ <p>
53
+ This application is requesting access to your account. By granting authorization, you allow the application to perform certain actions on your behalf.
54
+ </p>
55
+ <p>
56
+ If you’re comfortable with the access being requested, click the button below to grant authorization:
57
+ </p>
58
+ </div>
59
+ <div class="px-6 py-4 pt-0 flex justify-between">
60
+ <a href="javascript:history.back()" class="inline-block px-6 py-2 text-gray-600 rounded border" aria-label="Cancel authorization">
61
+ Cancel
62
+ </a>
63
+ <a href="${redirectUrl}" class="inline-block px-6 py-2 bg-black text-white rounded transition-colors duration-300 hover:bg-gray-800" aria-label="Authorize application">
64
+ Authorize
65
+ </a>
66
+ </div>
67
+ </div>
68
+ </div>
69
+ </div>
70
+
71
+ <p class="text-xs text-gray-400 mt-5 text-center">
72
+ This authorization page is provided by @scalar/mock-server
73
+ </p>
74
+
75
+ </div>
76
+ </body>
77
+ </html>
78
+ `;
79
+ }
80
+ function generateErrorHtml(title, message) {
81
+ return `<html>
82
+ <html lang="en">
83
+ <head>
84
+ <meta charset="UTF-8">
85
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
86
+ <title>OAuth 2.0 Authorization</title>
87
+ <script src="https://cdn.tailwindcss.com"></script>
88
+ </head>
89
+ <body>
90
+ <div class="p-4 m-8 flex flex-col gap-4 text-lg">
91
+ <h1 class="font-bold">
92
+ Error: ${title}
93
+ </h1>
94
+ <p>
95
+ ${message}
96
+ </p>
97
+ <p>
98
+ Example: <code class="bg-gray-100 py-1 px-2 rounded text-base"><a href="?redirect_uri=https://example.com/callback">?redirect_uri=https://example.com/callback</a></code>
99
+ </p>
100
+ </div>
101
+ </body>
102
+ </html>`;
103
+ }
104
+
105
+ export { respondWithAuthorizePage };
@@ -0,0 +1,14 @@
1
+ import type { Context } from 'hono';
2
+ /**
3
+ * OpenAPI endpoints
4
+ */
5
+ export declare function respondWithOpenApiDocument(c: Context, input?: string | Record<string, any>, format?: 'json' | 'yaml'): Promise<(Response & import("hono").TypedResponse<{
6
+ [x: string]: any;
7
+ }, import("hono/utils/http-status").StatusCode, "json">) | (Response & import("hono").TypedResponse<string, import("hono/utils/http-status").StatusCode, "text">) | (Response & import("hono").TypedResponse<{
8
+ error: string;
9
+ message: string;
10
+ }, 500, "json">) | (Response & import("hono").TypedResponse<{
11
+ error: string;
12
+ message: string;
13
+ }, 400, "json">)>;
14
+ //# sourceMappingURL=respondWithOpenApiDocument.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"respondWithOpenApiDocument.d.ts","sourceRoot":"","sources":["../../src/routes/respondWithOpenApiDocument.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAA;AAEnC;;GAEG;AACH,wBAAsB,0BAA0B,CAC9C,CAAC,EAAE,OAAO,EACV,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EACpC,MAAM,GAAE,MAAM,GAAG,MAAe;;;;;;;;kBAwCjC"}
@@ -0,0 +1,38 @@
1
+ import { openapi } from '@scalar/openapi-parser';
2
+
3
+ /**
4
+ * OpenAPI endpoints
5
+ */
6
+ async function respondWithOpenApiDocument(c, input, format = 'json') {
7
+ if (!input) {
8
+ return c.text('Not found', 404);
9
+ }
10
+ try {
11
+ const { specification } = await openapi().load(input).get();
12
+ // JSON
13
+ if (format === 'json') {
14
+ c.header('Content-Type', 'application/json');
15
+ return c.json(specification);
16
+ }
17
+ // YAML
18
+ try {
19
+ const yamlSpecification = await openapi().load(input).toYaml();
20
+ c.header('Content-Type', 'text/yaml');
21
+ return c.text(yamlSpecification);
22
+ }
23
+ catch (error) {
24
+ return c.json({
25
+ error: 'Failed to convert specification to YAML',
26
+ message: error instanceof Error ? error.message : 'Unknown error occurred',
27
+ }, 500);
28
+ }
29
+ }
30
+ catch (error) {
31
+ return c.json({
32
+ error: 'Failed to parse OpenAPI specification',
33
+ message: error instanceof Error ? error.message : 'Unknown error occurred',
34
+ }, 400);
35
+ }
36
+ }
37
+
38
+ export { respondWithOpenApiDocument };
@@ -0,0 +1,15 @@
1
+ import type { Context } from 'hono';
2
+ /**
3
+ * Responds with a JSON object simulating an OAuth 2.0 token response.
4
+ */
5
+ export declare function respondWithToken(c: Context): (Response & import("hono").TypedResponse<{
6
+ error: string;
7
+ error_description: string;
8
+ }, 400, "json">) | (Response & import("hono").TypedResponse<{
9
+ access_token: string;
10
+ token_type: string;
11
+ expires_in: number;
12
+ refresh_token: string;
13
+ scope: string;
14
+ }, import("hono/utils/http-status").StatusCode, "json">);
15
+ //# sourceMappingURL=respondWithToken.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"respondWithToken.d.ts","sourceRoot":"","sources":["../../src/routes/respondWithToken.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAA;AAKnC;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,OAAO;;;;;;;;;yDAuD1C"}
@@ -0,0 +1,47 @@
1
+ /** Always responds with this token */
2
+ const EXAMPLE_ACCESS_TOKEN = 'super-secret-access-token';
3
+ /**
4
+ * Responds with a JSON object simulating an OAuth 2.0 token response.
5
+ */
6
+ function respondWithToken(c) {
7
+ const grantType = c.req.query('grant_type');
8
+ if (!grantType) {
9
+ return c.json({
10
+ error: 'invalid_request',
11
+ error_description: 'Missing grant_type parameter',
12
+ }, 400);
13
+ }
14
+ // Validate supported grant types
15
+ const supportedGrantTypes = [
16
+ 'authorization_code',
17
+ 'client_credentials',
18
+ 'refresh_token',
19
+ ];
20
+ if (!supportedGrantTypes.includes(grantType)) {
21
+ return c.json({
22
+ error: 'unsupported_grant_type',
23
+ error_description: `Grant type must be one of: ${supportedGrantTypes.join(', ')}`,
24
+ }, 400);
25
+ }
26
+ // Validate required parameters for each grant type
27
+ if (grantType === 'authorization_code' && !c.req.query('code')) {
28
+ return c.json({
29
+ error: 'invalid_request',
30
+ error_description: 'Missing code parameter',
31
+ }, 400);
32
+ }
33
+ // Simulate token generation
34
+ const tokenResponse = {
35
+ access_token: EXAMPLE_ACCESS_TOKEN,
36
+ token_type: 'Bearer',
37
+ expires_in: 3600,
38
+ refresh_token: 'example-refresh-token',
39
+ scope: c.req.query('scope') ?? 'read write',
40
+ };
41
+ // Security headers
42
+ c.header('Cache-Control', 'no-store');
43
+ c.header('Pragma', 'no-cache');
44
+ return c.json(tokenResponse);
45
+ }
46
+
47
+ export { respondWithToken };
package/dist/types.d.ts CHANGED
@@ -1,5 +1,21 @@
1
+ import type { OpenAPI } from '@scalar/openapi-types';
2
+ import { type Context } from 'hono';
1
3
  /** Available HTTP methods for Hono routes */
2
4
  export declare const httpMethods: readonly ["get", "put", "post", "delete", "options", "patch"];
3
5
  /** Valid HTTP method */
4
6
  export type HttpMethod = (typeof httpMethods)[number];
7
+ export type MockServerOptions = {
8
+ /**
9
+ * The OpenAPI specification to use for mocking.
10
+ * Can be a string (URL or file path) or an object.
11
+ */
12
+ specification: string | Record<string, any>;
13
+ /**
14
+ * Callback function to be called before each request is processed.
15
+ */
16
+ onRequest?: (data: {
17
+ context: Context;
18
+ operation: OpenAPI.Operation;
19
+ }) => void;
20
+ };
5
21
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,6CAA6C;AAC7C,eAAO,MAAM,WAAW,+DAOd,CAAA;AAEV,wBAAwB;AACxB,MAAM,MAAM,UAAU,GAAG,CAAC,OAAO,WAAW,CAAC,CAAC,MAAM,CAAC,CAAA"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAA;AACpD,OAAO,EAAE,KAAK,OAAO,EAAQ,MAAM,MAAM,CAAA;AAEzC,6CAA6C;AAC7C,eAAO,MAAM,WAAW,+DAOd,CAAA;AAEV,wBAAwB;AACxB,MAAM,MAAM,UAAU,GAAG,CAAC,OAAO,WAAW,CAAC,CAAC,MAAM,CAAC,CAAA;AAErD,MAAM,MAAM,iBAAiB,GAAG;IAC9B;;;OAGG;IACH,aAAa,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IAE3C;;OAEG;IACH,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,SAAS,EAAE,OAAO,CAAC,SAAS,CAAA;KAAE,KAAK,IAAI,CAAA;CAC/E,CAAA"}
@@ -0,0 +1,4 @@
1
+ import type { OpenAPIV3, OpenAPIV3_1 } from '@scalar/openapi-types';
2
+ /** Helper function create an OpenAPI document with security schemss */
3
+ export declare function createOpenAPIDocument(securitySchemes: Record<string, OpenAPIV3.SecuritySchemeObject | OpenAPIV3_1.SecuritySchemeObject>): OpenAPIV3.Document;
4
+ //# sourceMappingURL=createOpenAPIDocument.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createOpenAPIDocument.d.ts","sourceRoot":"","sources":["../../src/utils/createOpenAPIDocument.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AAEnE,uEAAuE;AACvE,wBAAgB,qBAAqB,CACnC,eAAe,EAAE,MAAM,CACrB,MAAM,EACN,SAAS,CAAC,oBAAoB,GAAG,WAAW,CAAC,oBAAoB,CAClE,GACA,SAAS,CAAC,QAAQ,CAMpB"}
@@ -0,0 +1,7 @@
1
+ import type { OpenAPI } from '@scalar/openapi-types';
2
+ /**
3
+ * Extract path from URL
4
+ */
5
+ export declare function getPathFromUrl(url: string): string;
6
+ export declare function getOpenAuthTokenUrls(schema?: OpenAPI.Document): string[];
7
+ //# sourceMappingURL=getOpenAuthTokenUrls.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"getOpenAuthTokenUrls.d.ts","sourceRoot":"","sources":["../../src/utils/getOpenAuthTokenUrls.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAA0B,MAAM,uBAAuB,CAAA;AAE5E;;GAEG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAclD;AAiBD,wBAAgB,oBAAoB,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,QAAQ,GAAG,MAAM,EAAE,CAgCxE"}
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Extract path from URL
3
+ */
4
+ function getPathFromUrl(url) {
5
+ try {
6
+ // Handle relative URLs by prepending a base
7
+ const urlObject = url.startsWith('http')
8
+ ? new URL(url)
9
+ : new URL(url, 'http://example.com');
10
+ // Normalize: remove trailing slash except for root path
11
+ const path = urlObject.pathname;
12
+ return path === '/' ? path : path.replace(/\/$/, '');
13
+ }
14
+ catch (error) {
15
+ // If URL is invalid, return the original string
16
+ return url;
17
+ }
18
+ }
19
+ /**
20
+ * Returns all token URLs mentioned in the securitySchemes, without the domain
21
+ */
22
+ // Type guard for OAuth2 security scheme
23
+ function isOAuth2Scheme(scheme) {
24
+ return scheme.type === 'oauth2';
25
+ }
26
+ // Validate token URL
27
+ function isValidTokenUrl(url) {
28
+ return url.trim().length > 0;
29
+ }
30
+ function getOpenAuthTokenUrls(schema) {
31
+ if (!schema?.components?.securitySchemes) {
32
+ return [];
33
+ }
34
+ const securitySchemes = schema.components.securitySchemes;
35
+ // Use Set from the start for better memory efficiency
36
+ const tokenUrls = new Set();
37
+ // Iterate through all security schemes
38
+ for (const scheme of Object.values(securitySchemes)) {
39
+ if (!isOAuth2Scheme(scheme))
40
+ continue;
41
+ const flows = scheme.flows; // Type assertion no longer needed
42
+ // Helper to safely add valid token URLs
43
+ const addTokenUrl = (url) => {
44
+ if (url && isValidTokenUrl(url)) {
45
+ tokenUrls.add(getPathFromUrl(url));
46
+ }
47
+ };
48
+ addTokenUrl(flows?.password?.tokenUrl);
49
+ addTokenUrl(flows?.clientCredentials?.tokenUrl);
50
+ addTokenUrl(flows?.authorizationCode?.tokenUrl);
51
+ }
52
+ return Array.from(tokenUrls);
53
+ }
54
+
55
+ export { getOpenAuthTokenUrls, getPathFromUrl };
@@ -0,0 +1,10 @@
1
+ import type { OpenAPI } from '@scalar/openapi-types';
2
+ import type { Context } from 'hono';
3
+ /**
4
+ * Handles authentication for incoming requests based on the OpenAPI specification.
5
+ */
6
+ export declare function handleAuthentication(schema?: OpenAPI.Document, operation?: OpenAPI.Operation): (c: Context, next: () => Promise<void>) => Promise<(Response & import("hono").TypedResponse<{
7
+ error: string;
8
+ message: string;
9
+ }, 401, "json">) | undefined>;
10
+ //# sourceMappingURL=handleAuthentication.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handleAuthentication.d.ts","sourceRoot":"","sources":["../../src/utils/handleAuthentication.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAA;AACpD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAA;AAGnC;;GAEG;AACH,wBAAgB,oBAAoB,CAClC,MAAM,CAAC,EAAE,OAAO,CAAC,QAAQ,EACzB,SAAS,CAAC,EAAE,OAAO,CAAC,SAAS,OAEZ,OAAO,QAAQ,MAAM,OAAO,CAAC,IAAI,CAAC;;;8BA8GpD"}
@@ -0,0 +1,103 @@
1
+ import { getCookie } from 'hono/cookie';
2
+
3
+ /**
4
+ * Handles authentication for incoming requests based on the OpenAPI specification.
5
+ */
6
+ function handleAuthentication(schema, operation) {
7
+ return async (c, next) => {
8
+ const operationSecuritySchemes = operation?.security || schema?.security;
9
+ if (operationSecuritySchemes && operationSecuritySchemes.length > 0) {
10
+ let isAuthenticated = false;
11
+ let authScheme = '';
12
+ for (const securityRequirement of operationSecuritySchemes) {
13
+ let securitySchemeAuthenticated = true;
14
+ for (const [schemeName] of Object.entries(securityRequirement)) {
15
+ const scheme = schema?.components?.securitySchemes?.[schemeName];
16
+ if (scheme) {
17
+ switch (scheme.type) {
18
+ case 'http':
19
+ if (scheme.scheme === 'basic') {
20
+ authScheme = 'Basic';
21
+ const authHeader = c.req.header('Authorization');
22
+ if (authHeader?.startsWith('Basic ')) {
23
+ isAuthenticated = true;
24
+ }
25
+ }
26
+ else if (scheme.scheme === 'bearer') {
27
+ authScheme = 'Bearer';
28
+ const authHeader = c.req.header('Authorization');
29
+ if (authHeader?.startsWith('Bearer ')) {
30
+ isAuthenticated = true;
31
+ }
32
+ }
33
+ break;
34
+ case 'apiKey':
35
+ authScheme = `ApiKey ${scheme.name}`;
36
+ if (scheme.in === 'header') {
37
+ const apiKey = c.req.header(scheme.name);
38
+ if (apiKey) {
39
+ isAuthenticated = true;
40
+ }
41
+ }
42
+ else if (scheme.in === 'query') {
43
+ const apiKey = c.req.query(scheme.name);
44
+ if (apiKey) {
45
+ isAuthenticated = true;
46
+ }
47
+ }
48
+ else if (scheme.in === 'cookie') {
49
+ const apiKey = getCookie(c, scheme.name);
50
+ if (apiKey) {
51
+ isAuthenticated = true;
52
+ }
53
+ }
54
+ break;
55
+ case 'oauth2':
56
+ authScheme = 'Bearer';
57
+ // Handle OAuth 2.0 flows, including password grant
58
+ if (c.req.header('Authorization')?.startsWith('Bearer ')) {
59
+ isAuthenticated = true;
60
+ }
61
+ break;
62
+ }
63
+ }
64
+ if (!isAuthenticated) {
65
+ securitySchemeAuthenticated = false;
66
+ break;
67
+ }
68
+ }
69
+ if (securitySchemeAuthenticated) {
70
+ isAuthenticated = true;
71
+ break;
72
+ }
73
+ }
74
+ if (!isAuthenticated) {
75
+ let wwwAuthenticateValue = authScheme;
76
+ switch (authScheme) {
77
+ case 'Basic':
78
+ wwwAuthenticateValue +=
79
+ ' realm="Scalar Mock Server", charset="UTF-8"';
80
+ break;
81
+ case 'Bearer':
82
+ wwwAuthenticateValue +=
83
+ ' realm="Scalar Mock Server", error="invalid_token", error_description="The access token is invalid or has expired"';
84
+ break;
85
+ case 'ApiKey':
86
+ wwwAuthenticateValue += ` realm="Scalar Mock Server", error="invalid_token", error_description="Invalid or missing API key"`;
87
+ break;
88
+ default:
89
+ wwwAuthenticateValue = 'Bearer realm="Scalar Mock Server"';
90
+ }
91
+ c.header('WWW-Authenticate', wwwAuthenticateValue);
92
+ return c.json({
93
+ error: 'Unauthorized',
94
+ message: 'Authentication is required to access this resource.',
95
+ }, 401);
96
+ }
97
+ }
98
+ // If all checks pass, continue to the next middleware
99
+ await next();
100
+ };
101
+ }
102
+
103
+ export { handleAuthentication };
@@ -1,12 +1,6 @@
1
- export * from './anyBasicAuthentication.js';
2
- export * from './anyOpenAuthPasswordGrantAuthentication.js';
3
- export * from './findPreferredResponseKey.test';
4
1
  export * from './findPreferredResponseKey.js';
5
- export * from './getOpenAuthTokenUrl.js';
2
+ export * from './getOpenAuthTokenUrls.js';
6
3
  export * from './getOperations.js';
7
- export * from './honoRouteFromPath.test';
8
4
  export * from './honoRouteFromPath.js';
9
5
  export * from './isAuthenticationRequired.js';
10
- export * from './isBasicAuthenticationRequired.js';
11
- export * from './isOpenAuthPasswordGrantRequired.js';
12
6
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,0BAA0B,CAAA;AACxC,cAAc,0CAA0C,CAAA;AACxD,cAAc,iCAAiC,CAAA;AAC/C,cAAc,4BAA4B,CAAA;AAC1C,cAAc,uBAAuB,CAAA;AACrC,cAAc,iBAAiB,CAAA;AAC/B,cAAc,0BAA0B,CAAA;AACxC,cAAc,qBAAqB,CAAA;AACnC,cAAc,4BAA4B,CAAA;AAC1C,cAAc,iCAAiC,CAAA;AAC/C,cAAc,mCAAmC,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,4BAA4B,CAAA;AAC1C,cAAc,wBAAwB,CAAA;AACtC,cAAc,iBAAiB,CAAA;AAC/B,cAAc,qBAAqB,CAAA;AACnC,cAAc,4BAA4B,CAAA"}
@@ -0,0 +1,6 @@
1
+ import type { OpenAPIV3_1 } from '@scalar/openapi-types';
2
+ /**
3
+ * Log authentication instructions for different security schemes
4
+ */
5
+ export declare function logAuthenticationInstructions(securitySchemes: Record<string, OpenAPIV3_1.SecuritySchemeObject>): void;
6
+ //# sourceMappingURL=logAuthenticationInstructions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logAuthenticationInstructions.d.ts","sourceRoot":"","sources":["../../src/utils/logAuthenticationInstructions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AAIxD;;GAEG;AACH,wBAAgB,6BAA6B,CAC3C,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,oBAAoB,CAAC,QAoIlE"}
@@ -0,0 +1,112 @@
1
+ import { getPathFromUrl } from './getOpenAuthTokenUrls.js';
2
+
3
+ /**
4
+ * Log authentication instructions for different security schemes
5
+ */
6
+ function logAuthenticationInstructions(securitySchemes) {
7
+ if (!securitySchemes || Object.keys(securitySchemes).length === 0) {
8
+ return;
9
+ }
10
+ console.log('Authentication:');
11
+ console.log();
12
+ Object.entries(securitySchemes).forEach(([_, scheme]) => {
13
+ switch (scheme.type) {
14
+ case 'apiKey':
15
+ if (scheme.in === 'header') {
16
+ console.log('✅ API Key Authentication');
17
+ console.log(` Use any API key in the ${scheme.name} header`);
18
+ console.log();
19
+ console.log(` ${scheme.name}: YOUR_API_KEY_HERE`);
20
+ console.log();
21
+ }
22
+ else if (scheme.in === 'query') {
23
+ console.log(`✅ API Key Authentication`);
24
+ console.log(` Use any API key in the ${scheme.name} query parameter:`);
25
+ console.log();
26
+ console.log(` ?${scheme.name}=YOUR_API_KEY_HERE`);
27
+ console.log();
28
+ }
29
+ else if (scheme.in === 'cookie') {
30
+ console.log(`✅ API Key Authentication`);
31
+ console.log(` Use any API key in the ${scheme.name} cookie:`);
32
+ console.log();
33
+ console.log(` Cookie: ${scheme.name}=YOUR_API_KEY_HERE`);
34
+ console.log();
35
+ }
36
+ else {
37
+ console.error(`❌ Unsupported API Key Location: ${scheme.in}`);
38
+ }
39
+ break;
40
+ case 'http':
41
+ if (scheme.scheme === 'basic') {
42
+ console.log('✅ HTTP Basic Authentication');
43
+ console.log(' Use an Authorization header with any credentials ("username:password" in base64):');
44
+ console.log();
45
+ console.log(' Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=');
46
+ console.log();
47
+ }
48
+ else if (scheme.scheme === 'bearer') {
49
+ console.log('✅ Bearer Token Authentication');
50
+ console.log(' Use an Authorization header with any bearer token');
51
+ console.log();
52
+ console.log(' Authorization: Bearer YOUR_TOKEN_HERE');
53
+ console.log();
54
+ }
55
+ else {
56
+ console.error('❌ Unknown Security Scheme:', scheme);
57
+ }
58
+ break;
59
+ case 'oauth2':
60
+ if (scheme.flows) {
61
+ Object.keys(scheme.flows).forEach((flow) => {
62
+ switch (flow) {
63
+ case 'implicit':
64
+ console.log('✅ OAuth 2.0 Implicit Flow');
65
+ console.log(' Use the following URL to initiate the OAuth 2.0 Implicit Flow:');
66
+ console.log();
67
+ console.log(` GET ${scheme?.flows?.implicit?.authorizationUrl || '/oauth/authorize'}?response_type=token&client_id=YOUR_CLIENT_ID&redirect_uri=YOUR_REDIRECT_URI&scope=YOUR_SCOPES`);
68
+ console.log();
69
+ break;
70
+ case 'password':
71
+ console.log('✅ OAuth 2.0 Password Flow');
72
+ console.log(' Use the following URL to obtain an access token:');
73
+ console.log();
74
+ console.log(` POST ${getPathFromUrl(scheme?.flows?.password?.tokenUrl || '/oauth/token')}`);
75
+ console.log(' Content-Type: application/x-www-form-urlencoded');
76
+ console.log();
77
+ console.log(' grant_type=password&username=YOUR_USERNAME&password=YOUR_PASSWORD&client_id=YOUR_CLIENT_ID&client_secret=YOUR_CLIENT_SECRET');
78
+ console.log();
79
+ break;
80
+ case 'clientCredentials':
81
+ console.log('✅ OAuth 2.0 Client Credentials Flow');
82
+ console.log(' Use the following URL to obtain an access token:');
83
+ console.log();
84
+ console.log(` POST ${getPathFromUrl(scheme?.flows?.clientCredentials?.tokenUrl || '/oauth/token')}`);
85
+ console.log(' Content-Type: application/x-www-form-urlencoded');
86
+ console.log();
87
+ console.log(' grant_type=client_credentials&client_id=YOUR_CLIENT_ID&client_secret=YOUR_CLIENT_SECRET');
88
+ console.log();
89
+ break;
90
+ case 'authorizationCode':
91
+ console.log('✅ OAuth 2.0 Authorization Code Flow');
92
+ console.log(' Use the following URL to initiate the OAuth 2.0 Authorization Code Flow:');
93
+ console.log();
94
+ console.log(' GET', `${getPathFromUrl(scheme?.flows?.authorizationCode?.authorizationUrl || '/oauth/authorize')}?redirect_uri=https://YOUR_REDIRECT_URI_HERE`);
95
+ console.log();
96
+ break;
97
+ default:
98
+ console.warn(`Unsupported OAuth 2.0 flow: ${flow}`);
99
+ }
100
+ });
101
+ }
102
+ break;
103
+ case 'openIdConnect':
104
+ console.log('⚠️ OpenID Connect Authentication');
105
+ break;
106
+ default:
107
+ console.warn(`Unsupported security scheme type: ${scheme.type}`);
108
+ }
109
+ });
110
+ }
111
+
112
+ export { logAuthenticationInstructions };
@@ -0,0 +1,7 @@
1
+ import type { OpenAPI } from '@scalar/openapi-types';
2
+ import type { Hono } from 'hono';
3
+ /**
4
+ * Helper function to set up authentication routes for OAuth 2.0 flows
5
+ */
6
+ export declare function setupAuthenticationRoutes(app: Hono, schema?: OpenAPI.Document): void;
7
+ //# sourceMappingURL=setupAuthenticationRoutes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"setupAuthenticationRoutes.d.ts","sourceRoot":"","sources":["../../src/utils/setupAuthenticationRoutes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAA0B,MAAM,uBAAuB,CAAA;AAC5E,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAMhC;;GAEG;AACH,wBAAgB,yBAAyB,CACvC,GAAG,EAAE,IAAI,EACT,MAAM,CAAC,EAAE,OAAO,CAAC,QAAQ,QA6E1B"}
@@ -0,0 +1,64 @@
1
+ import { respondWithAuthorizePage } from '../routes/respondWithAuthorizePage.js';
2
+ import { respondWithToken } from '../routes/respondWithToken.js';
3
+ import { getOpenAuthTokenUrls, getPathFromUrl } from './getOpenAuthTokenUrls.js';
4
+
5
+ /**
6
+ * Helper function to set up authentication routes for OAuth 2.0 flows
7
+ */
8
+ function setupAuthenticationRoutes(app, schema) {
9
+ const securitySchemes = schema?.components?.securitySchemes || {};
10
+ // Set up authentication routes for OAuth 2.0 flows
11
+ getOpenAuthTokenUrls(schema).forEach((tokenUrl) => {
12
+ app.post(tokenUrl, (c) => {
13
+ return c.json({
14
+ access_token: 'super-secret-access-token',
15
+ token_type: 'Bearer',
16
+ expires_in: 3600,
17
+ refresh_token: 'example-refresh-token',
18
+ }, 200, {
19
+ /**
20
+ * When responding with an access token, the server must also include the additional
21
+ * Cache-Control: no-store HTTP header to ensure clients do not cache this request.
22
+ *
23
+ * @see https://www.oauth.com/oauth2-servers/access-tokens/access-token-response/
24
+ */
25
+ 'Cache-Control': 'no-store',
26
+ });
27
+ });
28
+ });
29
+ // Set up routes for different OAuth 2.0 flows
30
+ const authorizeUrls = new Set();
31
+ const tokenUrls = new Set();
32
+ Object.entries(securitySchemes).forEach(([_, scheme]) => {
33
+ if (scheme.type === 'oauth2') {
34
+ if (scheme.flows?.authorizationCode) {
35
+ const authorizeRoute = scheme.flows.authorizationCode.authorizationUrl ?? '/oauth/authorize';
36
+ const tokenRoute = scheme.flows.authorizationCode.tokenUrl ?? '/oauth/token';
37
+ authorizeUrls.add(getPathFromUrl(authorizeRoute));
38
+ tokenUrls.add(tokenRoute);
39
+ }
40
+ if (scheme.flows?.implicit) {
41
+ const authorizeRoute = scheme.flows.implicit.authorizationUrl ?? '/oauth/authorize';
42
+ authorizeUrls.add(getPathFromUrl(authorizeRoute));
43
+ }
44
+ if (scheme.flows?.password) {
45
+ const tokenRoute = scheme.flows.password.tokenUrl ?? '/oauth/token';
46
+ tokenUrls.add(tokenRoute);
47
+ }
48
+ if (scheme.flows?.clientCredentials) {
49
+ const tokenRoute = scheme.flows.clientCredentials.tokenUrl ?? '/oauth/token';
50
+ tokenUrls.add(tokenRoute);
51
+ }
52
+ }
53
+ });
54
+ // Set up unique authorization routes
55
+ authorizeUrls.forEach((authorizeUrl) => {
56
+ app.get(authorizeUrl, (c) => respondWithAuthorizePage(c, schema?.info?.title));
57
+ });
58
+ // Set up unique token routes
59
+ tokenUrls.forEach((tokenUrl) => {
60
+ app.post(tokenUrl, respondWithToken);
61
+ });
62
+ }
63
+
64
+ export { setupAuthenticationRoutes };
package/package.json CHANGED
@@ -16,7 +16,7 @@
16
16
  "swagger",
17
17
  "cli"
18
18
  ],
19
- "version": "0.2.63",
19
+ "version": "0.2.65",
20
20
  "engines": {
21
21
  "node": ">=18"
22
22
  },
@@ -38,15 +38,15 @@
38
38
  "dependencies": {
39
39
  "hono": "^4.6.5",
40
40
  "object-to-xml": "^2.0.0",
41
- "@scalar/oas-utils": "0.2.59",
42
- "@scalar/openapi-parser": "0.8.7",
43
- "@scalar/openapi-types": "0.1.3"
41
+ "@scalar/oas-utils": "0.2.61",
42
+ "@scalar/openapi-parser": "0.8.8",
43
+ "@scalar/openapi-types": "0.1.4"
44
44
  },
45
45
  "devDependencies": {
46
46
  "@hono/node-server": "^1.11.0",
47
47
  "@types/node": "^20.14.10",
48
- "@scalar/build-tooling": "0.1.11",
49
- "@scalar/hono-api-reference": "0.5.155"
48
+ "@scalar/hono-api-reference": "0.5.156",
49
+ "@scalar/build-tooling": "0.1.11"
50
50
  },
51
51
  "scripts": {
52
52
  "build": "scalar-build-rollup",
@@ -1,6 +0,0 @@
1
- import type { Context } from 'hono';
2
- /**
3
- * Middleware to check for any basic authentication header
4
- */
5
- export declare function anyBasicAuthentication(): (ctx: Context, next: () => Promise<void>) => Promise<void>;
6
- //# sourceMappingURL=anyBasicAuthentication.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"anyBasicAuthentication.d.ts","sourceRoot":"","sources":["../../src/utils/anyBasicAuthentication.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAA;AAGnC;;GAEG;AACH,wBAAgB,sBAAsB,UACR,OAAO,QAAQ,MAAM,OAAO,CAAC,IAAI,CAAC,mBAiB/D"}
@@ -1,25 +0,0 @@
1
- import { HTTPException } from 'hono/http-exception';
2
-
3
- /**
4
- * Middleware to check for any basic authentication header
5
- */
6
- function anyBasicAuthentication() {
7
- return async function (ctx, next) {
8
- // Check if the request has an Authorization header
9
- // Note: We don’t care *what* credentials are sent, though.
10
- if (ctx.req.header('Authorization')?.startsWith('Basic ')) {
11
- return await next();
12
- }
13
- // Unauthorized
14
- throw new HTTPException(401, {
15
- res: new Response('Unauthorized', {
16
- status: 401,
17
- headers: {
18
- 'WWW-Authenticate': 'Basic realm="Authentication Required"',
19
- },
20
- }),
21
- });
22
- };
23
- }
24
-
25
- export { anyBasicAuthentication };
@@ -1,6 +0,0 @@
1
- import type { Context } from 'hono';
2
- /**
3
- * Middleware to check for any bearer authentication header
4
- */
5
- export declare function anyOpenAuthPasswordGrantAuthentication(): (ctx: Context, next: () => Promise<void>) => Promise<void>;
6
- //# sourceMappingURL=anyOpenAuthPasswordGrantAuthentication.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"anyOpenAuthPasswordGrantAuthentication.d.ts","sourceRoot":"","sources":["../../src/utils/anyOpenAuthPasswordGrantAuthentication.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAA;AAGnC;;GAEG;AACH,wBAAgB,sCAAsC,UACxB,OAAO,QAAQ,MAAM,OAAO,CAAC,IAAI,CAAC,mBAc/D"}
@@ -1,22 +0,0 @@
1
- import { HTTPException } from 'hono/http-exception';
2
-
3
- /**
4
- * Middleware to check for any bearer authentication header
5
- */
6
- function anyOpenAuthPasswordGrantAuthentication() {
7
- return async function (ctx, next) {
8
- // Check if the request has an Authorization header
9
- // Note: We don’t care *what* credentials are sent, though.
10
- if (ctx.req.header('Authorization')?.startsWith('Bearer ')) {
11
- return await next();
12
- }
13
- // Unauthorized
14
- throw new HTTPException(401, {
15
- res: new Response('Unauthorized', {
16
- status: 401,
17
- }),
18
- });
19
- };
20
- }
21
-
22
- export { anyOpenAuthPasswordGrantAuthentication };
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=findPreferredResponseKey.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"findPreferredResponseKey.test.d.ts","sourceRoot":"","sources":["../../src/utils/findPreferredResponseKey.test.ts"],"names":[],"mappings":""}
@@ -1,3 +0,0 @@
1
- import type { OpenAPI } from '@scalar/openapi-types';
2
- export declare function getOpenAuthTokenUrl(schema?: OpenAPI.Document): string | false | undefined;
3
- //# sourceMappingURL=getOpenAuthTokenUrl.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"getOpenAuthTokenUrl.d.ts","sourceRoot":"","sources":["../../src/utils/getOpenAuthTokenUrl.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAA0B,MAAM,uBAAuB,CAAA;AAE5E,wBAAgB,mBAAmB,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,QAAQ,8BAgC5D"}
@@ -1,21 +0,0 @@
1
- function getOpenAuthTokenUrl(schema) {
2
- const securitySchemes = schema?.components?.securitySchemes;
3
- if (securitySchemes === undefined) {
4
- return false;
5
- }
6
- // TODO: Make this work with other OpenAuth workflows
7
- const openAuthPasswordGrant = Object.values(securitySchemes).filter((securityScheme) => {
8
- if (securityScheme.type === 'oauth2' &&
9
- securityScheme.flows?.password !== undefined) {
10
- return true;
11
- }
12
- return false;
13
- });
14
- if (!openAuthPasswordGrant.length) {
15
- return undefined;
16
- }
17
- // @ts-expect-error TypeScript, I know it’s there (or undefined, both is fine).
18
- return openAuthPasswordGrant[0]?.flows?.password?.tokenUrl;
19
- }
20
-
21
- export { getOpenAuthTokenUrl };
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=honoRouteFromPath.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"honoRouteFromPath.test.d.ts","sourceRoot":"","sources":["../../src/utils/honoRouteFromPath.test.ts"],"names":[],"mappings":""}
@@ -1,3 +0,0 @@
1
- import type { OpenAPI } from '@scalar/openapi-types';
2
- export declare function isBasicAuthenticationRequired(operation: OpenAPI.Operation, schema?: OpenAPI.Document): boolean;
3
- //# sourceMappingURL=isBasicAuthenticationRequired.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"isBasicAuthenticationRequired.d.ts","sourceRoot":"","sources":["../../src/utils/isBasicAuthenticationRequired.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAA0B,MAAM,uBAAuB,CAAA;AAE5E,wBAAgB,6BAA6B,CAC3C,SAAS,EAAE,OAAO,CAAC,SAAS,EAC5B,MAAM,CAAC,EAAE,OAAO,CAAC,QAAQ,WAwB1B"}
@@ -1,13 +0,0 @@
1
- function isBasicAuthenticationRequired(operation, schema) {
2
- const allowedSecuritySchemes = operation.security?.map((securityScheme) => {
3
- return Object.keys(securityScheme)[0];
4
- });
5
- // Check if one of them is HTTP Basic Auth
6
- const httpBasicAuthIsRequired = allowedSecuritySchemes?.findIndex((securitySchemeKey) => {
7
- const securityScheme = schema?.components?.securitySchemes?.[securitySchemeKey];
8
- return (securityScheme?.type === 'http' && securityScheme?.scheme === 'basic');
9
- }) >= 0;
10
- return httpBasicAuthIsRequired;
11
- }
12
-
13
- export { isBasicAuthenticationRequired };
@@ -1,3 +0,0 @@
1
- import type { OpenAPI } from '@scalar/openapi-types';
2
- export declare function isOpenAuthPasswordGrantRequired(operation: OpenAPI.Operation, schema?: OpenAPI.Document): boolean;
3
- //# sourceMappingURL=isOpenAuthPasswordGrantRequired.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"isOpenAuthPasswordGrantRequired.d.ts","sourceRoot":"","sources":["../../src/utils/isOpenAuthPasswordGrantRequired.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAA0B,MAAM,uBAAuB,CAAA;AAE5E,wBAAgB,+BAA+B,CAC7C,SAAS,EAAE,OAAO,CAAC,SAAS,EAC5B,MAAM,CAAC,EAAE,OAAO,CAAC,QAAQ,WAyB1B"}
@@ -1,14 +0,0 @@
1
- function isOpenAuthPasswordGrantRequired(operation, schema) {
2
- const allowedSecuritySchemes = operation.security?.map((securityScheme) => {
3
- return Object.keys(securityScheme)[0];
4
- });
5
- // Check if one of them is OpenAuth2 Password Grant
6
- const passwordGrantRequired = allowedSecuritySchemes?.findIndex((securitySchemeKey) => {
7
- const securityScheme = schema?.components?.securitySchemes?.[securitySchemeKey];
8
- return (securityScheme?.type === 'oauth2' &&
9
- securityScheme?.flows?.password !== undefined);
10
- }) >= 0;
11
- return passwordGrantRequired;
12
- }
13
-
14
- export { isOpenAuthPasswordGrantRequired };