@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 CHANGED
@@ -1,14 +1,10 @@
1
1
  # @qelos/integrator-nuxt
2
2
 
3
- Nuxt 3 integrator for [Qelos](https://qelos.io). Adds a server middleware that
4
- identifies the current user and active workspace via the
5
- [`@qelos/sdk`](https://www.npmjs.com/package/@qelos/sdk) before your own
6
- Nitro/Nuxt route handlers run, and exposes them through the H3 event context.
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` from `@qelos/sdk` or `null` when anonymous. |
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 tokens. |
85
- | `tokens` | The current access/refresh token pair (mutated on refresh). |
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
- refresh-token rotation stays on the server):
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
- ## Token refresh
121
-
122
- When the access token is rejected, the SDK tries to recover, in order:
198
+ ## Workspace resolution
123
199
 
124
- 1. The **refresh token** (`q_refresh_token`) via
125
- `sdk.authentication.refreshToken()` — issues a new access + refresh pair.
126
- 2. The **cookie token** (the access token cookie itself) via
127
- `sdk.authentication.refreshCookieToken()` — used for cookie-only sessions
128
- that do not carry a separate refresh token (e.g. social-auth flows).
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
- After a successful refresh the middleware fires the `onTokenRefresh` hook.
131
- The default implementation writes the new tokens back to the response cookies
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) || workspaces[0] || null,
164
- onTokenRefresh: async ({ event, newTokens }) => {
165
- // your own persistence — DB, signed cookie, KV, etc.
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/refresh flow:
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[];
@@ -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"}
@@ -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, TokenRefreshHook } from './types';
4
+ import type { QelosNuxtRuntimeConfig } from './types';
5
5
  export interface CreateMiddlewareOptions {
6
6
  config: QelosNuxtRuntimeConfig;
7
7
  /**
8
- * Hook invoked after a successful token refresh. The default implementation
9
- * writes the new tokens back to the response cookies.
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;
@@ -1,35 +1,7 @@
1
- import { defineEventHandler, getCookie, getRequestHeader, setCookie, createError, } from 'h3';
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 tokens = readTokens(event, config);
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 hasAuthMaterial = Boolean(config.apiToken || tokens.accessToken || tokens.refreshToken);
61
- if (!hasAuthMaterial) {
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
- try {
68
- ctx.user = await sdk.authentication.getLoggedInUser();
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
- catch (err) {
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 (ctx.user && ctx.workspaces.length) {
83
- if (resolveWorkspace) {
84
- ctx.workspace = await resolveWorkspace({
85
- event,
86
- user: ctx.user,
87
- workspaces: ctx.workspaces,
88
- }) || null;
89
- }
90
- else {
91
- ctx.workspace = ctx.workspaces[0] || null;
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
  }
@@ -1 +1 @@
1
- {"version":3,"file":"middleware.js","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"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"}
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, QelosTokenPair, ResolvedTokens, TokenRefreshContext, TokenRefreshHook, } from './types';
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',
@@ -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;AAWnB,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,GAAG,aAAa,EAAE,GAAG,OAAO,CAAC;QAExD,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,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"}
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,2 @@
1
+ declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<any>>;
2
+ export default _default;
@@ -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