@qelos/integrator-nuxt 4.0.0 → 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +112 -59
- package/dist/cookies.d.ts +19 -0
- package/dist/cookies.js +39 -0
- package/dist/cookies.js.map +1 -0
- package/dist/middleware.d.ts +4 -8
- package/dist/middleware.js +58 -54
- package/dist/middleware.js.map +1 -1
- package/dist/module.d.ts +6 -1
- package/dist/module.js +21 -1
- package/dist/module.js.map +1 -1
- package/dist/proxy-target.d.ts +17 -0
- package/dist/proxy-target.js +26 -0
- package/dist/proxy-target.js.map +1 -0
- package/dist/runtime/api-proxy.d.ts +2 -0
- package/dist/runtime/api-proxy.js +27 -0
- package/dist/runtime/api-proxy.js.map +1 -0
- package/dist/sdk-factory.d.ts +3 -10
- package/dist/sdk-factory.js +14 -70
- package/dist/sdk-factory.js.map +1 -1
- package/dist/types.d.ts +9 -29
- package/package.json +2 -2
- package/src/cookies.test.ts +112 -0
- package/src/cookies.ts +49 -0
- package/src/middleware.ts +65 -78
- package/src/module.ts +28 -5
- package/src/proxy-target.test.ts +110 -0
- package/src/proxy-target.ts +30 -0
- package/src/runtime/api-proxy.ts +36 -0
- package/src/sdk-factory.ts +17 -89
- package/src/types.ts +9 -33
package/README.md
CHANGED
|
@@ -1,14 +1,10 @@
|
|
|
1
1
|
# @qelos/integrator-nuxt
|
|
2
2
|
|
|
3
|
-
Nuxt 3 integrator for [Qelos](https://qelos.io).
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
A token-refresh hook keeps the access token fresh: when the SDK detects an
|
|
9
|
-
expired access token, it transparently refreshes it — either through the
|
|
10
|
-
request's refresh token or, for cookie-only sessions, through the access
|
|
11
|
-
cookie itself — and writes the rotated cookie pair back on the response.
|
|
3
|
+
Nuxt 3 integrator for [Qelos](https://qelos.io). It plugs into a Nuxt app's
|
|
4
|
+
Nitro server to make the Nuxt host act as a same-origin BFF for a managed
|
|
5
|
+
Qelos app: requests under `/api/**` are proxied to Qelos, and every other
|
|
6
|
+
request resolves the current user up front so your route handlers and Vue
|
|
7
|
+
components can use them directly.
|
|
12
8
|
|
|
13
9
|
## Install
|
|
14
10
|
|
|
@@ -16,6 +12,10 @@ cookie itself — and writes the rotated cookie pair back on the response.
|
|
|
16
12
|
pnpm add @qelos/integrator-nuxt
|
|
17
13
|
```
|
|
18
14
|
|
|
15
|
+
> Requires Node 18+ — the middleware uses
|
|
16
|
+
> [`Response.headers.getSetCookie()`](https://developer.mozilla.org/docs/Web/API/Headers/getSetCookie)
|
|
17
|
+
> to pipe individual upstream `Set-Cookie` headers back to the client.
|
|
18
|
+
|
|
19
19
|
## Configure
|
|
20
20
|
|
|
21
21
|
```ts
|
|
@@ -25,8 +25,6 @@ export default defineNuxtConfig({
|
|
|
25
25
|
qelos: {
|
|
26
26
|
appUrl: 'https://your-qelos-app.com',
|
|
27
27
|
// Optional — defaults shown:
|
|
28
|
-
accessTokenCookie: 'q_access_token',
|
|
29
|
-
refreshTokenCookie: 'q_refresh_token',
|
|
30
28
|
requireAuth: false,
|
|
31
29
|
skipPaths: ['/api/_auth', '/health'],
|
|
32
30
|
},
|
|
@@ -36,6 +34,83 @@ export default defineNuxtConfig({
|
|
|
36
34
|
You can also override any field at runtime via `NUXT_QELOS_*` env vars (Nuxt
|
|
37
35
|
runtime-config conventions apply).
|
|
38
36
|
|
|
37
|
+
## API proxy
|
|
38
|
+
|
|
39
|
+
The module registers a catch-all `/api/**` Nitro server handler that
|
|
40
|
+
transparently proxies any request the consuming Nuxt app does not handle
|
|
41
|
+
itself to the configured Qelos managed-app origin. This lets the Nuxt app
|
|
42
|
+
act as a same-origin BFF: `@qelos/sdk` calls from the browser, server, or
|
|
43
|
+
the Qelos web SDK can all hit `/api/...` on the Nuxt host and reach Qelos
|
|
44
|
+
without CORS or cross-site cookie pain.
|
|
45
|
+
|
|
46
|
+
User-defined `server/api/*.ts` (and `server/routes/*.ts`) routes still
|
|
47
|
+
take precedence — the proxy only catches requests no other handler matched.
|
|
48
|
+
|
|
49
|
+
### Cookie domain rewrite
|
|
50
|
+
|
|
51
|
+
The proxy forwards the incoming `Cookie` header as-is (the Qelos session
|
|
52
|
+
cookie name is treated as opaque) and forwards upstream `Set-Cookie`
|
|
53
|
+
headers back to the client, rewriting the `Domain=` attribute on every
|
|
54
|
+
upstream cookie to the inbound request's own host. That way the session
|
|
55
|
+
cookie set by Qelos is valid on the Nuxt app's domain regardless of which
|
|
56
|
+
host Qelos issues cookies from.
|
|
57
|
+
|
|
58
|
+
### Resolving the proxy target
|
|
59
|
+
|
|
60
|
+
The managed Qelos app URL (`qelos.appUrl`) is the proxy target. Env vars are
|
|
61
|
+
only dev-time overrides for when the configured `appUrl` isn't reachable from
|
|
62
|
+
the local host:
|
|
63
|
+
|
|
64
|
+
1. `NUXT_QELOS_PROXY_TARGET` env var.
|
|
65
|
+
2. `QELOS_IP` env var (dev fallback).
|
|
66
|
+
3. `QELOS_API_IP` env var (dev fallback).
|
|
67
|
+
4. `qelos.appUrl`.
|
|
68
|
+
|
|
69
|
+
If none of these are set, the handler responds with `503` so misconfiguration
|
|
70
|
+
fails loudly.
|
|
71
|
+
|
|
72
|
+
### Opting out
|
|
73
|
+
|
|
74
|
+
Set `qelos.disableProxy: true` in `nuxt.config.ts` to skip registration of
|
|
75
|
+
the proxy handler — useful when the Nuxt app implements every `/api/*`
|
|
76
|
+
route itself or terminates the proxy elsewhere (CDN, reverse proxy, etc.).
|
|
77
|
+
|
|
78
|
+
WebSocket upgrades are not proxied; route them explicitly if needed.
|
|
79
|
+
|
|
80
|
+
## Middleware
|
|
81
|
+
|
|
82
|
+
On every non-`/api/` request, the server middleware identifies the current
|
|
83
|
+
user by calling the managed Qelos app directly:
|
|
84
|
+
|
|
85
|
+
1. Resolve the upstream origin the same way the `/api/**` proxy does
|
|
86
|
+
(`NUXT_QELOS_PROXY_TARGET` → `QELOS_IP` → `QELOS_API_IP` →
|
|
87
|
+
`qelos.appUrl`).
|
|
88
|
+
2. Issue `fetch('${upstream}/api/me')` with the incoming request's `Cookie`
|
|
89
|
+
header forwarded verbatim — the Qelos session cookie name is opaque to
|
|
90
|
+
the integrator, so the whole header is piped through unchanged. Any
|
|
91
|
+
incoming `Authorization` header is forwarded too.
|
|
92
|
+
3. For every `Set-Cookie` header on the upstream response, rewrite the
|
|
93
|
+
`Domain=` attribute to the inbound request's `Host` (port stripped) and
|
|
94
|
+
append it to the outgoing response. This is how session rotations from
|
|
95
|
+
Qelos reach the browser when the managed app and the Nuxt host live on
|
|
96
|
+
different origins.
|
|
97
|
+
4. On `2xx`, parse the JSON body and expose it as
|
|
98
|
+
`event.context.qelos.user`. On any other status (or a network error),
|
|
99
|
+
leave `user = null` — and respond with `401` if `qelos.requireAuth` is
|
|
100
|
+
set.
|
|
101
|
+
|
|
102
|
+
The middleware is independent from the `/api/**` proxy: the proxy handles
|
|
103
|
+
forwarding of API calls themselves, while the middleware identifies the
|
|
104
|
+
user on every page/non-API request before your route handlers run. To
|
|
105
|
+
avoid double-hitting Qelos for `/api/me` (once for proxying, once for user
|
|
106
|
+
resolution), the module adds `/api/` to `skipPaths` automatically when the
|
|
107
|
+
proxy is enabled.
|
|
108
|
+
|
|
109
|
+
> The middleware does not attempt to strip the `Secure` attribute from
|
|
110
|
+
> rotated cookies. In local dev over plain HTTP, browsers will drop
|
|
111
|
+
> `Secure` cookies — configure the managed Qelos app to issue non-`Secure`
|
|
112
|
+
> cookies in that environment, or run the Nuxt host over HTTPS.
|
|
113
|
+
|
|
39
114
|
## Use in server routes
|
|
40
115
|
|
|
41
116
|
The recommended wrapper, `defineQelosEventHandler`, gives you a typed `qelos`
|
|
@@ -76,13 +151,16 @@ export default defineEventHandler((event) => {
|
|
|
76
151
|
|
|
77
152
|
`event.context.qelos` is typed as `QelosRequestContext`:
|
|
78
153
|
|
|
79
|
-
| field | description
|
|
80
|
-
|
|
81
|
-
| `user` | `IUser`
|
|
82
|
-
| `workspace` | The active `IWorkspace` for the request, or `null`.
|
|
83
|
-
| `workspaces` | All workspaces the user has access to.
|
|
84
|
-
| `sdk` | A request-scoped `QelosSDK` instance bound to the
|
|
85
|
-
|
|
154
|
+
| field | description |
|
|
155
|
+
|--------------|------------------------------------------------------------------|
|
|
156
|
+
| `user` | The `IUser` body returned by `/api/me`, or `null` when anonymous. |
|
|
157
|
+
| `workspace` | The active `IWorkspace` for the request, or `null`. |
|
|
158
|
+
| `workspaces` | All workspaces the user has access to. |
|
|
159
|
+
| `sdk` | A request-scoped `QelosSDK` instance bound to the live cookies. |
|
|
160
|
+
|
|
161
|
+
The `sdk` reads cookies from the current `H3Event` on every call, so any
|
|
162
|
+
session rotation piped through by the middleware is picked up automatically
|
|
163
|
+
by subsequent SDK requests in the same handler.
|
|
86
164
|
|
|
87
165
|
## Use in components / pages
|
|
88
166
|
|
|
@@ -104,7 +182,7 @@ const { user, workspace, workspaces, isAuthenticated } = useQelos();
|
|
|
104
182
|
```
|
|
105
183
|
|
|
106
184
|
For data calls from the browser, prefer hitting your own server routes (so
|
|
107
|
-
|
|
185
|
+
session cookies stay on the server):
|
|
108
186
|
|
|
109
187
|
```vue
|
|
110
188
|
<script setup lang="ts">
|
|
@@ -117,41 +195,18 @@ If you need direct browser access to the Qelos API, use
|
|
|
117
195
|
server-side SDK in this package is bound to per-request cookies and isn't
|
|
118
196
|
intended for client use.
|
|
119
197
|
|
|
120
|
-
##
|
|
121
|
-
|
|
122
|
-
When the access token is rejected, the SDK tries to recover, in order:
|
|
198
|
+
## Workspace resolution
|
|
123
199
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
200
|
+
`event.context.qelos.workspace` defaults to whatever the managed Qelos app
|
|
201
|
+
reports on `user.workspace` from `/api/me`. That field is non-null only when
|
|
202
|
+
the user has already activated a workspace on the Qelos side; when it is
|
|
203
|
+
`null`, the frontend is expected to prompt the user to either activate an
|
|
204
|
+
existing workspace or create a new one. The middleware deliberately does
|
|
205
|
+
**not** auto-pick `workspaces[0]` — that would silently put the user into
|
|
206
|
+
the wrong workspace.
|
|
129
207
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
(`HttpOnly`, `SameSite=Lax`).
|
|
133
|
-
|
|
134
|
-
### Manual cookie refresh
|
|
135
|
-
|
|
136
|
-
Long-lived integrator-hosted sessions can also call the SDK directly to
|
|
137
|
-
proactively refresh the cookie token, e.g. from a Nitro route handler:
|
|
138
|
-
|
|
139
|
-
```ts
|
|
140
|
-
// server/api/session/refresh.post.ts
|
|
141
|
-
import { defineQelosEventHandler } from '@qelos/integrator-nuxt';
|
|
142
|
-
|
|
143
|
-
export default defineQelosEventHandler(async ({ qelos }) => {
|
|
144
|
-
const result = await qelos.sdk.authentication.refreshCookieToken();
|
|
145
|
-
// result.headers['set-cookie'] — fresh cookie value to forward
|
|
146
|
-
return { user: result.payload.user };
|
|
147
|
-
});
|
|
148
|
-
```
|
|
149
|
-
|
|
150
|
-
## Custom workspace resolution / refresh hook
|
|
151
|
-
|
|
152
|
-
If the default behavior (first workspace from `sdk.workspaces.getList()` and a
|
|
153
|
-
cookie-based refresh writeback) doesn't fit your app, register your own
|
|
154
|
-
middleware instead of (or in addition to) the module's default handler:
|
|
208
|
+
To override the default (e.g. force a particular workspace per request),
|
|
209
|
+
register your own middleware:
|
|
155
210
|
|
|
156
211
|
```ts
|
|
157
212
|
// server/middleware/qelos.ts
|
|
@@ -160,12 +215,9 @@ import { createQelosMiddleware } from '@qelos/integrator-nuxt';
|
|
|
160
215
|
export default createQelosMiddleware({
|
|
161
216
|
config: useRuntimeConfig().qelos,
|
|
162
217
|
resolveWorkspace: ({ user, workspaces }) =>
|
|
163
|
-
workspaces.find(w => w._id === user.metadata?.activeWorkspace)
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
setCookie(event, 'q_access_token', newTokens.accessToken, { httpOnly: true });
|
|
167
|
-
setCookie(event, 'q_refresh_token', newTokens.refreshToken, { httpOnly: true });
|
|
168
|
-
},
|
|
218
|
+
workspaces.find(w => w._id === user.metadata?.activeWorkspace)
|
|
219
|
+
|| user.workspace
|
|
220
|
+
|| null,
|
|
169
221
|
});
|
|
170
222
|
```
|
|
171
223
|
|
|
@@ -175,7 +227,8 @@ in `nuxt.config.ts`.
|
|
|
175
227
|
## API token mode
|
|
176
228
|
|
|
177
229
|
For service-to-service deployments where every request shares one Qelos API
|
|
178
|
-
token, set `apiToken` and the middleware will skip the cookie
|
|
230
|
+
token, set `apiToken` and the middleware will skip the cookie flow entirely
|
|
231
|
+
and build an SDK that authenticates with the static token:
|
|
179
232
|
|
|
180
233
|
```ts
|
|
181
234
|
qelos: {
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rewrite the `Domain=` attribute of a single `Set-Cookie` header value.
|
|
3
|
+
*
|
|
4
|
+
* - If the cookie contains a `Domain=<value>` attribute (case-insensitive), its
|
|
5
|
+
* value is replaced with `newDomain`, with any trailing `:port` suffix
|
|
6
|
+
* stripped first (cookie `Domain` attributes do not include a port).
|
|
7
|
+
* - If `newDomain` is `undefined` (or empty), the entire `; Domain=<value>`
|
|
8
|
+
* segment is removed so the browser scopes the cookie to the current host.
|
|
9
|
+
* - If no `Domain=` attribute is present, the input is returned unchanged.
|
|
10
|
+
*
|
|
11
|
+
* All other attributes, their casing/order and the original cookie name/value
|
|
12
|
+
* bytes are preserved.
|
|
13
|
+
*/
|
|
14
|
+
export declare function rewriteSetCookieDomain(setCookie: string, newDomain: string | undefined): string;
|
|
15
|
+
/**
|
|
16
|
+
* Apply {@link rewriteSetCookieDomain} to each entry of a `Set-Cookie` array
|
|
17
|
+
* (e.g. the result of `Headers.getSetCookie()`).
|
|
18
|
+
*/
|
|
19
|
+
export declare function rewriteSetCookieDomains(values: string[], newDomain: string | undefined): string[];
|
package/dist/cookies.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rewrite the `Domain=` attribute of a single `Set-Cookie` header value.
|
|
3
|
+
*
|
|
4
|
+
* - If the cookie contains a `Domain=<value>` attribute (case-insensitive), its
|
|
5
|
+
* value is replaced with `newDomain`, with any trailing `:port` suffix
|
|
6
|
+
* stripped first (cookie `Domain` attributes do not include a port).
|
|
7
|
+
* - If `newDomain` is `undefined` (or empty), the entire `; Domain=<value>`
|
|
8
|
+
* segment is removed so the browser scopes the cookie to the current host.
|
|
9
|
+
* - If no `Domain=` attribute is present, the input is returned unchanged.
|
|
10
|
+
*
|
|
11
|
+
* All other attributes, their casing/order and the original cookie name/value
|
|
12
|
+
* bytes are preserved.
|
|
13
|
+
*/
|
|
14
|
+
export function rewriteSetCookieDomain(setCookie, newDomain) {
|
|
15
|
+
const domainSegment = /;\s*Domain\s*=\s*[^;]+/i;
|
|
16
|
+
if (!domainSegment.test(setCookie))
|
|
17
|
+
return setCookie;
|
|
18
|
+
const stripped = newDomain ? stripPort(newDomain) : '';
|
|
19
|
+
if (!stripped) {
|
|
20
|
+
return setCookie.replace(domainSegment, '');
|
|
21
|
+
}
|
|
22
|
+
return setCookie.replace(/(;\s*Domain\s*=\s*)[^;]+/i, `$1${stripped}`);
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Apply {@link rewriteSetCookieDomain} to each entry of a `Set-Cookie` array
|
|
26
|
+
* (e.g. the result of `Headers.getSetCookie()`).
|
|
27
|
+
*/
|
|
28
|
+
export function rewriteSetCookieDomains(values, newDomain) {
|
|
29
|
+
return values.map((value) => rewriteSetCookieDomain(value, newDomain));
|
|
30
|
+
}
|
|
31
|
+
function stripPort(host) {
|
|
32
|
+
if (host.startsWith('[')) {
|
|
33
|
+
const end = host.indexOf(']');
|
|
34
|
+
return end === -1 ? host : host.slice(1, end);
|
|
35
|
+
}
|
|
36
|
+
const colon = host.indexOf(':');
|
|
37
|
+
return colon === -1 ? host : host.slice(0, colon);
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=cookies.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cookies.js","sourceRoot":"","sources":["../src/cookies.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,sBAAsB,CACpC,SAAiB,EACjB,SAA6B;IAE7B,MAAM,aAAa,GAAG,yBAAyB,CAAC;IAChD,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,SAAS,CAAC;QAAE,OAAO,SAAS,CAAC;IAErD,MAAM,QAAQ,GAAG,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IACvD,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,OAAO,SAAS,CAAC,OAAO,CAAC,aAAa,EAAE,EAAE,CAAC,CAAC;IAC9C,CAAC;IACD,OAAO,SAAS,CAAC,OAAO,CACtB,2BAA2B,EAC3B,KAAK,QAAQ,EAAE,CAChB,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,uBAAuB,CACrC,MAAgB,EAChB,SAA6B;IAE7B,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,sBAAsB,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC,CAAC;AACzE,CAAC;AAED,SAAS,SAAS,CAAC,IAAY;IAC7B,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QACzB,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QAC9B,OAAO,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IAChD,CAAC;IACD,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IAChC,OAAO,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;AACpD,CAAC"}
|
package/dist/middleware.d.ts
CHANGED
|
@@ -1,17 +1,13 @@
|
|
|
1
1
|
import { type H3Event } from 'h3';
|
|
2
2
|
import type { IUser } from '@qelos/sdk/dist/authentication';
|
|
3
3
|
import type { IWorkspace } from '@qelos/sdk/workspaces';
|
|
4
|
-
import type { QelosNuxtRuntimeConfig
|
|
4
|
+
import type { QelosNuxtRuntimeConfig } from './types';
|
|
5
5
|
export interface CreateMiddlewareOptions {
|
|
6
6
|
config: QelosNuxtRuntimeConfig;
|
|
7
7
|
/**
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
|
|
11
|
-
onTokenRefresh?: TokenRefreshHook;
|
|
12
|
-
/**
|
|
13
|
-
* Resolve the active workspace for a request. Defaults to picking the first
|
|
14
|
-
* workspace returned from `sdk.workspaces.getList()`.
|
|
8
|
+
* Resolve the active workspace for a request. Defaults to whatever
|
|
9
|
+
* `/api/me` reports on `user.workspace` (the user's currently activated
|
|
10
|
+
* workspace, or `null` when none is active).
|
|
15
11
|
*/
|
|
16
12
|
resolveWorkspace?: (params: {
|
|
17
13
|
event: H3Event;
|
package/dist/middleware.js
CHANGED
|
@@ -1,35 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { appendResponseHeader, createError, defineEventHandler, getRequestHeader, } from 'h3';
|
|
2
|
+
import { rewriteSetCookieDomain } from './cookies';
|
|
3
|
+
import { resolveQelosProxyTarget } from './proxy-target';
|
|
2
4
|
import { createRequestSdk } from './sdk-factory';
|
|
3
|
-
const DEFAULT_ACCESS_COOKIE = 'q_access_token';
|
|
4
|
-
const DEFAULT_REFRESH_COOKIE = 'q_refresh_token';
|
|
5
|
-
function readTokens(event, config) {
|
|
6
|
-
const accessCookie = config.accessTokenCookie || DEFAULT_ACCESS_COOKIE;
|
|
7
|
-
const refreshCookie = config.refreshTokenCookie || DEFAULT_REFRESH_COOKIE;
|
|
8
|
-
const cookieAccess = getCookie(event, accessCookie);
|
|
9
|
-
const cookieRefresh = getCookie(event, refreshCookie);
|
|
10
|
-
const authHeader = getRequestHeader(event, 'authorization');
|
|
11
|
-
const headerAccess = authHeader && authHeader.toLowerCase().startsWith('bearer ')
|
|
12
|
-
? authHeader.slice(7).trim()
|
|
13
|
-
: undefined;
|
|
14
|
-
return {
|
|
15
|
-
accessToken: headerAccess || cookieAccess || undefined,
|
|
16
|
-
refreshToken: cookieRefresh || undefined,
|
|
17
|
-
};
|
|
18
|
-
}
|
|
19
|
-
function writeTokensToCookies(event, config, tokens) {
|
|
20
|
-
const accessCookie = config.accessTokenCookie || DEFAULT_ACCESS_COOKIE;
|
|
21
|
-
const refreshCookie = config.refreshTokenCookie || DEFAULT_REFRESH_COOKIE;
|
|
22
|
-
const cookieOptions = {
|
|
23
|
-
httpOnly: true,
|
|
24
|
-
secure: true,
|
|
25
|
-
sameSite: 'lax',
|
|
26
|
-
path: '/',
|
|
27
|
-
};
|
|
28
|
-
setCookie(event, accessCookie, tokens.accessToken, cookieOptions);
|
|
29
|
-
if (tokens.refreshToken) {
|
|
30
|
-
setCookie(event, refreshCookie, tokens.refreshToken, cookieOptions);
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
5
|
function shouldSkip(event, config) {
|
|
34
6
|
if (!config.skipPaths?.length) {
|
|
35
7
|
return false;
|
|
@@ -37,37 +9,67 @@ function shouldSkip(event, config) {
|
|
|
37
9
|
const path = event.path || event.node?.req?.url || '';
|
|
38
10
|
return config.skipPaths.some((prefix) => path.startsWith(prefix));
|
|
39
11
|
}
|
|
12
|
+
function buildMeUrl(base) {
|
|
13
|
+
return `${base.replace(/\/+$/, '')}/api/me`;
|
|
14
|
+
}
|
|
40
15
|
export function createQelosMiddleware(options) {
|
|
41
16
|
const { config, resolveWorkspace } = options;
|
|
42
|
-
const onTokenRefresh = options.onTokenRefresh
|
|
43
|
-
|| (async ({ event, newTokens }) => {
|
|
44
|
-
writeTokensToCookies(event, config, newTokens);
|
|
45
|
-
});
|
|
46
17
|
return defineEventHandler(async (event) => {
|
|
47
18
|
if (shouldSkip(event, config)) {
|
|
48
19
|
return;
|
|
49
20
|
}
|
|
50
|
-
const
|
|
51
|
-
const sdk = createRequestSdk({ config, tokens, event, onTokenRefresh });
|
|
21
|
+
const sdk = createRequestSdk({ config, event });
|
|
52
22
|
const ctx = {
|
|
53
23
|
user: null,
|
|
54
24
|
workspace: null,
|
|
55
25
|
workspaces: [],
|
|
56
26
|
sdk,
|
|
57
|
-
tokens,
|
|
58
27
|
};
|
|
59
28
|
event.context.qelos = ctx;
|
|
60
|
-
const
|
|
61
|
-
if (!
|
|
29
|
+
const base = resolveQelosProxyTarget(config);
|
|
30
|
+
if (!base && !config.apiToken) {
|
|
62
31
|
if (config.requireAuth) {
|
|
63
32
|
throw createError({ statusCode: 401, statusMessage: 'Unauthorized' });
|
|
64
33
|
}
|
|
65
34
|
return;
|
|
66
35
|
}
|
|
67
|
-
|
|
68
|
-
|
|
36
|
+
if (base) {
|
|
37
|
+
const headers = {};
|
|
38
|
+
const cookieHeader = getRequestHeader(event, 'cookie');
|
|
39
|
+
if (cookieHeader)
|
|
40
|
+
headers.cookie = cookieHeader;
|
|
41
|
+
const authHeader = getRequestHeader(event, 'authorization');
|
|
42
|
+
if (authHeader)
|
|
43
|
+
headers.authorization = authHeader;
|
|
44
|
+
const originalHost = getRequestHeader(event, 'host');
|
|
45
|
+
let response;
|
|
46
|
+
try {
|
|
47
|
+
response = await fetch(buildMeUrl(base), {
|
|
48
|
+
method: 'GET',
|
|
49
|
+
headers,
|
|
50
|
+
redirect: 'manual',
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
if (config.requireAuth) {
|
|
55
|
+
throw createError({ statusCode: 401, statusMessage: 'Unauthorized' });
|
|
56
|
+
}
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
const setCookieValues = response.headers.getSetCookie?.() ?? [];
|
|
60
|
+
for (const value of setCookieValues) {
|
|
61
|
+
appendResponseHeader(event, 'set-cookie', rewriteSetCookieDomain(value, originalHost));
|
|
62
|
+
}
|
|
63
|
+
if (response.ok) {
|
|
64
|
+
try {
|
|
65
|
+
ctx.user = (await response.json());
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
ctx.user = null;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
69
71
|
}
|
|
70
|
-
|
|
72
|
+
if (!ctx.user) {
|
|
71
73
|
if (config.requireAuth) {
|
|
72
74
|
throw createError({ statusCode: 401, statusMessage: 'Unauthorized' });
|
|
73
75
|
}
|
|
@@ -79,17 +81,19 @@ export function createQelosMiddleware(options) {
|
|
|
79
81
|
catch {
|
|
80
82
|
ctx.workspaces = [];
|
|
81
83
|
}
|
|
82
|
-
if (
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
84
|
+
if (resolveWorkspace) {
|
|
85
|
+
ctx.workspace = (await resolveWorkspace({
|
|
86
|
+
event,
|
|
87
|
+
user: ctx.user,
|
|
88
|
+
workspaces: ctx.workspaces,
|
|
89
|
+
})) || null;
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
// `/api/me` carries `user.workspace` (`{ _id, name, roles }`) only when
|
|
93
|
+
// the user has activated a workspace. Null means the frontend must
|
|
94
|
+
// prompt to activate/create one — don't silently pick workspaces[0].
|
|
95
|
+
ctx.workspace =
|
|
96
|
+
ctx.user.workspace || null;
|
|
93
97
|
}
|
|
94
98
|
});
|
|
95
99
|
}
|
package/dist/middleware.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"middleware.js","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,
|
|
1
|
+
{"version":3,"file":"middleware.js","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"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"}
|
package/dist/module.d.ts
CHANGED
|
@@ -5,6 +5,11 @@ export interface QelosNuxtModuleOptions extends QelosNuxtRuntimeConfig {
|
|
|
5
5
|
* want to call `createQelosMiddleware` yourself from your own server route.
|
|
6
6
|
*/
|
|
7
7
|
disableMiddleware?: boolean;
|
|
8
|
+
/**
|
|
9
|
+
* Disable registration of the catch-all `/api/**` proxy server handler.
|
|
10
|
+
* Defaults to `false`.
|
|
11
|
+
*/
|
|
12
|
+
disableProxy?: boolean;
|
|
8
13
|
}
|
|
9
14
|
declare const _default: import("nuxt/schema").NuxtModule<QelosNuxtModuleOptions, QelosNuxtModuleOptions, false>;
|
|
10
15
|
export default _default;
|
|
@@ -12,4 +17,4 @@ export { createQelosMiddleware } from './middleware';
|
|
|
12
17
|
export { createRequestSdk } from './sdk-factory';
|
|
13
18
|
export { defineQelosEventHandler } from './event-handler';
|
|
14
19
|
export type { DefineQelosEventHandlerOptions, QelosEventContext, QelosEventHandler, } from './event-handler';
|
|
15
|
-
export type { QelosNuxtRuntimeConfig, QelosRequestContext,
|
|
20
|
+
export type { QelosNuxtRuntimeConfig, QelosRequestContext, } from './types';
|
package/dist/module.js
CHANGED
|
@@ -15,7 +15,21 @@ export default defineNuxtModule({
|
|
|
15
15
|
throw new Error('[@qelos/integrator-nuxt] `qelos.appUrl` must be set in nuxt.config');
|
|
16
16
|
}
|
|
17
17
|
const { resolve } = createResolver(import.meta.url);
|
|
18
|
-
const { disableMiddleware, ...runtimeConfig } = options;
|
|
18
|
+
const { disableMiddleware, disableProxy, ...runtimeConfig } = options;
|
|
19
|
+
if (!disableProxy) {
|
|
20
|
+
// When the catch-all `/api/**` proxy is enabled, the user-resolution
|
|
21
|
+
// middleware MUST skip `/api/` requests. The middleware itself calls
|
|
22
|
+
// `${appUrl}/api/me` to identify the user, so without this skip
|
|
23
|
+
// every inbound `/api/me` would be both proxied AND used as the
|
|
24
|
+
// identification probe — a duplicate upstream hit and a potential
|
|
25
|
+
// cookie-rewrite loop. Users can extend `skipPaths` further but
|
|
26
|
+
// `/api/` must remain covered whenever `disableProxy` is false.
|
|
27
|
+
const existingSkipPaths = runtimeConfig.skipPaths ?? [];
|
|
28
|
+
const alreadyCovered = existingSkipPaths.some((prefix) => '/api/'.startsWith(prefix));
|
|
29
|
+
if (!alreadyCovered) {
|
|
30
|
+
runtimeConfig.skipPaths = ['/api/', ...existingSkipPaths];
|
|
31
|
+
}
|
|
32
|
+
}
|
|
19
33
|
nuxt.options.runtimeConfig.qelos = {
|
|
20
34
|
...nuxt.options.runtimeConfig.qelos,
|
|
21
35
|
...runtimeConfig,
|
|
@@ -26,6 +40,12 @@ export default defineNuxtModule({
|
|
|
26
40
|
middleware: true,
|
|
27
41
|
});
|
|
28
42
|
}
|
|
43
|
+
if (!disableProxy) {
|
|
44
|
+
addServerHandler({
|
|
45
|
+
route: '/api/**',
|
|
46
|
+
handler: resolve('./runtime/api-proxy'),
|
|
47
|
+
});
|
|
48
|
+
}
|
|
29
49
|
addImportsDir(resolve('./runtime/composables'));
|
|
30
50
|
addTypeTemplate({
|
|
31
51
|
filename: 'types/qelos-nuxt.d.ts',
|
package/dist/module.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"module.js","sourceRoot":"","sources":["../src/module.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gBAAgB,EAChB,gBAAgB,EAChB,aAAa,EACb,cAAc,EACd,eAAe,GAChB,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"module.js","sourceRoot":"","sources":["../src/module.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gBAAgB,EAChB,gBAAgB,EAChB,aAAa,EACb,cAAc,EACd,eAAe,GAChB,MAAM,WAAW,CAAC;AAgBnB,eAAe,gBAAgB,CAAyB;IACtD,IAAI,EAAE;QACJ,IAAI,EAAE,wBAAwB;QAC9B,SAAS,EAAE,OAAO;QAClB,aAAa,EAAE;YACb,IAAI,EAAE,QAAQ;SACf;KACF;IACD,QAAQ,EAAE;QACR,MAAM,EAAE,EAAE;KACX;IACD,KAAK,CAAC,OAAO,EAAE,IAAI;QACjB,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC;YACpB,MAAM,IAAI,KAAK,CAAC,oEAAoE,CAAC,CAAC;QACxF,CAAC;QAED,MAAM,EAAE,OAAO,EAAE,GAAG,cAAc,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAEpD,MAAM,EAAE,iBAAiB,EAAE,YAAY,EAAE,GAAG,aAAa,EAAE,GAAG,OAAO,CAAC;QAEtE,IAAI,CAAC,YAAY,EAAE,CAAC;YAClB,qEAAqE;YACrE,qEAAqE;YACrE,gEAAgE;YAChE,gEAAgE;YAChE,kEAAkE;YAClE,gEAAgE;YAChE,gEAAgE;YAChE,MAAM,iBAAiB,GAAG,aAAa,CAAC,SAAS,IAAI,EAAE,CAAC;YACxD,MAAM,cAAc,GAAG,iBAAiB,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,OAAO,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC;YACtF,IAAI,CAAC,cAAc,EAAE,CAAC;gBACpB,aAAa,CAAC,SAAS,GAAG,CAAC,OAAO,EAAE,GAAG,iBAAiB,CAAC,CAAC;YAC5D,CAAC;QACH,CAAC;QAED,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,KAAK,GAAG;YACjC,GAAI,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,KAA6C;YAC5E,GAAG,aAAa;SACjB,CAAC;QAEF,IAAI,CAAC,iBAAiB,EAAE,CAAC;YACvB,gBAAgB,CAAC;gBACf,OAAO,EAAE,OAAO,CAAC,0BAA0B,CAAC;gBAC5C,UAAU,EAAE,IAAI;aACjB,CAAC,CAAC;QACL,CAAC;QAED,IAAI,CAAC,YAAY,EAAE,CAAC;YAClB,gBAAgB,CAAC;gBACf,KAAK,EAAE,SAAS;gBAChB,OAAO,EAAE,OAAO,CAAC,qBAAqB,CAAC;aACxC,CAAC,CAAC;QACL,CAAC;QAED,aAAa,CAAC,OAAO,CAAC,uBAAuB,CAAC,CAAC,CAAC;QAEhD,eAAe,CAAC;YACd,QAAQ,EAAE,uBAAuB;YACjC,WAAW,EAAE,GAAG,EAAE,CAAC;;;;;;;;;;CAUxB;SACI,CAAC,CAAC;IACL,CAAC;CACF,CAAC,CAAC;AAEH,OAAO,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACjD,OAAO,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { QelosNuxtRuntimeConfig } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Resolve the origin to which `/api/**` requests are proxied. The managed
|
|
4
|
+
* Qelos app URL (`config.appUrl`) is the proxy target — env vars are only
|
|
5
|
+
* dev-time overrides when the configured `appUrl` is not reachable from
|
|
6
|
+
* the local host.
|
|
7
|
+
*
|
|
8
|
+
* Priority (highest first):
|
|
9
|
+
* 1. `process.env.NUXT_QELOS_PROXY_TARGET`
|
|
10
|
+
* 2. `process.env.QELOS_IP`
|
|
11
|
+
* 3. `process.env.QELOS_API_IP`
|
|
12
|
+
* 4. `config.appUrl`
|
|
13
|
+
*
|
|
14
|
+
* Empty / whitespace-only values are skipped, and `undefined` is returned when
|
|
15
|
+
* nothing is configured so the caller can surface a clear error.
|
|
16
|
+
*/
|
|
17
|
+
export declare function resolveQelosProxyTarget(config: QelosNuxtRuntimeConfig): string | undefined;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve the origin to which `/api/**` requests are proxied. The managed
|
|
3
|
+
* Qelos app URL (`config.appUrl`) is the proxy target — env vars are only
|
|
4
|
+
* dev-time overrides when the configured `appUrl` is not reachable from
|
|
5
|
+
* the local host.
|
|
6
|
+
*
|
|
7
|
+
* Priority (highest first):
|
|
8
|
+
* 1. `process.env.NUXT_QELOS_PROXY_TARGET`
|
|
9
|
+
* 2. `process.env.QELOS_IP`
|
|
10
|
+
* 3. `process.env.QELOS_API_IP`
|
|
11
|
+
* 4. `config.appUrl`
|
|
12
|
+
*
|
|
13
|
+
* Empty / whitespace-only values are skipped, and `undefined` is returned when
|
|
14
|
+
* nothing is configured so the caller can surface a clear error.
|
|
15
|
+
*/
|
|
16
|
+
export function resolveQelosProxyTarget(config) {
|
|
17
|
+
const env = process.env;
|
|
18
|
+
const fromEnv = env.NUXT_QELOS_PROXY_TARGET?.trim() ||
|
|
19
|
+
env.QELOS_IP?.trim() ||
|
|
20
|
+
env.QELOS_API_IP?.trim();
|
|
21
|
+
if (fromEnv)
|
|
22
|
+
return fromEnv;
|
|
23
|
+
const fromAppUrl = config.appUrl?.trim();
|
|
24
|
+
return fromAppUrl || undefined;
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=proxy-target.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"proxy-target.js","sourceRoot":"","sources":["../src/proxy-target.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,uBAAuB,CACrC,MAA8B;IAE9B,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC;IACxB,MAAM,OAAO,GACX,GAAG,CAAC,uBAAuB,EAAE,IAAI,EAAE;QACnC,GAAG,CAAC,QAAQ,EAAE,IAAI,EAAE;QACpB,GAAG,CAAC,YAAY,EAAE,IAAI,EAAE,CAAC;IAC3B,IAAI,OAAO;QAAE,OAAO,OAAO,CAAC;IAE5B,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC;IACzC,OAAO,UAAU,IAAI,SAAS,CAAC;AACjC,CAAC"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// This file is registered as a Nuxt server handler by `module.ts`. It is
|
|
2
|
+
// bundled by Nitro, so `#imports` resolves at the consuming-app build time
|
|
3
|
+
// (not at this package's `tsc` build).
|
|
4
|
+
// @ts-ignore - resolved by Nitro
|
|
5
|
+
import { useRuntimeConfig } from '#imports';
|
|
6
|
+
import { createError, defineEventHandler, getRequestHeader, proxyRequest, } from 'h3';
|
|
7
|
+
// @ts-ignore - resolved by Nitro (transitive dep of Nuxt)
|
|
8
|
+
import { joinURL } from 'ufo';
|
|
9
|
+
import { resolveQelosProxyTarget } from '../proxy-target';
|
|
10
|
+
export default defineEventHandler((event) => {
|
|
11
|
+
const config = (useRuntimeConfig().qelos || {});
|
|
12
|
+
const base = resolveQelosProxyTarget(config);
|
|
13
|
+
if (!base) {
|
|
14
|
+
throw createError({
|
|
15
|
+
statusCode: 503,
|
|
16
|
+
statusMessage: '[@qelos/integrator-nuxt] Qelos API proxy is not configured. Set qelos.appUrl (or NUXT_QELOS_PROXY_TARGET / QELOS_IP / QELOS_API_IP in development).',
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
const originalHost = getRequestHeader(event, 'host');
|
|
20
|
+
const path = event.path.split('?')[0] ?? event.path;
|
|
21
|
+
const target = joinURL(base, path);
|
|
22
|
+
return proxyRequest(event, target, {
|
|
23
|
+
fetch: globalThis.fetch,
|
|
24
|
+
cookieDomainRewrite: { '*': originalHost ?? '' },
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
//# sourceMappingURL=api-proxy.js.map
|