@qelos/integrator-express 4.0.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/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Polyform Shield License 1.0.0
2
+
3
+ Copyright (c) 2025 Velocitech LTD
4
+
5
+ Your use of this software is governed by the Polyform Shield License 1.0.0.
6
+
7
+ You may obtain a copy of the License at:
8
+ https://polyformproject.org/licenses/shield/1.0.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+
12
+ Additional Terms – Exception Notice:
13
+
14
+ The author of this software grants additional permissions beyond the Polyform Shield License 1.0.0:
15
+
16
+ ✅ All uses of the software are permitted — including modification, distribution, commercial use, and sublicensing — as long as the software is not used to create, offer, or operate any product or service that competes directly with Qelos (https://qelos.io), a no-code platform for building AI-assisted SaaS applications.
17
+
18
+ ❌ "Direct competition" includes platforms, tools, or services that enable users to visually or programmatically create SaaS applications with the assistance of artificial intelligence.
19
+
20
+ This exception does not modify the terms of the original Polyform Shield License for other users and applies only by explicit permission of the licensor.
package/README.md ADDED
@@ -0,0 +1,163 @@
1
+ # @qelos/integrator-express
2
+
3
+ Express middleware that calls the Qelos SDK to identify the current user and
4
+ their active workspace before your route handler runs, exposing them on
5
+ `req.qelos.user` / `req.qelos.workspace`.
6
+
7
+ This is the Express implementation of the Qelos integrator contract — the same
8
+ shape exposed by `@qelos/integrator-nuxt`, `@qelos/plugin-netlify-api`, etc.
9
+
10
+ ## Install
11
+
12
+ ```sh
13
+ npm install @qelos/integrator-express @qelos/sdk
14
+ # express is a peer dependency
15
+ npm install express
16
+ ```
17
+
18
+ ## Quick start
19
+
20
+ ```ts
21
+ import express from 'express';
22
+ import { createQelosMiddleware, requireUser } from '@qelos/integrator-express';
23
+
24
+ const app = express();
25
+
26
+ app.use(
27
+ createQelosMiddleware({
28
+ config: {
29
+ appUrl: process.env.QELOS_APP_URL!, // e.g. https://yourdomain.com
30
+ },
31
+ }),
32
+ );
33
+
34
+ app.get('/me', (req, res) => {
35
+ // user/workspace are null when the request is anonymous
36
+ res.json({
37
+ user: req.qelos!.user,
38
+ workspace: req.qelos!.workspace,
39
+ });
40
+ });
41
+
42
+ // Short-circuit with 401 when there is no authenticated user:
43
+ app.get(
44
+ '/private',
45
+ requireUser((req, res) => {
46
+ res.json(req.qelos!.user);
47
+ }),
48
+ );
49
+ ```
50
+
51
+ ## What the middleware does
52
+
53
+ 1. Reads the access token from `Authorization: Bearer ...` or the
54
+ `q_access_token` cookie, and the refresh token from `q_refresh_token`.
55
+ 2. Builds a per-request Qelos SDK instance bound to those tokens.
56
+ 3. Calls `sdk.authentication.getLoggedInUser()` and
57
+ `sdk.workspaces.getList()`.
58
+ 4. Picks the active workspace (first by default — override with
59
+ `resolveWorkspace`).
60
+ 5. Attaches everything to `req.qelos` and calls `next()`.
61
+
62
+ The middleware never throws for anonymous requests by default — it just leaves
63
+ `req.qelos.user` and `req.qelos.workspace` as `null`. Pass `requireAuth: true`
64
+ to short-circuit anonymous requests with `401`.
65
+
66
+ ## Token refresh
67
+
68
+ When the access token is rejected, the SDK tries to recover, in order:
69
+
70
+ 1. The **refresh token** (`q_refresh_token`) via
71
+ `sdk.authentication.refreshToken()` — issues a new access + refresh pair.
72
+ 2. The **cookie token** (the access token cookie itself) via
73
+ `sdk.authentication.refreshCookieToken()` — used for cookie-only sessions
74
+ that do not carry a separate refresh token (e.g. social-auth flows).
75
+
76
+ After a successful refresh the middleware fires the `onTokenRefresh` hook.
77
+ The default implementation writes the new tokens back to the response cookies
78
+ (`HttpOnly`, `SameSite=Lax`, `Secure` whenever `appUrl` is `https://...`).
79
+
80
+ You can supply your own — for example, to mint your own session cookie or push
81
+ the new tokens into a session store:
82
+
83
+ ```ts
84
+ app.use(
85
+ createQelosMiddleware({
86
+ config: { appUrl: process.env.QELOS_APP_URL! },
87
+ onTokenRefresh: async ({ req, res, newTokens, oldTokens }) => {
88
+ await sessionStore.rotate(req.sessionID, newTokens);
89
+ },
90
+ }),
91
+ );
92
+ ```
93
+
94
+ The hook receives `{ req, res, oldTokens, newTokens, sdk }`. Throwing aborts
95
+ the in-flight request.
96
+
97
+ ### Manual cookie refresh
98
+
99
+ Long-lived integrator-hosted sessions can also call the SDK directly to
100
+ proactively refresh the cookie token (e.g. before a navigation that hands the
101
+ session over to a downstream service):
102
+
103
+ ```ts
104
+ const result = await req.qelos!.sdk.authentication.refreshCookieToken();
105
+ // result.headers['set-cookie'] — fresh cookie value to forward
106
+ // result.payload.user — refreshed user
107
+ ```
108
+
109
+ ## Configuration
110
+
111
+ ```ts
112
+ createQelosMiddleware({
113
+ config: {
114
+ appUrl: 'https://yourdomain.com', // required
115
+
116
+ // Service-to-service: use a static API token instead of cookies/refresh.
117
+ apiToken: process.env.QELOS_API_TOKEN,
118
+
119
+ // Cookie names. Defaults shown.
120
+ accessTokenCookie: 'q_access_token',
121
+ refreshTokenCookie: 'q_refresh_token',
122
+
123
+ // Reject anonymous requests with 401. Defaults to false.
124
+ requireAuth: false,
125
+
126
+ // Skip the middleware entirely for these path prefixes.
127
+ skipPaths: ['/health', '/metrics'],
128
+
129
+ // Anything you want passed through to the per-request SDK.
130
+ sdkOptions: {},
131
+ },
132
+
133
+ // Override workspace selection. Defaults to `workspaces[0]`.
134
+ resolveWorkspace: ({ req, user, workspaces }) => {
135
+ const headerId = req.headers['x-qelos-workspace'];
136
+ return workspaces.find((w) => w._id === headerId) || workspaces[0] || null;
137
+ },
138
+ });
139
+ ```
140
+
141
+ ## TypeScript
142
+
143
+ Importing from `@qelos/integrator-express` augments the Express `Request` type
144
+ (via `declare module 'express'` and `declare module 'express-serve-static-core'`)
145
+ so `req.qelos` is typed everywhere in your app. The shape is:
146
+
147
+ ```ts
148
+ interface QelosRequestContext {
149
+ user: IUser | null;
150
+ workspace: IWorkspace | null;
151
+ workspaces: IWorkspace[];
152
+ sdk: QelosSDK; // bound to the current request's tokens
153
+ tokens: QelosTokenPair; // mutated in place when a refresh occurs
154
+ }
155
+ ```
156
+
157
+ `req.qelos` is typed as non-optional. If you use `skipPaths`, the property is
158
+ unset for skipped requests — guard with `if (req.qelos) { ... }` in those routes.
159
+
160
+ ## Requirements
161
+
162
+ - Node.js >= 18 (uses the global `fetch`).
163
+ - Express 4 or 5.
@@ -0,0 +1,6 @@
1
+ export { createQelosMiddleware, requireUser, } from './middleware';
2
+ export type { CreateMiddlewareOptions, QelosMiddleware, } from './middleware';
3
+ export { createRequestSdk } from './sdk-factory';
4
+ 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';
package/dist/index.js ADDED
@@ -0,0 +1,14 @@
1
+ "use strict";
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; } });
7
+ var sdk_factory_1 = require("./sdk-factory");
8
+ Object.defineProperty(exports, "createRequestSdk", { enumerable: true, get: function () { return sdk_factory_1.createRequestSdk; } });
9
+ var social_auth_1 = require("./social-auth");
10
+ 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
+ Object.defineProperty(exports, "getSocialAuthSetCookieParts", { enumerable: true, get: function () { return social_auth_1.getSocialAuthSetCookieParts; } });
13
+ Object.defineProperty(exports, "parseSocialCallbackRefreshToken", { enumerable: true, get: function () { return social_auth_1.parseSocialCallbackRefreshToken; } });
14
+ //# sourceMappingURL=index.js.map
@@ -0,0 +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"}
@@ -0,0 +1,29 @@
1
+ import type { Request, RequestHandler } from 'express';
2
+ import type { IUser } from '@qelos/sdk/dist/authentication';
3
+ import type { IWorkspace } from '@qelos/sdk/workspaces';
4
+ import type { QelosExpressConfig, TokenRefreshHook } from './types';
5
+ export interface CreateMiddlewareOptions {
6
+ config: QelosExpressConfig;
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()`.
15
+ */
16
+ resolveWorkspace?: (params: {
17
+ req: Request;
18
+ user: IUser;
19
+ workspaces: IWorkspace[];
20
+ }) => IWorkspace | null | Promise<IWorkspace | null>;
21
+ }
22
+ export declare function createQelosMiddleware(options: CreateMiddlewareOptions): RequestHandler;
23
+ export type QelosMiddleware = ReturnType<typeof createQelosMiddleware>;
24
+ /**
25
+ * Wrap a route handler so it only runs when `req.qelos.user` is populated.
26
+ * Otherwise responds with 401. Intended to be mounted *after*
27
+ * `createQelosMiddleware`.
28
+ */
29
+ export declare function requireUser(handler: RequestHandler): RequestHandler;
@@ -0,0 +1,174 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createQelosMiddleware = createQelosMiddleware;
4
+ exports.requireUser = requireUser;
5
+ 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
+ };
44
+ }
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('; ');
55
+ }
56
+ function appendSetCookie(res, value) {
57
+ const existing = res.getHeader('set-cookie');
58
+ if (Array.isArray(existing)) {
59
+ res.setHeader('set-cookie', [...existing, value]);
60
+ }
61
+ else if (typeof existing === 'string') {
62
+ res.setHeader('set-cookie', [existing, value]);
63
+ }
64
+ else {
65
+ res.setHeader('set-cookie', [value]);
66
+ }
67
+ }
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
+ function createQelosMiddleware(options) {
98
+ const { config, resolveWorkspace } = options;
99
+ const onTokenRefresh = options.onTokenRefresh ||
100
+ (async ({ res, newTokens }) => {
101
+ writeTokensToCookies(res, config, newTokens);
102
+ });
103
+ return async function qelosMiddleware(req, res, next) {
104
+ if (shouldSkip(req, config)) {
105
+ next();
106
+ return;
107
+ }
108
+ const tokens = readTokens(req, config);
109
+ const sdk = (0, sdk_factory_1.createRequestSdk)({ config, tokens, req, res, onTokenRefresh });
110
+ const ctx = {
111
+ user: null,
112
+ workspace: null,
113
+ workspaces: [],
114
+ sdk,
115
+ tokens,
116
+ };
117
+ req.qelos = ctx;
118
+ const hasAuthMaterial = Boolean(config.apiToken || tokens.accessToken || tokens.refreshToken);
119
+ if (!hasAuthMaterial) {
120
+ if (config.requireAuth) {
121
+ res.status(401).json({ code: 'UNAUTHORIZED' });
122
+ return;
123
+ }
124
+ next();
125
+ return;
126
+ }
127
+ try {
128
+ ctx.user = await sdk.authentication.getLoggedInUser();
129
+ }
130
+ catch {
131
+ if (config.requireAuth) {
132
+ res.status(401).json({ code: 'UNAUTHORIZED' });
133
+ return;
134
+ }
135
+ next();
136
+ return;
137
+ }
138
+ try {
139
+ ctx.workspaces = await sdk.workspaces.getList();
140
+ }
141
+ catch {
142
+ ctx.workspaces = [];
143
+ }
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
+ }
156
+ }
157
+ next();
158
+ };
159
+ }
160
+ /**
161
+ * Wrap a route handler so it only runs when `req.qelos.user` is populated.
162
+ * Otherwise responds with 401. Intended to be mounted *after*
163
+ * `createQelosMiddleware`.
164
+ */
165
+ function requireUser(handler) {
166
+ return function qelosRequireUser(req, res, next) {
167
+ if (!req.qelos || !req.qelos.user) {
168
+ res.status(401).json({ code: 'UNAUTHORIZED' });
169
+ return;
170
+ }
171
+ return handler(req, res, next);
172
+ };
173
+ }
174
+ //# sourceMappingURL=middleware.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"middleware.js","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ import type { Request, Response } from 'express';
2
+ import QelosSDK from '@qelos/sdk';
3
+ import type { QelosExpressConfig, QelosTokenPair, TokenRefreshHook } from './types';
4
+ export interface CreateSdkParams {
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
+ req: Request;
13
+ res: Response;
14
+ onTokenRefresh?: TokenRefreshHook;
15
+ }
16
+ export declare function createRequestSdk({ config, tokens, req, res, onTokenRefresh, }: CreateSdkParams): QelosSDK;
@@ -0,0 +1,97 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.createRequestSdk = createRequestSdk;
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;
17
+ 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
+ const options = {
61
+ appUrl: config.appUrl,
62
+ fetch: globalThis.fetch,
63
+ forceRefresh: !config.apiToken,
64
+ ...baseOptions,
65
+ };
66
+ if (config.apiToken) {
67
+ options.apiToken = config.apiToken;
68
+ }
69
+ else {
70
+ if (tokens.accessToken) {
71
+ options.accessToken = tokens.accessToken;
72
+ }
73
+ if (tokens.refreshToken) {
74
+ options.refreshToken = tokens.refreshToken;
75
+ }
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;
96
+ }
97
+ //# sourceMappingURL=sdk-factory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sdk-factory.js","sourceRoot":"","sources":["../src/sdk-factory.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ import type QelosSDK from '@qelos/sdk';
2
+ import type { SocialAuthCallbackPayload } from '@qelos/sdk';
3
+ /**
4
+ * Runs {@link QelosSDK.authentication.socialCallback} for `requestUrl` and
5
+ * forwards session `Set-Cookie` headers to the Express response.
6
+ */
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';
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parseSocialCallbackRefreshToken = exports.getSocialAuthSetCookieParts = exports.applySocialAuthCookiesToServerResponse = void 0;
4
+ exports.completeSocialAuthCallback = completeSocialAuthCallback;
5
+ const sdk_1 = require("@qelos/sdk");
6
+ /**
7
+ * Runs {@link QelosSDK.authentication.socialCallback} for `requestUrl` and
8
+ * forwards session `Set-Cookie` headers to the Express response.
9
+ */
10
+ async function completeSocialAuthCallback(sdk, requestUrl, res) {
11
+ const result = await sdk.authentication.socialCallback(requestUrl);
12
+ (0, sdk_1.applySocialAuthCookiesToServerResponse)(res, result);
13
+ return result;
14
+ }
15
+ var sdk_2 = require("@qelos/sdk");
16
+ Object.defineProperty(exports, "applySocialAuthCookiesToServerResponse", { enumerable: true, get: function () { return sdk_2.applySocialAuthCookiesToServerResponse; } });
17
+ Object.defineProperty(exports, "getSocialAuthSetCookieParts", { enumerable: true, get: function () { return sdk_2.getSocialAuthSetCookieParts; } });
18
+ Object.defineProperty(exports, "parseSocialCallbackRefreshToken", { enumerable: true, get: function () { return sdk_2.parseSocialCallbackRefreshToken; } });
19
+ //# sourceMappingURL=social-auth.js.map
@@ -0,0 +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"}
@@ -0,0 +1,32 @@
1
+ import type { Request, Response } from 'express';
2
+ import type QelosSDK from '@qelos/sdk';
3
+ import type { IUser } from '@qelos/sdk/dist/authentication';
4
+ import type { IWorkspace } from '@qelos/sdk/workspaces';
5
+ 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 {
9
+ /**
10
+ * Optional extra options merged into the per-request SDK instance.
11
+ */
12
+ sdkOptions?: Partial<QelosSDKOptions>;
13
+ }
14
+ export interface TokenRefreshContext {
15
+ req: Request;
16
+ res: Response;
17
+ oldTokens: QelosTokenPair;
18
+ newTokens: ResolvedTokens;
19
+ sdk: QelosSDK;
20
+ }
21
+ export type TokenRefreshHook = (ctx: TokenRefreshContext) => void | Promise<void>;
22
+ export type QelosRequestContext = QelosContext<QelosSDK, IUser, IWorkspace>;
23
+ declare module 'express' {
24
+ interface Request {
25
+ qelos: QelosRequestContext;
26
+ }
27
+ }
28
+ declare module 'express-serve-static-core' {
29
+ interface Request {
30
+ qelos: QelosRequestContext;
31
+ }
32
+ }
package/dist/types.js ADDED
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
package/package.json ADDED
@@ -0,0 +1,66 @@
1
+ {
2
+ "name": "@qelos/integrator-express",
3
+ "version": "4.0.0",
4
+ "description": "Express middleware that identifies the Qelos user and active workspace before your route handlers run",
5
+ "main": "./dist/index.js",
6
+ "types": "./dist/index.d.ts",
7
+ "exports": {
8
+ ".": {
9
+ "types": "./dist/index.d.ts",
10
+ "import": "./dist/index.js",
11
+ "require": "./dist/index.js",
12
+ "default": "./dist/index.js"
13
+ },
14
+ "./types": {
15
+ "types": "./dist/types.d.ts",
16
+ "import": "./dist/types.js",
17
+ "require": "./dist/types.js",
18
+ "default": "./dist/types.js"
19
+ },
20
+ "./package.json": "./package.json"
21
+ },
22
+ "files": [
23
+ "dist",
24
+ "README.md"
25
+ ],
26
+ "keywords": [
27
+ "qelos",
28
+ "express",
29
+ "middleware",
30
+ "integrator",
31
+ "auth"
32
+ ],
33
+ "author": "David Meir-Levy <davidmeirlevy@gmail.com>",
34
+ "license": "MIT",
35
+ "publishConfig": {
36
+ "access": "public"
37
+ },
38
+ "engines": {
39
+ "node": ">=18"
40
+ },
41
+ "peerDependencies": {
42
+ "express": "^4.17.0 || ^5.0.0"
43
+ },
44
+ "peerDependenciesMeta": {
45
+ "express": {
46
+ "optional": false
47
+ }
48
+ },
49
+ "dependencies": {
50
+ "@qelos/global-types": "^4.0.0",
51
+ "@qelos/sdk": "^4.0.0"
52
+ },
53
+ "devDependencies": {
54
+ "@types/express": "^4.17.21",
55
+ "@types/express-serve-static-core": "^4.19.6",
56
+ "express": "^4.21.2",
57
+ "tsx": "^4.21.0",
58
+ "typescript": "^5.6.3"
59
+ },
60
+ "scripts": {
61
+ "type-check": "tsc --noEmit",
62
+ "build": "tsc",
63
+ "pre-build": "tsc",
64
+ "test": "node --import tsx --test test/**/*.test.ts"
65
+ }
66
+ }