@qelos/integrator-express 4.0.1 → 4.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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,43 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.rewriteSetCookieDomain = rewriteSetCookieDomain;
4
+ exports.rewriteSetCookieDomains = rewriteSetCookieDomains;
5
+ /**
6
+ * Rewrite the `Domain=` attribute of a single `Set-Cookie` header value.
7
+ *
8
+ * - If the cookie contains a `Domain=<value>` attribute (case-insensitive), its
9
+ * value is replaced with `newDomain`, with any trailing `:port` suffix
10
+ * stripped first (cookie `Domain` attributes do not include a port).
11
+ * - If `newDomain` is `undefined` (or empty), the entire `; Domain=<value>`
12
+ * segment is removed so the browser scopes the cookie to the current host.
13
+ * - If no `Domain=` attribute is present, the input is returned unchanged.
14
+ *
15
+ * All other attributes, their casing/order and the original cookie name/value
16
+ * bytes are preserved.
17
+ */
18
+ function rewriteSetCookieDomain(setCookie, newDomain) {
19
+ const domainSegment = /;\s*Domain\s*=\s*[^;]+/i;
20
+ if (!domainSegment.test(setCookie))
21
+ return setCookie;
22
+ const stripped = newDomain ? stripPort(newDomain) : '';
23
+ if (!stripped) {
24
+ return setCookie.replace(domainSegment, '');
25
+ }
26
+ return setCookie.replace(/(;\s*Domain\s*=\s*)[^;]+/i, `$1${stripped}`);
27
+ }
28
+ /**
29
+ * Apply {@link rewriteSetCookieDomain} to each entry of a `Set-Cookie` array
30
+ * (e.g. the result of `Headers.getSetCookie()`).
31
+ */
32
+ function rewriteSetCookieDomains(values, newDomain) {
33
+ return values.map((value) => rewriteSetCookieDomain(value, newDomain));
34
+ }
35
+ function stripPort(host) {
36
+ if (host.startsWith('[')) {
37
+ const end = host.indexOf(']');
38
+ return end === -1 ? host : host.slice(1, end);
39
+ }
40
+ const colon = host.indexOf(':');
41
+ return colon === -1 ? host : host.slice(0, colon);
42
+ }
43
+ //# sourceMappingURL=cookies.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cookies.js","sourceRoot":"","sources":["../src/cookies.ts"],"names":[],"mappings":";;AAaA,wDAeC;AAMD,0DAKC;AAvCD;;;;;;;;;;;;GAYG;AACH,SAAgB,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,SAAgB,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/index.d.ts CHANGED
@@ -1,6 +1,33 @@
1
+ import type { RequestHandler } from 'express';
2
+ import { type CreateMiddlewareOptions } from './middleware';
1
3
  export { createQelosMiddleware, requireUser, } from './middleware';
2
4
  export type { CreateMiddlewareOptions, QelosMiddleware, } from './middleware';
5
+ export { createQelosProxy } from './proxy';
6
+ export type { CreateProxyOptions, QelosProxyHandler } from './proxy';
3
7
  export { createRequestSdk } from './sdk-factory';
4
8
  export type { CreateSdkParams } from './sdk-factory';
5
- export type { QelosExpressConfig, QelosRequestContext, QelosTokenPair, ResolvedTokens, TokenRefreshContext, TokenRefreshHook, } from './types';
6
- export { completeSocialAuthCallback, applySocialAuthCookiesToServerResponse, getSocialAuthSetCookieParts, parseSocialCallbackRefreshToken, type SocialAuthCallbackPayload, type SocialCallbackInput, } from './social-auth';
9
+ export { rewriteSetCookieDomain, rewriteSetCookieDomains } from './cookies';
10
+ export { resolveQelosProxyTarget } from './proxy-target';
11
+ export type { QelosExpressConfig, QelosRequestContext, } from './types';
12
+ export { completeSocialAuthCallback, getSocialAuthSetCookieParts, parseSocialCallbackRefreshToken, type SocialAuthCallbackPayload, type SocialCallbackInput, } from './social-auth';
13
+ export interface QelosIntegrator {
14
+ /**
15
+ * User-resolution middleware. Forwards inbound cookies to `/api/me` and
16
+ * exposes `req.qelos.user` / `req.qelos.workspace` / `req.qelos.workspaces`
17
+ * / `req.qelos.sdk`.
18
+ */
19
+ middleware: RequestHandler;
20
+ /**
21
+ * Catch-all reverse-proxy handler for `/api/**`. Mount it AFTER your own
22
+ * `/api/*` routes so user-defined endpoints still take precedence. Returns
23
+ * `null` when `config.disableProxy === true`.
24
+ */
25
+ proxy: RequestHandler | null;
26
+ }
27
+ /**
28
+ * Build both the user-resolution middleware and the `/api/**` reverse proxy
29
+ * from a single config. When the proxy is enabled (the default), `/api/` is
30
+ * auto-prepended to `skipPaths` so the middleware doesn't shadow the proxy
31
+ * by running its own `/api/me` probe on every proxied request.
32
+ */
33
+ export declare function createQelosIntegrator(options: CreateMiddlewareOptions): QelosIntegrator;
package/dist/index.js CHANGED
@@ -1,14 +1,43 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.parseSocialCallbackRefreshToken = exports.getSocialAuthSetCookieParts = exports.applySocialAuthCookiesToServerResponse = exports.completeSocialAuthCallback = exports.createRequestSdk = exports.requireUser = exports.createQelosMiddleware = void 0;
4
- var middleware_1 = require("./middleware");
5
- Object.defineProperty(exports, "createQelosMiddleware", { enumerable: true, get: function () { return middleware_1.createQelosMiddleware; } });
6
- Object.defineProperty(exports, "requireUser", { enumerable: true, get: function () { return middleware_1.requireUser; } });
3
+ exports.parseSocialCallbackRefreshToken = exports.getSocialAuthSetCookieParts = exports.completeSocialAuthCallback = exports.resolveQelosProxyTarget = exports.rewriteSetCookieDomains = exports.rewriteSetCookieDomain = exports.createRequestSdk = exports.createQelosProxy = exports.requireUser = exports.createQelosMiddleware = void 0;
4
+ exports.createQelosIntegrator = createQelosIntegrator;
5
+ const middleware_1 = require("./middleware");
6
+ const proxy_1 = require("./proxy");
7
+ var middleware_2 = require("./middleware");
8
+ Object.defineProperty(exports, "createQelosMiddleware", { enumerable: true, get: function () { return middleware_2.createQelosMiddleware; } });
9
+ Object.defineProperty(exports, "requireUser", { enumerable: true, get: function () { return middleware_2.requireUser; } });
10
+ var proxy_2 = require("./proxy");
11
+ Object.defineProperty(exports, "createQelosProxy", { enumerable: true, get: function () { return proxy_2.createQelosProxy; } });
7
12
  var sdk_factory_1 = require("./sdk-factory");
8
13
  Object.defineProperty(exports, "createRequestSdk", { enumerable: true, get: function () { return sdk_factory_1.createRequestSdk; } });
14
+ var cookies_1 = require("./cookies");
15
+ Object.defineProperty(exports, "rewriteSetCookieDomain", { enumerable: true, get: function () { return cookies_1.rewriteSetCookieDomain; } });
16
+ Object.defineProperty(exports, "rewriteSetCookieDomains", { enumerable: true, get: function () { return cookies_1.rewriteSetCookieDomains; } });
17
+ var proxy_target_1 = require("./proxy-target");
18
+ Object.defineProperty(exports, "resolveQelosProxyTarget", { enumerable: true, get: function () { return proxy_target_1.resolveQelosProxyTarget; } });
9
19
  var social_auth_1 = require("./social-auth");
10
20
  Object.defineProperty(exports, "completeSocialAuthCallback", { enumerable: true, get: function () { return social_auth_1.completeSocialAuthCallback; } });
11
- Object.defineProperty(exports, "applySocialAuthCookiesToServerResponse", { enumerable: true, get: function () { return social_auth_1.applySocialAuthCookiesToServerResponse; } });
12
21
  Object.defineProperty(exports, "getSocialAuthSetCookieParts", { enumerable: true, get: function () { return social_auth_1.getSocialAuthSetCookieParts; } });
13
22
  Object.defineProperty(exports, "parseSocialCallbackRefreshToken", { enumerable: true, get: function () { return social_auth_1.parseSocialCallbackRefreshToken; } });
23
+ /**
24
+ * Build both the user-resolution middleware and the `/api/**` reverse proxy
25
+ * from a single config. When the proxy is enabled (the default), `/api/` is
26
+ * auto-prepended to `skipPaths` so the middleware doesn't shadow the proxy
27
+ * by running its own `/api/me` probe on every proxied request.
28
+ */
29
+ function createQelosIntegrator(options) {
30
+ const proxyEnabled = options.config.disableProxy !== true;
31
+ let config = options.config;
32
+ if (proxyEnabled) {
33
+ const existingSkipPaths = options.config.skipPaths ?? [];
34
+ const alreadyCovered = existingSkipPaths.some((prefix) => '/api/'.startsWith(prefix));
35
+ if (!alreadyCovered) {
36
+ config = { ...options.config, skipPaths: ['/api/', ...existingSkipPaths] };
37
+ }
38
+ }
39
+ const middleware = (0, middleware_1.createQelosMiddleware)({ ...options, config });
40
+ const proxy = proxyEnabled ? (0, proxy_1.createQelosProxy)({ config }) : null;
41
+ return { middleware, proxy };
42
+ }
14
43
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,2CAGsB;AAFpB,mHAAA,qBAAqB,OAAA;AACrB,yGAAA,WAAW,OAAA;AAOb,6CAAiD;AAAxC,+GAAA,gBAAgB,OAAA;AAYzB,6CAOuB;AANrB,yHAAA,0BAA0B,OAAA;AAC1B,qIAAA,sCAAsC,OAAA;AACtC,0HAAA,2BAA2B,OAAA;AAC3B,8HAAA,+BAA+B,OAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAyDA,sDAkBC;AA1ED,6CAAmF;AACnF,mCAA2C;AAG3C,2CAGsB;AAFpB,mHAAA,qBAAqB,OAAA;AACrB,yGAAA,WAAW,OAAA;AAOb,iCAA2C;AAAlC,yGAAA,gBAAgB,OAAA;AAGzB,6CAAiD;AAAxC,+GAAA,gBAAgB,OAAA;AAGzB,qCAA4E;AAAnE,iHAAA,sBAAsB,OAAA;AAAE,kHAAA,uBAAuB,OAAA;AACxD,+CAAyD;AAAhD,uHAAA,uBAAuB,OAAA;AAOhC,6CAMuB;AALrB,yHAAA,0BAA0B,OAAA;AAC1B,0HAAA,2BAA2B,OAAA;AAC3B,8HAAA,+BAA+B,OAAA;AAoBjC;;;;;GAKG;AACH,SAAgB,qBAAqB,CACnC,OAAgC;IAEhC,MAAM,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC,YAAY,KAAK,IAAI,CAAC;IAE1D,IAAI,MAAM,GAAuB,OAAO,CAAC,MAAM,CAAC;IAChD,IAAI,YAAY,EAAE,CAAC;QACjB,MAAM,iBAAiB,GAAG,OAAO,CAAC,MAAM,CAAC,SAAS,IAAI,EAAE,CAAC;QACzD,MAAM,cAAc,GAAG,iBAAiB,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,OAAO,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC;QACtF,IAAI,CAAC,cAAc,EAAE,CAAC;YACpB,MAAM,GAAG,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,SAAS,EAAE,CAAC,OAAO,EAAE,GAAG,iBAAiB,CAAC,EAAE,CAAC;QAC7E,CAAC;IACH,CAAC;IAED,MAAM,UAAU,GAAG,IAAA,kCAAqB,EAAC,EAAE,GAAG,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC;IACjE,MAAM,KAAK,GAAG,YAAY,CAAC,CAAC,CAAC,IAAA,wBAAgB,EAAC,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IAEjE,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC;AAC/B,CAAC"}
@@ -1,17 +1,13 @@
1
1
  import type { Request, RequestHandler } from 'express';
2
2
  import type { IUser } from '@qelos/sdk/dist/authentication';
3
3
  import type { IWorkspace } from '@qelos/sdk/workspaces';
4
- import type { QelosExpressConfig, TokenRefreshHook } from './types';
4
+ import type { QelosExpressConfig } from './types';
5
5
  export interface CreateMiddlewareOptions {
6
6
  config: QelosExpressConfig;
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
  req: Request;
@@ -2,56 +2,17 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.createQelosMiddleware = createQelosMiddleware;
4
4
  exports.requireUser = requireUser;
5
+ const cookies_1 = require("./cookies");
6
+ const proxy_target_1 = require("./proxy-target");
5
7
  const sdk_factory_1 = require("./sdk-factory");
6
- const DEFAULT_ACCESS_COOKIE = 'q_access_token';
7
- const DEFAULT_REFRESH_COOKIE = 'q_refresh_token';
8
- function readCookie(req, name) {
9
- // Prefer cookie-parser output when available.
10
- const parsed = req.cookies;
11
- if (parsed && typeof parsed[name] === 'string') {
12
- return parsed[name];
13
- }
14
- const header = req.headers.cookie;
15
- if (!header)
16
- return undefined;
17
- const prefix = name + '=';
18
- for (const part of header.split(';')) {
19
- const trimmed = part.trim();
20
- if (trimmed.startsWith(prefix)) {
21
- try {
22
- return decodeURIComponent(trimmed.slice(prefix.length));
23
- }
24
- catch {
25
- return trimmed.slice(prefix.length);
26
- }
27
- }
28
- }
29
- return undefined;
30
- }
31
- function readTokens(req, config) {
32
- const accessCookie = config.accessTokenCookie || DEFAULT_ACCESS_COOKIE;
33
- const refreshCookie = config.refreshTokenCookie || DEFAULT_REFRESH_COOKIE;
34
- const cookieAccess = readCookie(req, accessCookie);
35
- const cookieRefresh = readCookie(req, refreshCookie);
36
- const authHeader = req.headers.authorization;
37
- const headerAccess = authHeader && authHeader.toLowerCase().startsWith('bearer ')
38
- ? authHeader.slice(7).trim()
39
- : undefined;
40
- return {
41
- accessToken: headerAccess || cookieAccess || undefined,
42
- refreshToken: cookieRefresh || undefined,
43
- };
8
+ function shouldSkip(req, config) {
9
+ if (!config.skipPaths?.length)
10
+ return false;
11
+ const path = req.path || req.url || '';
12
+ return config.skipPaths.some((prefix) => path.startsWith(prefix));
44
13
  }
45
- function serializeCookie(name, value, secure) {
46
- const parts = [
47
- `${name}=${encodeURIComponent(value)}`,
48
- 'Path=/',
49
- 'HttpOnly',
50
- 'SameSite=Lax',
51
- ];
52
- if (secure)
53
- parts.push('Secure');
54
- return parts.join('; ');
14
+ function buildMeUrl(base) {
15
+ return `${base.replace(/\/+$/, '')}/api/me`;
55
16
  }
56
17
  function appendSetCookie(res, value) {
57
18
  const existing = res.getHeader('set-cookie');
@@ -65,58 +26,24 @@ function appendSetCookie(res, value) {
65
26
  res.setHeader('set-cookie', [value]);
66
27
  }
67
28
  }
68
- function writeTokensToCookies(res, config, tokens) {
69
- const accessCookie = config.accessTokenCookie || DEFAULT_ACCESS_COOKIE;
70
- const refreshCookie = config.refreshTokenCookie || DEFAULT_REFRESH_COOKIE;
71
- const secure = !/^http:\/\//i.test(config.appUrl);
72
- const cookieRes = res;
73
- if (typeof cookieRes.cookie === 'function') {
74
- const cookieOptions = {
75
- httpOnly: true,
76
- secure,
77
- sameSite: 'lax',
78
- path: '/',
79
- };
80
- cookieRes.cookie(accessCookie, tokens.accessToken, cookieOptions);
81
- if (tokens.refreshToken) {
82
- cookieRes.cookie(refreshCookie, tokens.refreshToken, cookieOptions);
83
- }
84
- return;
85
- }
86
- appendSetCookie(res, serializeCookie(accessCookie, tokens.accessToken, secure));
87
- if (tokens.refreshToken) {
88
- appendSetCookie(res, serializeCookie(refreshCookie, tokens.refreshToken, secure));
89
- }
90
- }
91
- function shouldSkip(req, config) {
92
- if (!config.skipPaths?.length)
93
- return false;
94
- const path = req.path || req.url || '';
95
- return config.skipPaths.some((prefix) => path.startsWith(prefix));
96
- }
97
29
  function createQelosMiddleware(options) {
98
30
  const { config, resolveWorkspace } = options;
99
- const onTokenRefresh = options.onTokenRefresh ||
100
- (async ({ res, newTokens }) => {
101
- writeTokensToCookies(res, config, newTokens);
102
- });
31
+ const sdkFetch = (config.sdkOptions?.fetch ?? globalThis.fetch);
103
32
  return async function qelosMiddleware(req, res, next) {
104
33
  if (shouldSkip(req, config)) {
105
34
  next();
106
35
  return;
107
36
  }
108
- const tokens = readTokens(req, config);
109
- const sdk = (0, sdk_factory_1.createRequestSdk)({ config, tokens, req, res, onTokenRefresh });
37
+ const sdk = (0, sdk_factory_1.createRequestSdk)({ config, req });
110
38
  const ctx = {
111
39
  user: null,
112
40
  workspace: null,
113
41
  workspaces: [],
114
42
  sdk,
115
- tokens,
116
43
  };
117
44
  req.qelos = ctx;
118
- const hasAuthMaterial = Boolean(config.apiToken || tokens.accessToken || tokens.refreshToken);
119
- if (!hasAuthMaterial) {
45
+ const base = (0, proxy_target_1.resolveQelosProxyTarget)(config);
46
+ if (!base && !config.apiToken) {
120
47
  if (config.requireAuth) {
121
48
  res.status(401).json({ code: 'UNAUTHORIZED' });
122
49
  return;
@@ -124,10 +51,44 @@ function createQelosMiddleware(options) {
124
51
  next();
125
52
  return;
126
53
  }
127
- try {
128
- ctx.user = await sdk.authentication.getLoggedInUser();
54
+ if (base) {
55
+ const headers = {};
56
+ if (req.headers.cookie)
57
+ headers.cookie = req.headers.cookie;
58
+ if (req.headers.authorization) {
59
+ headers.authorization = req.headers.authorization;
60
+ }
61
+ const originalHost = req.headers.host;
62
+ let upstream;
63
+ try {
64
+ upstream = await sdkFetch(buildMeUrl(base), {
65
+ method: 'GET',
66
+ headers,
67
+ redirect: 'manual',
68
+ });
69
+ }
70
+ catch {
71
+ if (config.requireAuth) {
72
+ res.status(401).json({ code: 'UNAUTHORIZED' });
73
+ return;
74
+ }
75
+ next();
76
+ return;
77
+ }
78
+ const setCookieValues = upstream.headers.getSetCookie?.() ?? [];
79
+ for (const value of setCookieValues) {
80
+ appendSetCookie(res, (0, cookies_1.rewriteSetCookieDomain)(value, originalHost));
81
+ }
82
+ if (upstream.ok) {
83
+ try {
84
+ ctx.user = (await upstream.json());
85
+ }
86
+ catch {
87
+ ctx.user = null;
88
+ }
89
+ }
129
90
  }
130
- catch {
91
+ if (!ctx.user) {
131
92
  if (config.requireAuth) {
132
93
  res.status(401).json({ code: 'UNAUTHORIZED' });
133
94
  return;
@@ -141,18 +102,20 @@ function createQelosMiddleware(options) {
141
102
  catch {
142
103
  ctx.workspaces = [];
143
104
  }
144
- if (ctx.user && ctx.workspaces.length) {
145
- if (resolveWorkspace) {
146
- ctx.workspace =
147
- (await resolveWorkspace({
148
- req,
149
- user: ctx.user,
150
- workspaces: ctx.workspaces,
151
- })) || null;
152
- }
153
- else {
154
- ctx.workspace = ctx.workspaces[0] || null;
155
- }
105
+ if (resolveWorkspace) {
106
+ ctx.workspace =
107
+ (await resolveWorkspace({
108
+ req,
109
+ user: ctx.user,
110
+ workspaces: ctx.workspaces,
111
+ })) || null;
112
+ }
113
+ else {
114
+ // `/api/me` carries `user.workspace` (`{ _id, name, roles }`) only when
115
+ // the user has activated a workspace. Null means the frontend must
116
+ // prompt to activate/create one — don't silently pick workspaces[0].
117
+ ctx.workspace =
118
+ ctx.user.workspace || null;
156
119
  }
157
120
  next();
158
121
  };
@@ -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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"}
@@ -0,0 +1,17 @@
1
+ import type { QelosExpressConfig } 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.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: QelosExpressConfig): string | undefined;
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolveQelosProxyTarget = resolveQelosProxyTarget;
4
+ /**
5
+ * Resolve the origin to which `/api/**` requests are proxied. The managed
6
+ * Qelos app URL (`config.appUrl`) is the proxy target — env vars are only
7
+ * dev-time overrides when the configured `appUrl` is not reachable from
8
+ * the local host.
9
+ *
10
+ * Priority (highest first):
11
+ * 1. `process.env.QELOS_PROXY_TARGET`
12
+ * 2. `process.env.QELOS_IP`
13
+ * 3. `process.env.QELOS_API_IP`
14
+ * 4. `config.appUrl`
15
+ *
16
+ * Empty / whitespace-only values are skipped, and `undefined` is returned when
17
+ * nothing is configured so the caller can surface a clear error.
18
+ */
19
+ function resolveQelosProxyTarget(config) {
20
+ const env = process.env;
21
+ const fromEnv = env.QELOS_PROXY_TARGET?.trim() ||
22
+ env.QELOS_IP?.trim() ||
23
+ env.QELOS_API_IP?.trim();
24
+ if (fromEnv)
25
+ return fromEnv;
26
+ const fromAppUrl = config.appUrl?.trim();
27
+ return fromAppUrl || undefined;
28
+ }
29
+ //# sourceMappingURL=proxy-target.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"proxy-target.js","sourceRoot":"","sources":["../src/proxy-target.ts"],"names":[],"mappings":";;AAiBA,0DAYC;AA3BD;;;;;;;;;;;;;;GAcG;AACH,SAAgB,uBAAuB,CACrC,MAA0B;IAE1B,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC;IACxB,MAAM,OAAO,GACX,GAAG,CAAC,kBAAkB,EAAE,IAAI,EAAE;QAC9B,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,16 @@
1
+ import type { RequestHandler } from 'express';
2
+ import type { QelosExpressConfig } from './types';
3
+ export interface CreateProxyOptions {
4
+ config: QelosExpressConfig;
5
+ }
6
+ /**
7
+ * Catch-all `/api/**` reverse proxy. Forwards the inbound request to the
8
+ * configured Qelos origin, streaming bodies in both directions and rewriting
9
+ * the `Domain=` attribute of every upstream `Set-Cookie` header to the
10
+ * inbound request's host so the cookie becomes first-party.
11
+ *
12
+ * Responds with `503` when no proxy target is configured. Does not proxy
13
+ * WebSocket upgrades.
14
+ */
15
+ export declare function createQelosProxy(options: CreateProxyOptions): RequestHandler;
16
+ export type QelosProxyHandler = ReturnType<typeof createQelosProxy>;
package/dist/proxy.js ADDED
@@ -0,0 +1,132 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ Object.defineProperty(exports, "__esModule", { value: true });
26
+ exports.createQelosProxy = createQelosProxy;
27
+ const http = __importStar(require("node:http"));
28
+ const https = __importStar(require("node:https"));
29
+ const node_url_1 = require("node:url");
30
+ const cookies_1 = require("./cookies");
31
+ const proxy_target_1 = require("./proxy-target");
32
+ const HOP_BY_HOP = new Set([
33
+ 'connection',
34
+ 'keep-alive',
35
+ 'proxy-authenticate',
36
+ 'proxy-authorization',
37
+ 'te',
38
+ 'trailer',
39
+ 'transfer-encoding',
40
+ 'upgrade',
41
+ 'host',
42
+ 'content-length',
43
+ ]);
44
+ function buildForwardedHeaders(source, targetHost) {
45
+ const out = {};
46
+ for (const [name, value] of Object.entries(source)) {
47
+ if (value === undefined)
48
+ continue;
49
+ if (HOP_BY_HOP.has(name.toLowerCase()))
50
+ continue;
51
+ out[name] = value;
52
+ }
53
+ out['host'] = targetHost;
54
+ return out;
55
+ }
56
+ /**
57
+ * Catch-all `/api/**` reverse proxy. Forwards the inbound request to the
58
+ * configured Qelos origin, streaming bodies in both directions and rewriting
59
+ * the `Domain=` attribute of every upstream `Set-Cookie` header to the
60
+ * inbound request's host so the cookie becomes first-party.
61
+ *
62
+ * Responds with `503` when no proxy target is configured. Does not proxy
63
+ * WebSocket upgrades.
64
+ */
65
+ function createQelosProxy(options) {
66
+ const { config } = options;
67
+ return function qelosApiProxy(req, res) {
68
+ const base = (0, proxy_target_1.resolveQelosProxyTarget)(config);
69
+ if (!base) {
70
+ res.status(503).json({
71
+ code: 'QELOS_PROXY_NOT_CONFIGURED',
72
+ message: '[@qelos/integrator-express] Qelos API proxy is not configured. ' +
73
+ 'Set config.appUrl (or QELOS_PROXY_TARGET / QELOS_IP / QELOS_API_IP in development).',
74
+ });
75
+ return;
76
+ }
77
+ let target;
78
+ try {
79
+ const baseUrl = new node_url_1.URL(base);
80
+ target = new node_url_1.URL(req.originalUrl || req.url, baseUrl);
81
+ }
82
+ catch {
83
+ res.status(502).json({
84
+ code: 'QELOS_PROXY_BAD_TARGET',
85
+ message: '[@qelos/integrator-express] Invalid proxy target URL.',
86
+ });
87
+ return;
88
+ }
89
+ const client = target.protocol === 'https:' ? https : http;
90
+ const originalHost = req.headers.host;
91
+ const forwardedHeaders = buildForwardedHeaders(req.headers, target.host);
92
+ const upstreamReq = client.request({
93
+ protocol: target.protocol,
94
+ hostname: target.hostname,
95
+ port: target.port || (target.protocol === 'https:' ? 443 : 80),
96
+ method: req.method,
97
+ path: target.pathname + target.search,
98
+ headers: forwardedHeaders,
99
+ }, (upstreamRes) => {
100
+ const status = upstreamRes.statusCode ?? 502;
101
+ for (const [name, value] of Object.entries(upstreamRes.headers)) {
102
+ if (value === undefined)
103
+ continue;
104
+ const lower = name.toLowerCase();
105
+ if (HOP_BY_HOP.has(lower))
106
+ continue;
107
+ if (lower === 'set-cookie') {
108
+ const values = Array.isArray(value) ? value : [value];
109
+ res.setHeader('set-cookie', values.map((v) => (0, cookies_1.rewriteSetCookieDomain)(v, originalHost)));
110
+ continue;
111
+ }
112
+ res.setHeader(name, value);
113
+ }
114
+ res.status(status);
115
+ upstreamRes.pipe(res);
116
+ });
117
+ upstreamReq.on('error', (err) => {
118
+ if (!res.headersSent) {
119
+ res.status(502).json({
120
+ code: 'QELOS_PROXY_UPSTREAM_ERROR',
121
+ message: err.message,
122
+ });
123
+ }
124
+ else {
125
+ res.end();
126
+ }
127
+ });
128
+ req.on('aborted', () => upstreamReq.destroy());
129
+ req.pipe(upstreamReq);
130
+ };
131
+ }
132
+ //# sourceMappingURL=proxy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"proxy.js","sourceRoot":"","sources":["../src/proxy.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AAiDA,4CA2EC;AA3HD,gDAAkC;AAClC,kDAAoC;AACpC,uCAA+B;AAE/B,uCAAmD;AACnD,iDAAyD;AAGzD,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC;IACzB,YAAY;IACZ,YAAY;IACZ,oBAAoB;IACpB,qBAAqB;IACrB,IAAI;IACJ,SAAS;IACT,mBAAmB;IACnB,SAAS;IACT,MAAM;IACN,gBAAgB;CACjB,CAAC,CAAC;AAEH,SAAS,qBAAqB,CAC5B,MAA2B,EAC3B,UAAkB;IAElB,MAAM,GAAG,GAA6B,EAAE,CAAC;IACzC,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QACnD,IAAI,KAAK,KAAK,SAAS;YAAE,SAAS;QAClC,IAAI,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;YAAE,SAAS;QACjD,GAAG,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC;IACpB,CAAC;IACD,GAAG,CAAC,MAAM,CAAC,GAAG,UAAU,CAAC;IACzB,OAAO,GAAG,CAAC;AACb,CAAC;AAMD;;;;;;;;GAQG;AACH,SAAgB,gBAAgB,CAAC,OAA2B;IAC1D,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC;IAE3B,OAAO,SAAS,aAAa,CAAC,GAAY,EAAE,GAAa;QACvD,MAAM,IAAI,GAAG,IAAA,sCAAuB,EAAC,MAAM,CAAC,CAAC;QAC7C,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;gBACnB,IAAI,EAAE,4BAA4B;gBAClC,OAAO,EACL,iEAAiE;oBACjE,qFAAqF;aACxF,CAAC,CAAC;YACH,OAAO;QACT,CAAC;QAED,IAAI,MAAW,CAAC;QAChB,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,IAAI,cAAG,CAAC,IAAI,CAAC,CAAC;YAC9B,MAAM,GAAG,IAAI,cAAG,CAAC,GAAG,CAAC,WAAW,IAAI,GAAG,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;QACxD,CAAC;QAAC,MAAM,CAAC;YACP,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;gBACnB,IAAI,EAAE,wBAAwB;gBAC9B,OAAO,EAAE,uDAAuD;aACjE,CAAC,CAAC;YACH,OAAO;QACT,CAAC;QAED,MAAM,MAAM,GAAG,MAAM,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC;QAC3D,MAAM,YAAY,GAAG,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC;QACtC,MAAM,gBAAgB,GAAG,qBAAqB,CAAC,GAAG,CAAC,OAAO,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC;QAEzE,MAAM,WAAW,GAAG,MAAM,CAAC,OAAO,CAChC;YACE,QAAQ,EAAE,MAAM,CAAC,QAAQ;YACzB,QAAQ,EAAE,MAAM,CAAC,QAAQ;YACzB,IAAI,EAAE,MAAM,CAAC,IAAI,IAAI,CAAC,MAAM,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;YAC9D,MAAM,EAAE,GAAG,CAAC,MAAM;YAClB,IAAI,EAAE,MAAM,CAAC,QAAQ,GAAG,MAAM,CAAC,MAAM;YACrC,OAAO,EAAE,gBAAgB;SAC1B,EACD,CAAC,WAAW,EAAE,EAAE;YACd,MAAM,MAAM,GAAG,WAAW,CAAC,UAAU,IAAI,GAAG,CAAC;YAC7C,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,WAAW,CAAC,OAAO,CAAC,EAAE,CAAC;gBAChE,IAAI,KAAK,KAAK,SAAS;oBAAE,SAAS;gBAClC,MAAM,KAAK,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;gBACjC,IAAI,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC;oBAAE,SAAS;gBACpC,IAAI,KAAK,KAAK,YAAY,EAAE,CAAC;oBAC3B,MAAM,MAAM,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;oBACtD,GAAG,CAAC,SAAS,CACX,YAAY,EACZ,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAA,gCAAsB,EAAC,CAAC,EAAE,YAAY,CAAC,CAAC,CAC3D,CAAC;oBACF,SAAS;gBACX,CAAC;gBACD,GAAG,CAAC,SAAS,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;YAC7B,CAAC;YACD,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;YACnB,WAAW,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACxB,CAAC,CACF,CAAC;QAEF,WAAW,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE;YAC9B,IAAI,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC;gBACrB,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;oBACnB,IAAI,EAAE,4BAA4B;oBAClC,OAAO,EAAE,GAAG,CAAC,OAAO;iBACrB,CAAC,CAAC;YACL,CAAC;iBAAM,CAAC;gBACN,GAAG,CAAC,GAAG,EAAE,CAAC;YACZ,CAAC;QACH,CAAC,CAAC,CAAC;QAEH,GAAG,CAAC,EAAE,CAAC,SAAS,EAAE,GAAG,EAAE,CAAC,WAAW,CAAC,OAAO,EAAE,CAAC,CAAC;QAC/C,GAAG,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IACxB,CAAC,CAAC;AACJ,CAAC"}
@@ -1,16 +1,8 @@
1
- import type { Request, Response } from 'express';
1
+ import type { Request } from 'express';
2
2
  import QelosSDK from '@qelos/sdk';
3
- import type { QelosExpressConfig, QelosTokenPair, TokenRefreshHook } from './types';
3
+ import type { QelosExpressConfig } from './types';
4
4
  export interface CreateSdkParams {
5
5
  config: QelosExpressConfig;
6
- /**
7
- * Tokens for the current request. The factory mutates this object in place
8
- * when a token refresh occurs, so callers can read the latest pair after
9
- * the SDK has been used.
10
- */
11
- tokens: QelosTokenPair;
12
6
  req: Request;
13
- res: Response;
14
- onTokenRefresh?: TokenRefreshHook;
15
7
  }
16
- export declare function createRequestSdk({ config, tokens, req, res, onTokenRefresh, }: CreateSdkParams): QelosSDK;
8
+ export declare function createRequestSdk({ config, req }: CreateSdkParams): QelosSDK;
@@ -5,93 +5,29 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.createRequestSdk = createRequestSdk;
7
7
  const sdk_1 = __importDefault(require("@qelos/sdk"));
8
- const NO_AUTH_URLS = new Set([
9
- '/api/token/refresh',
10
- '/api/cookie/refresh',
11
- '/api/signin',
12
- '/api/signup',
13
- ]);
14
- function createRequestSdk({ config, tokens, req, res, onTokenRefresh, }) {
15
- let sdk;
16
- let refreshInFlight = null;
8
+ function createRequestSdk({ config, req }) {
17
9
  const baseOptions = config.sdkOptions || {};
18
- async function performRefresh() {
19
- if (!tokens.refreshToken && !tokens.accessToken) {
20
- throw new Error('no refresh token available');
21
- }
22
- const previous = {
23
- accessToken: tokens.accessToken,
24
- refreshToken: tokens.refreshToken,
25
- };
26
- let refreshed;
27
- if (tokens.refreshToken) {
28
- const result = await sdk.authentication.refreshToken(tokens.refreshToken);
29
- refreshed = {
30
- accessToken: result.payload.token,
31
- refreshToken: result.payload.refreshToken,
32
- };
33
- }
34
- else {
35
- const result = await sdk.authentication.refreshCookieToken(tokens.accessToken);
36
- refreshed = {
37
- accessToken: result.payload.cookieToken,
38
- };
39
- }
40
- tokens.accessToken = refreshed.accessToken;
41
- tokens.refreshToken = refreshed.refreshToken;
42
- if (onTokenRefresh) {
43
- await onTokenRefresh({
44
- req,
45
- res,
46
- oldTokens: previous,
47
- newTokens: refreshed,
48
- sdk,
49
- });
50
- }
51
- }
52
- function ensureRefresh() {
53
- if (!refreshInFlight) {
54
- refreshInFlight = performRefresh().finally(() => {
55
- refreshInFlight = null;
56
- });
57
- }
58
- return refreshInFlight;
59
- }
60
10
  const options = {
61
11
  appUrl: config.appUrl,
62
12
  fetch: globalThis.fetch,
63
- forceRefresh: !config.apiToken,
64
13
  ...baseOptions,
65
14
  };
66
15
  if (config.apiToken) {
67
16
  options.apiToken = config.apiToken;
17
+ return new sdk_1.default(options);
68
18
  }
69
- else {
70
- if (tokens.accessToken) {
71
- options.accessToken = tokens.accessToken;
19
+ options.extraHeaders = async () => {
20
+ const headers = {};
21
+ const cookieHeader = req.headers.cookie;
22
+ if (cookieHeader) {
23
+ headers.cookie = cookieHeader;
72
24
  }
73
- if (tokens.refreshToken) {
74
- options.refreshToken = tokens.refreshToken;
25
+ const authHeader = req.headers.authorization;
26
+ if (authHeader) {
27
+ headers.authorization = authHeader;
75
28
  }
76
- options.extraHeaders = async (relativeUrl, forceRefresh) => {
77
- const headers = {};
78
- if (NO_AUTH_URLS.has(relativeUrl)) {
79
- return headers;
80
- }
81
- if (forceRefresh && tokens.refreshToken) {
82
- await ensureRefresh();
83
- }
84
- const token = sdk?.authentication?.accessToken || tokens.accessToken;
85
- if (token) {
86
- headers.authorization = 'Bearer ' + token;
87
- }
88
- return headers;
89
- };
90
- options.onFailedRefreshToken = async () => {
91
- await ensureRefresh();
92
- };
93
- }
94
- sdk = new sdk_1.default(options);
95
- return sdk;
29
+ return headers;
30
+ };
31
+ return new sdk_1.default(options);
96
32
  }
97
33
  //# sourceMappingURL=sdk-factory.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"sdk-factory.js","sourceRoot":"","sources":["../src/sdk-factory.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"sdk-factory.js","sourceRoot":"","sources":["../src/sdk-factory.ts"],"names":[],"mappings":";;;;;AAUA,4CA4BC;AArCD,qDAAkC;AASlC,SAAgB,gBAAgB,CAAC,EAAE,MAAM,EAAE,GAAG,EAAmB;IAC/D,MAAM,WAAW,GAA6B,MAAM,CAAC,UAAU,IAAI,EAAE,CAAC;IAEtE,MAAM,OAAO,GAAoB;QAC/B,MAAM,EAAE,MAAM,CAAC,MAAM;QACrB,KAAK,EAAE,UAAU,CAAC,KAA6B;QAC/C,GAAG,WAAW;KACf,CAAC;IAEF,IAAI,MAAM,CAAC,QAAQ,EAAE,CAAC;QACpB,OAAO,CAAC,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC;QACnC,OAAO,IAAI,aAAQ,CAAC,OAAO,CAAC,CAAC;IAC/B,CAAC;IAED,OAAO,CAAC,YAAY,GAAG,KAAK,IAAI,EAAE;QAChC,MAAM,OAAO,GAA2B,EAAE,CAAC;QAC3C,MAAM,YAAY,GAAG,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC;QACxC,IAAI,YAAY,EAAE,CAAC;YACjB,OAAO,CAAC,MAAM,GAAG,YAAY,CAAC;QAChC,CAAC;QACD,MAAM,UAAU,GAAG,GAAG,CAAC,OAAO,CAAC,aAAa,CAAC;QAC7C,IAAI,UAAU,EAAE,CAAC;YACf,OAAO,CAAC,aAAa,GAAG,UAAU,CAAC;QACrC,CAAC;QACD,OAAO,OAAO,CAAC;IACjB,CAAC,CAAC;IAEF,OAAO,IAAI,aAAQ,CAAC,OAAO,CAAC,CAAC;AAC/B,CAAC"}
@@ -1,10 +1,11 @@
1
+ import type { Request, Response } from 'express';
1
2
  import type QelosSDK from '@qelos/sdk';
2
3
  import type { SocialAuthCallbackPayload } from '@qelos/sdk';
3
4
  /**
4
5
  * Runs {@link QelosSDK.authentication.socialCallback} for `requestUrl` and
5
- * forwards session `Set-Cookie` headers to the Express response.
6
+ * forwards session `Set-Cookie` headers to the Express response, rewriting
7
+ * the `Domain=` attribute of each cookie to the inbound request's host so
8
+ * the upstream-issued cookies land as first-party on the integrator host.
6
9
  */
7
- export declare function completeSocialAuthCallback(sdk: QelosSDK, requestUrl: string, res: {
8
- setHeader(name: string, value: string | string[]): void;
9
- }): Promise<SocialAuthCallbackPayload>;
10
- export { applySocialAuthCookiesToServerResponse, getSocialAuthSetCookieParts, parseSocialCallbackRefreshToken, type SocialAuthCallbackPayload, type SocialCallbackInput, } from '@qelos/sdk';
10
+ export declare function completeSocialAuthCallback(sdk: QelosSDK, req: Pick<Request, 'url' | 'headers'>, res: Pick<Response, 'setHeader'>): Promise<SocialAuthCallbackPayload>;
11
+ export { getSocialAuthSetCookieParts, parseSocialCallbackRefreshToken, type SocialAuthCallbackPayload, type SocialCallbackInput, } from '@qelos/sdk';
@@ -1,19 +1,26 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.parseSocialCallbackRefreshToken = exports.getSocialAuthSetCookieParts = exports.applySocialAuthCookiesToServerResponse = void 0;
3
+ exports.parseSocialCallbackRefreshToken = exports.getSocialAuthSetCookieParts = void 0;
4
4
  exports.completeSocialAuthCallback = completeSocialAuthCallback;
5
5
  const sdk_1 = require("@qelos/sdk");
6
+ const cookies_1 = require("./cookies");
6
7
  /**
7
8
  * Runs {@link QelosSDK.authentication.socialCallback} for `requestUrl` and
8
- * forwards session `Set-Cookie` headers to the Express response.
9
+ * forwards session `Set-Cookie` headers to the Express response, rewriting
10
+ * the `Domain=` attribute of each cookie to the inbound request's host so
11
+ * the upstream-issued cookies land as first-party on the integrator host.
9
12
  */
10
- async function completeSocialAuthCallback(sdk, requestUrl, res) {
11
- const result = await sdk.authentication.socialCallback(requestUrl);
12
- (0, sdk_1.applySocialAuthCookiesToServerResponse)(res, result);
13
+ async function completeSocialAuthCallback(sdk, req, res) {
14
+ const result = await sdk.authentication.socialCallback(req.url);
15
+ const parts = (0, sdk_1.getSocialAuthSetCookieParts)(result);
16
+ if (parts.length) {
17
+ const host = req.headers.host;
18
+ const rewritten = parts.map((value) => (0, cookies_1.rewriteSetCookieDomain)(value, host));
19
+ res.setHeader('Set-Cookie', rewritten.length === 1 ? rewritten[0] : rewritten);
20
+ }
13
21
  return result;
14
22
  }
15
23
  var sdk_2 = require("@qelos/sdk");
16
- Object.defineProperty(exports, "applySocialAuthCookiesToServerResponse", { enumerable: true, get: function () { return sdk_2.applySocialAuthCookiesToServerResponse; } });
17
24
  Object.defineProperty(exports, "getSocialAuthSetCookieParts", { enumerable: true, get: function () { return sdk_2.getSocialAuthSetCookieParts; } });
18
25
  Object.defineProperty(exports, "parseSocialCallbackRefreshToken", { enumerable: true, get: function () { return sdk_2.parseSocialCallbackRefreshToken; } });
19
26
  //# sourceMappingURL=social-auth.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"social-auth.js","sourceRoot":"","sources":["../src/social-auth.ts"],"names":[],"mappings":";;;AAQA,gEAQC;AAdD,oCAAoE;AAEpE;;;GAGG;AACI,KAAK,UAAU,0BAA0B,CAC9C,GAAa,EACb,UAAkB,EAClB,GAAgE;IAEhE,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,cAAc,CAAC,cAAc,CAAC,UAAU,CAAC,CAAC;IACnE,IAAA,4CAAsC,EAAC,GAAG,EAAE,MAAM,CAAC,CAAC;IACpD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,kCAMoB;AALlB,6HAAA,sCAAsC,OAAA;AACtC,kHAAA,2BAA2B,OAAA;AAC3B,sHAAA,+BAA+B,OAAA"}
1
+ {"version":3,"file":"social-auth.js","sourceRoot":"","sources":["../src/social-auth.ts"],"names":[],"mappings":";;;AAYA,gEAaC;AAtBD,oCAAyD;AACzD,uCAAmD;AAEnD;;;;;GAKG;AACI,KAAK,UAAU,0BAA0B,CAC9C,GAAa,EACb,GAAqC,EACrC,GAAgC;IAEhC,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,cAAc,CAAC,cAAc,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAChE,MAAM,KAAK,GAAG,IAAA,iCAA2B,EAAC,MAAM,CAAC,CAAC;IAClD,IAAI,KAAK,CAAC,MAAM,EAAE,CAAC;QACjB,MAAM,IAAI,GAAG,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC;QAC9B,MAAM,SAAS,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,IAAA,gCAAsB,EAAC,KAAK,EAAE,IAAI,CAAC,CAAC,CAAC;QAC5E,GAAG,CAAC,SAAS,CAAC,YAAY,EAAE,SAAS,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;IAClF,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,kCAKoB;AAJlB,kHAAA,2BAA2B,OAAA;AAC3B,sHAAA,+BAA+B,OAAA"}
package/dist/types.d.ts CHANGED
@@ -1,25 +1,59 @@
1
- import type { Request, Response } from 'express';
2
1
  import type QelosSDK from '@qelos/sdk';
3
2
  import type { IUser } from '@qelos/sdk/dist/authentication';
4
3
  import type { IWorkspace } from '@qelos/sdk/workspaces';
5
4
  import type { QelosSDKOptions } from '@qelos/sdk/types';
6
- import type { QelosConfig, QelosContext, QelosTokenPair, ResolvedTokens } from '@qelos/global-types';
7
- export type { QelosTokenPair, ResolvedTokens } from '@qelos/global-types';
8
- export interface QelosExpressConfig extends QelosConfig {
5
+ export interface QelosExpressConfig {
6
+ /**
7
+ * Base URL of the Qelos backend (e.g. https://yourdomain.com).
8
+ */
9
+ appUrl: string;
10
+ /**
11
+ * Static API token used for service-to-service calls. When provided, no
12
+ * cookie-based authentication is performed.
13
+ */
14
+ apiToken?: string;
15
+ /**
16
+ * If true, the request is rejected (401) when the user cannot be resolved.
17
+ * Defaults to `false` — anonymous requests pass through with
18
+ * `req.qelos.user = null`.
19
+ */
20
+ requireAuth?: boolean;
21
+ /**
22
+ * Skip the middleware entirely for requests matching any of these path
23
+ * prefixes. Useful for `/api/_auth`, `/health`, etc.
24
+ */
25
+ skipPaths?: string[];
26
+ /**
27
+ * If true, the catch-all `/api/**` proxy handler is not registered when
28
+ * using `createQelosIntegrator`. Defaults to `false`.
29
+ */
30
+ disableProxy?: boolean;
9
31
  /**
10
32
  * Optional extra options merged into the per-request SDK instance.
11
33
  */
12
34
  sdkOptions?: Partial<QelosSDKOptions>;
13
35
  }
14
- export interface TokenRefreshContext {
15
- req: Request;
16
- res: Response;
17
- oldTokens: QelosTokenPair;
18
- newTokens: ResolvedTokens;
36
+ export interface QelosRequestContext {
37
+ /**
38
+ * The authenticated user, or `null` when anonymous.
39
+ */
40
+ user: IUser | null;
41
+ /**
42
+ * The active workspace for the request, or `null` when none is active /
43
+ * the user is anonymous.
44
+ */
45
+ workspace: IWorkspace | null;
46
+ /**
47
+ * The full list of workspaces the user has access to.
48
+ */
49
+ workspaces: IWorkspace[];
50
+ /**
51
+ * SDK instance bound to the current request's cookies. `extraHeaders`
52
+ * re-reads cookies live on each call, so cookie rotations applied by the
53
+ * middleware are picked up automatically.
54
+ */
19
55
  sdk: QelosSDK;
20
56
  }
21
- export type TokenRefreshHook = (ctx: TokenRefreshContext) => void | Promise<void>;
22
- export type QelosRequestContext = QelosContext<QelosSDK, IUser, IWorkspace>;
23
57
  declare module 'express' {
24
58
  interface Request {
25
59
  qelos: QelosRequestContext;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qelos/integrator-express",
3
- "version": "4.0.1",
3
+ "version": "4.1.1",
4
4
  "description": "Express middleware that identifies the Qelos user and active workspace before your route handlers run",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -47,8 +47,8 @@
47
47
  }
48
48
  },
49
49
  "dependencies": {
50
- "@qelos/global-types": "4.0.0",
51
- "@qelos/sdk": "4.0.0"
50
+ "@qelos/global-types": "4.1.1",
51
+ "@qelos/sdk": "4.1.1"
52
52
  },
53
53
  "devDependencies": {
54
54
  "@types/express": "^4.17.21",