@qelos/integrator-nest 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,211 @@
1
+ # @qelos/integrator-nest
2
+
3
+ NestJS module that calls the Qelos SDK to identify the current user and their
4
+ active workspace before your route handler runs, exposing them on
5
+ `request.qelos.user` / `request.qelos.workspace`.
6
+
7
+ This is the NestJS implementation of the Qelos integrator contract — the same
8
+ shape exposed by `@qelos/integrator-express`, `@qelos/integrator-fastify`,
9
+ `@qelos/integrator-nuxt`, `@qelos/plugin-netlify-api`, etc. It works with
10
+ both Nest's Express adapter and its Fastify adapter.
11
+
12
+ ## Install
13
+
14
+ ```sh
15
+ npm install @qelos/integrator-nest @qelos/sdk
16
+ # Nest is a peer dependency
17
+ npm install @nestjs/common @nestjs/core
18
+ ```
19
+
20
+ ## Quick start
21
+
22
+ ```ts
23
+ // app.module.ts
24
+ import { Module } from '@nestjs/common';
25
+ import { QelosModule } from '@qelos/integrator-nest';
26
+
27
+ @Module({
28
+ imports: [
29
+ QelosModule.forRoot({
30
+ config: {
31
+ appUrl: process.env.QELOS_APP_URL!, // e.g. https://yourdomain.com
32
+ },
33
+ }),
34
+ ],
35
+ })
36
+ export class AppModule {}
37
+ ```
38
+
39
+ `forRoot` registers `QelosMiddleware` on every route by default. To restrict
40
+ its scope, import the module without `forRoot` and apply the middleware
41
+ yourself in `configure`:
42
+
43
+ ```ts
44
+ import {
45
+ QelosMiddleware,
46
+ QelosModule,
47
+ type QelosModuleOptions,
48
+ } from '@qelos/integrator-nest';
49
+ import { type MiddlewareConsumer, Module, type NestModule } from '@nestjs/common';
50
+
51
+ const options: QelosModuleOptions = {
52
+ config: { appUrl: process.env.QELOS_APP_URL! },
53
+ };
54
+
55
+ @Module({
56
+ imports: [QelosModule.forRoot(options)],
57
+ })
58
+ export class AppModule implements NestModule {
59
+ configure(consumer: MiddlewareConsumer) {
60
+ consumer.apply(QelosMiddleware).forRoutes('api/*');
61
+ }
62
+ }
63
+ ```
64
+
65
+ ## Use in controllers
66
+
67
+ ```ts
68
+ import { Controller, Get, UseGuards } from '@nestjs/common';
69
+ import {
70
+ QelosAuthGuard,
71
+ QelosUser,
72
+ QelosWorkspace,
73
+ QelosCtx,
74
+ type QelosRequestContext,
75
+ } from '@qelos/integrator-nest';
76
+ import type { IUser } from '@qelos/sdk/dist/authentication';
77
+ import type { IWorkspace } from '@qelos/sdk/workspaces';
78
+
79
+ @Controller()
80
+ export class AppController {
81
+ @Get('me')
82
+ // user/workspace are null when the request is anonymous
83
+ me(
84
+ @QelosUser() user: IUser | null,
85
+ @QelosWorkspace() workspace: IWorkspace | null,
86
+ ) {
87
+ return { user, workspace };
88
+ }
89
+
90
+ // Short-circuit with 401 when there is no authenticated user.
91
+ @Get('private')
92
+ @UseGuards(QelosAuthGuard)
93
+ private(@QelosCtx() ctx: QelosRequestContext) {
94
+ return ctx.user;
95
+ }
96
+ }
97
+ ```
98
+
99
+ ## What the middleware does
100
+
101
+ 1. Reads the access token from `Authorization: Bearer ...` or the
102
+ `q_access_token` cookie, and the refresh token from `q_refresh_token`.
103
+ 2. Builds a per-request Qelos SDK instance bound to those tokens.
104
+ 3. Calls `sdk.authentication.getLoggedInUser()` and
105
+ `sdk.workspaces.getList()`.
106
+ 4. Picks the active workspace (first by default — override with
107
+ `resolveWorkspace`).
108
+ 5. Attaches everything to `request.qelos`.
109
+
110
+ The middleware never throws for anonymous requests by default — it just
111
+ leaves `request.qelos.user` and `request.qelos.workspace` as `null`. Pass
112
+ `requireAuth: true` to short-circuit anonymous requests with `401`, or use
113
+ the per-route `QelosAuthGuard` for finer-grained control.
114
+
115
+ ## Token refresh
116
+
117
+ When the access token is rejected, the SDK tries to recover, in order:
118
+
119
+ 1. The **refresh token** (`q_refresh_token`) via
120
+ `sdk.authentication.refreshToken()` — issues a new access + refresh pair.
121
+ 2. The **cookie token** (the access token cookie itself) via
122
+ `sdk.authentication.refreshCookieToken()` — used for cookie-only sessions
123
+ that do not carry a separate refresh token (e.g. social-auth flows).
124
+
125
+ After a successful refresh the middleware fires the `onTokenRefresh` hook.
126
+ The default implementation writes the new tokens back to the response cookies
127
+ (`HttpOnly`, `SameSite=Lax`, `Secure` whenever `appUrl` is `https://...`).
128
+
129
+ You can supply your own — for example, to mint your own session cookie or
130
+ push the new tokens into a session store:
131
+
132
+ ```ts
133
+ QelosModule.forRoot({
134
+ config: { appUrl: process.env.QELOS_APP_URL! },
135
+ onTokenRefresh: async ({ request, response, newTokens }) => {
136
+ await sessionStore.rotate(request.session.id, newTokens);
137
+ },
138
+ });
139
+ ```
140
+
141
+ The hook receives `{ request, response, oldTokens, newTokens, sdk }`. The
142
+ `request` and `response` types are intentionally generic since Nest can run
143
+ on either Express or Fastify.
144
+
145
+ ### Manual cookie refresh
146
+
147
+ Long-lived integrator-hosted sessions can also call the SDK directly to
148
+ proactively refresh the cookie token:
149
+
150
+ ```ts
151
+ @Get('refresh-session')
152
+ async refresh(@QelosCtx() ctx: QelosRequestContext) {
153
+ const result = await ctx.sdk.authentication.refreshCookieToken();
154
+ // result.headers['set-cookie'] — fresh cookie value to forward
155
+ return { user: result.payload.user };
156
+ }
157
+ ```
158
+
159
+ ## Async configuration
160
+
161
+ ```ts
162
+ import { ConfigModule, ConfigService } from '@nestjs/config';
163
+
164
+ QelosModule.forRootAsync({
165
+ imports: [ConfigModule],
166
+ inject: [ConfigService],
167
+ useFactory: (config: ConfigService) => ({
168
+ config: {
169
+ appUrl: config.getOrThrow('QELOS_APP_URL'),
170
+ apiToken: config.get('QELOS_API_TOKEN'),
171
+ },
172
+ }),
173
+ });
174
+ ```
175
+
176
+ ## Configuration
177
+
178
+ ```ts
179
+ QelosModule.forRoot({
180
+ config: {
181
+ appUrl: 'https://yourdomain.com', // required
182
+
183
+ // Service-to-service: use a static API token instead of cookies/refresh.
184
+ apiToken: process.env.QELOS_API_TOKEN,
185
+
186
+ // Cookie names. Defaults shown.
187
+ accessTokenCookie: 'q_access_token',
188
+ refreshTokenCookie: 'q_refresh_token',
189
+
190
+ // Reject anonymous requests with 401. Defaults to false.
191
+ requireAuth: false,
192
+
193
+ // Skip the middleware entirely for these path prefixes.
194
+ skipPaths: ['/health', '/metrics'],
195
+
196
+ // Anything you want passed through to the per-request SDK.
197
+ sdkOptions: {},
198
+ },
199
+
200
+ // Override workspace selection. Defaults to `workspaces[0]`.
201
+ resolveWorkspace: ({ request, user, workspaces }) => {
202
+ const headerId = request.headers['x-qelos-workspace'];
203
+ return workspaces.find((w) => w._id === headerId) || workspaces[0] || null;
204
+ },
205
+ });
206
+ ```
207
+
208
+ ## Requirements
209
+
210
+ - Node.js >= 18 (uses the global `fetch`).
211
+ - NestJS 9, 10, or 11.
@@ -0,0 +1,3 @@
1
+ export declare const QELOS_MODULE_OPTIONS: unique symbol;
2
+ /** Request-scoped injection token for the per-request Qelos SDK (`request.qelos.sdk`). */
3
+ export declare const QELOS_SDK: unique symbol;
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.QELOS_SDK = exports.QELOS_MODULE_OPTIONS = void 0;
4
+ exports.QELOS_MODULE_OPTIONS = Symbol.for('@qelos/integrator-nest:module-options');
5
+ /** Request-scoped injection token for the per-request Qelos SDK (`request.qelos.sdk`). */
6
+ exports.QELOS_SDK = Symbol.for('@qelos/integrator-nest:sdk');
7
+ //# sourceMappingURL=constants.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants.js","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":";;;AAAa,QAAA,oBAAoB,GAAG,MAAM,CAAC,GAAG,CAAC,uCAAuC,CAAC,CAAC;AAExF,0FAA0F;AAC7E,QAAA,SAAS,GAAG,MAAM,CAAC,GAAG,CAAC,4BAA4B,CAAC,CAAC"}
@@ -0,0 +1,30 @@
1
+ import { Inject } from '@nestjs/common';
2
+ /**
3
+ * Inject the full Qelos request context attached by `QelosMiddleware`.
4
+ *
5
+ * ```ts
6
+ * @Get('ctx')
7
+ * ctx(@QelosCtx() ctx: QelosRequestContext) { ... }
8
+ * ```
9
+ */
10
+ export declare const QelosCtx: (...dataOrPipes: unknown[]) => ParameterDecorator;
11
+ /**
12
+ * Inject the authenticated user, or `null` for anonymous requests.
13
+ */
14
+ export declare const QelosUser: (...dataOrPipes: unknown[]) => ParameterDecorator;
15
+ /**
16
+ * Inject the active workspace, or `null` when none is active / the user is
17
+ * anonymous.
18
+ */
19
+ export declare const QelosWorkspace: (...dataOrPipes: unknown[]) => ParameterDecorator;
20
+ /**
21
+ * Inject the per-request Qelos SDK (`request.qelos.sdk`). Requires
22
+ * `QelosModule.forRoot` / `forRootAsync`. For constructor injection into your
23
+ * own providers, set `scope: Scope.REQUEST` on that provider (Nest propagates
24
+ * request scope from controllers).
25
+ *
26
+ * ```ts
27
+ * constructor(@QelosSdk() private readonly sdk: QelosSDK) {}
28
+ * ```
29
+ */
30
+ export declare function QelosSdk(): ReturnType<typeof Inject>;
@@ -0,0 +1,47 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.QelosWorkspace = exports.QelosUser = exports.QelosCtx = void 0;
4
+ exports.QelosSdk = QelosSdk;
5
+ const common_1 = require("@nestjs/common");
6
+ const constants_1 = require("./constants");
7
+ /**
8
+ * Inject the full Qelos request context attached by `QelosMiddleware`.
9
+ *
10
+ * ```ts
11
+ * @Get('ctx')
12
+ * ctx(@QelosCtx() ctx: QelosRequestContext) { ... }
13
+ * ```
14
+ */
15
+ exports.QelosCtx = (0, common_1.createParamDecorator)((_data, ctx) => {
16
+ const request = ctx.switchToHttp().getRequest();
17
+ return request?.qelos;
18
+ });
19
+ /**
20
+ * Inject the authenticated user, or `null` for anonymous requests.
21
+ */
22
+ exports.QelosUser = (0, common_1.createParamDecorator)((_data, ctx) => {
23
+ const request = ctx.switchToHttp().getRequest();
24
+ return request?.qelos?.user ?? null;
25
+ });
26
+ /**
27
+ * Inject the active workspace, or `null` when none is active / the user is
28
+ * anonymous.
29
+ */
30
+ exports.QelosWorkspace = (0, common_1.createParamDecorator)((_data, ctx) => {
31
+ const request = ctx.switchToHttp().getRequest();
32
+ return request?.qelos?.workspace ?? null;
33
+ });
34
+ /**
35
+ * Inject the per-request Qelos SDK (`request.qelos.sdk`). Requires
36
+ * `QelosModule.forRoot` / `forRootAsync`. For constructor injection into your
37
+ * own providers, set `scope: Scope.REQUEST` on that provider (Nest propagates
38
+ * request scope from controllers).
39
+ *
40
+ * ```ts
41
+ * constructor(@QelosSdk() private readonly sdk: QelosSDK) {}
42
+ * ```
43
+ */
44
+ function QelosSdk() {
45
+ return (0, common_1.Inject)(constants_1.QELOS_SDK);
46
+ }
47
+ //# sourceMappingURL=decorators.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decorators.js","sourceRoot":"","sources":["../src/decorators.ts"],"names":[],"mappings":";;;AAsDA,4BAEC;AAxDD,2CAIwB;AACxB,2CAAwC;AAGxC;;;;;;;GAOG;AACU,QAAA,QAAQ,GAAG,IAAA,6BAAoB,EAC1C,CAAC,KAAc,EAAE,GAAqB,EAAmC,EAAE;IACzE,MAAM,OAAO,GAAG,GAAG,CAAC,YAAY,EAAE,CAAC,UAAU,EAAc,CAAC;IAC5D,OAAO,OAAO,EAAE,KAAK,CAAC;AACxB,CAAC,CACF,CAAC;AAEF;;GAEG;AACU,QAAA,SAAS,GAAG,IAAA,6BAAoB,EAC3C,CAAC,KAAc,EAAE,GAAqB,EAAE,EAAE;IACxC,MAAM,OAAO,GAAG,GAAG,CAAC,YAAY,EAAE,CAAC,UAAU,EAAc,CAAC;IAC5D,OAAO,OAAO,EAAE,KAAK,EAAE,IAAI,IAAI,IAAI,CAAC;AACtC,CAAC,CACF,CAAC;AAEF;;;GAGG;AACU,QAAA,cAAc,GAAG,IAAA,6BAAoB,EAChD,CAAC,KAAc,EAAE,GAAqB,EAAE,EAAE;IACxC,MAAM,OAAO,GAAG,GAAG,CAAC,YAAY,EAAE,CAAC,UAAU,EAAc,CAAC;IAC5D,OAAO,OAAO,EAAE,KAAK,EAAE,SAAS,IAAI,IAAI,CAAC;AAC3C,CAAC,CACF,CAAC;AAEF;;;;;;;;;GASG;AACH,SAAgB,QAAQ;IACtB,OAAO,IAAA,eAAM,EAAC,qBAAS,CAAC,CAAC;AAC3B,CAAC"}
@@ -0,0 +1,18 @@
1
+ import { CanActivate, ExecutionContext } from '@nestjs/common';
2
+ /**
3
+ * Route-level guard that requires `request.qelos.user` to be populated
4
+ * (same contract as Express `requireUser`).
5
+ *
6
+ * ```ts
7
+ * @UseGuards(QelosGuard)
8
+ * @Get('me')
9
+ * me() { ... }
10
+ * ```
11
+ *
12
+ * Requires `QelosMiddleware` to have run first.
13
+ */
14
+ export declare class QelosGuard implements CanActivate {
15
+ canActivate(context: ExecutionContext): boolean;
16
+ }
17
+ /** Previous name for {@link QelosGuard}; kept for compatibility. */
18
+ export { QelosGuard as QelosAuthGuard };
package/dist/guard.js ADDED
@@ -0,0 +1,37 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.QelosAuthGuard = exports.QelosGuard = void 0;
10
+ const common_1 = require("@nestjs/common");
11
+ /**
12
+ * Route-level guard that requires `request.qelos.user` to be populated
13
+ * (same contract as Express `requireUser`).
14
+ *
15
+ * ```ts
16
+ * @UseGuards(QelosGuard)
17
+ * @Get('me')
18
+ * me() { ... }
19
+ * ```
20
+ *
21
+ * Requires `QelosMiddleware` to have run first.
22
+ */
23
+ let QelosGuard = class QelosGuard {
24
+ canActivate(context) {
25
+ const request = context.switchToHttp().getRequest();
26
+ if (!request?.qelos || !request.qelos.user) {
27
+ throw new common_1.UnauthorizedException();
28
+ }
29
+ return true;
30
+ }
31
+ };
32
+ exports.QelosGuard = QelosGuard;
33
+ exports.QelosAuthGuard = QelosGuard;
34
+ exports.QelosAuthGuard = exports.QelosGuard = QelosGuard = __decorate([
35
+ (0, common_1.Injectable)()
36
+ ], QelosGuard);
37
+ //# sourceMappingURL=guard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"guard.js","sourceRoot":"","sources":["../src/guard.ts"],"names":[],"mappings":";;;;;;;;;AAAA,2CAKwB;AAGxB;;;;;;;;;;;GAWG;AAEI,IAAM,UAAU,GAAhB,MAAM,UAAU;IACrB,WAAW,CAAC,OAAyB;QACnC,MAAM,OAAO,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,UAAU,EAAc,CAAC;QAChE,IAAI,CAAC,OAAO,EAAE,KAAK,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;YAC3C,MAAM,IAAI,8BAAqB,EAAE,CAAC;QACpC,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;CACF,CAAA;AARY,gCAAU;AAWA,oCAAc;8CAXxB,UAAU;IADtB,IAAA,mBAAU,GAAE;GACA,UAAU,CAQtB"}
@@ -0,0 +1,9 @@
1
+ export { QELOS_MODULE_OPTIONS, QELOS_SDK } from './constants';
2
+ export { QelosModule } from './module';
3
+ export type { QelosModuleAsyncOptions, QelosOptionsFactory, } from './module';
4
+ export { QelosMiddleware } from './middleware';
5
+ export { QelosAuthGuard, QelosGuard } from './guard';
6
+ export { QelosCtx, QelosSdk, QelosUser, QelosWorkspace } from './decorators';
7
+ export { createRequestSdk } from './sdk-factory';
8
+ export type { CreateSdkParams } from './sdk-factory';
9
+ export type { AnyRequest, AnyResponse, QelosModuleOptions, QelosNestConfig, QelosRequestContext, QelosTokenPair, ResolvedTokens, TokenRefreshContext, TokenRefreshHook, WorkspaceResolver, } from './types';
package/dist/index.js ADDED
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createRequestSdk = exports.QelosWorkspace = exports.QelosUser = exports.QelosSdk = exports.QelosCtx = exports.QelosGuard = exports.QelosAuthGuard = exports.QelosMiddleware = exports.QelosModule = exports.QELOS_SDK = exports.QELOS_MODULE_OPTIONS = void 0;
4
+ var constants_1 = require("./constants");
5
+ Object.defineProperty(exports, "QELOS_MODULE_OPTIONS", { enumerable: true, get: function () { return constants_1.QELOS_MODULE_OPTIONS; } });
6
+ Object.defineProperty(exports, "QELOS_SDK", { enumerable: true, get: function () { return constants_1.QELOS_SDK; } });
7
+ var module_1 = require("./module");
8
+ Object.defineProperty(exports, "QelosModule", { enumerable: true, get: function () { return module_1.QelosModule; } });
9
+ var middleware_1 = require("./middleware");
10
+ Object.defineProperty(exports, "QelosMiddleware", { enumerable: true, get: function () { return middleware_1.QelosMiddleware; } });
11
+ var guard_1 = require("./guard");
12
+ Object.defineProperty(exports, "QelosAuthGuard", { enumerable: true, get: function () { return guard_1.QelosAuthGuard; } });
13
+ Object.defineProperty(exports, "QelosGuard", { enumerable: true, get: function () { return guard_1.QelosGuard; } });
14
+ var decorators_1 = require("./decorators");
15
+ Object.defineProperty(exports, "QelosCtx", { enumerable: true, get: function () { return decorators_1.QelosCtx; } });
16
+ Object.defineProperty(exports, "QelosSdk", { enumerable: true, get: function () { return decorators_1.QelosSdk; } });
17
+ Object.defineProperty(exports, "QelosUser", { enumerable: true, get: function () { return decorators_1.QelosUser; } });
18
+ Object.defineProperty(exports, "QelosWorkspace", { enumerable: true, get: function () { return decorators_1.QelosWorkspace; } });
19
+ var sdk_factory_1 = require("./sdk-factory");
20
+ Object.defineProperty(exports, "createRequestSdk", { enumerable: true, get: function () { return sdk_factory_1.createRequestSdk; } });
21
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,yCAA8D;AAArD,iHAAA,oBAAoB,OAAA;AAAE,sGAAA,SAAS,OAAA;AACxC,mCAAuC;AAA9B,qGAAA,WAAW,OAAA;AAKpB,2CAA+C;AAAtC,6GAAA,eAAe,OAAA;AACxB,iCAAqD;AAA5C,uGAAA,cAAc,OAAA;AAAE,mGAAA,UAAU,OAAA;AACnC,2CAA6E;AAApE,sGAAA,QAAQ,OAAA;AAAE,sGAAA,QAAQ,OAAA;AAAE,uGAAA,SAAS,OAAA;AAAE,4GAAA,cAAc,OAAA;AACtD,6CAAiD;AAAxC,+GAAA,gBAAgB,OAAA"}
@@ -0,0 +1,7 @@
1
+ import { type NestMiddleware } from '@nestjs/common';
2
+ import type { QelosModuleOptions } from './types';
3
+ export declare class QelosMiddleware implements NestMiddleware {
4
+ private readonly options;
5
+ constructor(options: QelosModuleOptions);
6
+ use(req: unknown, res: unknown, next: (err?: unknown) => void): Promise<void>;
7
+ }
@@ -0,0 +1,100 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
12
+ return function (target, key) { decorator(target, key, paramIndex); }
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.QelosMiddleware = void 0;
16
+ const common_1 = require("@nestjs/common");
17
+ const constants_1 = require("./constants");
18
+ const sdk_factory_1 = require("./sdk-factory");
19
+ const request_utils_1 = require("./request-utils");
20
+ let QelosMiddleware = class QelosMiddleware {
21
+ constructor(options) {
22
+ this.options = options;
23
+ }
24
+ async use(req, res, next) {
25
+ const request = req;
26
+ const response = res;
27
+ const { config, resolveWorkspace } = this.options;
28
+ const onTokenRefresh = this.options.onTokenRefresh ||
29
+ (async ({ response: r, newTokens }) => {
30
+ (0, request_utils_1.writeTokensToCookies)(r, config, newTokens);
31
+ });
32
+ if ((0, request_utils_1.shouldSkip)(request, config)) {
33
+ next();
34
+ return;
35
+ }
36
+ const tokens = (0, request_utils_1.readTokens)(request, config);
37
+ const sdk = (0, sdk_factory_1.createRequestSdk)({
38
+ config,
39
+ tokens,
40
+ request,
41
+ response,
42
+ onTokenRefresh,
43
+ });
44
+ const ctx = {
45
+ user: null,
46
+ workspace: null,
47
+ workspaces: [],
48
+ sdk,
49
+ tokens,
50
+ };
51
+ request.qelos = ctx;
52
+ const hasAuthMaterial = Boolean(config.apiToken || tokens.accessToken || tokens.refreshToken);
53
+ if (!hasAuthMaterial) {
54
+ if (config.requireAuth) {
55
+ next(new common_1.UnauthorizedException());
56
+ return;
57
+ }
58
+ next();
59
+ return;
60
+ }
61
+ try {
62
+ ctx.user = await sdk.authentication.getLoggedInUser();
63
+ }
64
+ catch {
65
+ if (config.requireAuth) {
66
+ next(new common_1.UnauthorizedException());
67
+ return;
68
+ }
69
+ next();
70
+ return;
71
+ }
72
+ try {
73
+ ctx.workspaces = await sdk.workspaces.getList();
74
+ }
75
+ catch {
76
+ ctx.workspaces = [];
77
+ }
78
+ if (ctx.user && ctx.workspaces.length) {
79
+ if (resolveWorkspace) {
80
+ ctx.workspace =
81
+ (await resolveWorkspace({
82
+ request,
83
+ user: ctx.user,
84
+ workspaces: ctx.workspaces,
85
+ })) || null;
86
+ }
87
+ else {
88
+ ctx.workspace = ctx.workspaces[0] || null;
89
+ }
90
+ }
91
+ next();
92
+ }
93
+ };
94
+ exports.QelosMiddleware = QelosMiddleware;
95
+ exports.QelosMiddleware = QelosMiddleware = __decorate([
96
+ (0, common_1.Injectable)(),
97
+ __param(0, (0, common_1.Inject)(constants_1.QELOS_MODULE_OPTIONS)),
98
+ __metadata("design:paramtypes", [Object])
99
+ ], QelosMiddleware);
100
+ //# 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,7 @@
1
+ import type { QelosModuleOptions, QelosNestConfig } from './types';
2
+ /**
3
+ * `forRoot` / `forRootAsync` accept either a full {@link QelosModuleOptions}
4
+ * object or a shorthand {@link QelosNestConfig} (same shape as Express
5
+ * `createQelosMiddleware({ config })`).
6
+ */
7
+ export declare function normalizeModuleOptions(input: QelosNestConfig | QelosModuleOptions): QelosModuleOptions;
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.normalizeModuleOptions = normalizeModuleOptions;
4
+ /**
5
+ * `forRoot` / `forRootAsync` accept either a full {@link QelosModuleOptions}
6
+ * object or a shorthand {@link QelosNestConfig} (same shape as Express
7
+ * `createQelosMiddleware({ config })`).
8
+ */
9
+ function normalizeModuleOptions(input) {
10
+ if (input !== null &&
11
+ typeof input === 'object' &&
12
+ 'config' in input &&
13
+ input.config !== undefined &&
14
+ typeof input.config === 'object' &&
15
+ input.config !== null &&
16
+ 'appUrl' in input.config) {
17
+ return input;
18
+ }
19
+ return { config: input };
20
+ }
21
+ //# sourceMappingURL=module-options.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"module-options.js","sourceRoot":"","sources":["../src/module-options.ts"],"names":[],"mappings":";;AAOA,wDAeC;AApBD;;;;GAIG;AACH,SAAgB,sBAAsB,CACpC,KAA2C;IAE3C,IACE,KAAK,KAAK,IAAI;QACd,OAAO,KAAK,KAAK,QAAQ;QACzB,QAAQ,IAAI,KAAK;QAChB,KAA4B,CAAC,MAAM,KAAK,SAAS;QAClD,OAAQ,KAA4B,CAAC,MAAM,KAAK,QAAQ;QACvD,KAA4B,CAAC,MAAM,KAAK,IAAI;QAC7C,QAAQ,IAAK,KAA4B,CAAC,MAAM,EAChD,CAAC;QACD,OAAO,KAA2B,CAAC;IACrC,CAAC;IACD,OAAO,EAAE,MAAM,EAAE,KAAwB,EAAE,CAAC;AAC9C,CAAC"}
@@ -0,0 +1,33 @@
1
+ import { type DynamicModule, type FactoryProvider, type MiddlewareConsumer, type ModuleMetadata, type NestModule, type Type } from '@nestjs/common';
2
+ import type { QelosModuleOptions, QelosNestConfig } from './types';
3
+ export interface QelosModuleAsyncOptions extends Pick<ModuleMetadata, 'imports'> {
4
+ useFactory: (...args: unknown[]) => Promise<QelosNestConfig | QelosModuleOptions> | QelosNestConfig | QelosModuleOptions;
5
+ inject?: FactoryProvider['inject'];
6
+ useExisting?: Type<QelosOptionsFactory>;
7
+ useClass?: Type<QelosOptionsFactory>;
8
+ }
9
+ export interface QelosOptionsFactory {
10
+ createQelosOptions(): Promise<QelosNestConfig | QelosModuleOptions> | QelosNestConfig | QelosModuleOptions;
11
+ }
12
+ export declare class QelosModule implements NestModule {
13
+ private static readonly sdkProvider;
14
+ /**
15
+ * Register the module with statically-known options.
16
+ *
17
+ * Pass either a full {@link QelosModuleOptions} object or a shorthand
18
+ * {@link QelosNestConfig} (e.g. `{ appUrl, apiToken }`).
19
+ *
20
+ * The middleware is wired to `forRoutes('*')` by default. Override by
21
+ * importing the module without `forRoot` and applying `QelosMiddleware`
22
+ * directly inside your own `MiddlewareConsumer`.
23
+ */
24
+ static forRoot(config: QelosNestConfig): DynamicModule;
25
+ static forRoot(options: QelosModuleOptions): DynamicModule;
26
+ /**
27
+ * Register the module with options resolved asynchronously (e.g. via
28
+ * `ConfigService`).
29
+ */
30
+ static forRootAsync(options: QelosModuleAsyncOptions): DynamicModule;
31
+ private static createAsyncProviders;
32
+ configure(consumer: MiddlewareConsumer): void;
33
+ }
package/dist/module.js ADDED
@@ -0,0 +1,88 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var QelosModule_1;
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.QelosModule = void 0;
11
+ const common_1 = require("@nestjs/common");
12
+ const core_1 = require("@nestjs/core");
13
+ const constants_1 = require("./constants");
14
+ const module_options_1 = require("./module-options");
15
+ const middleware_1 = require("./middleware");
16
+ let QelosModule = QelosModule_1 = class QelosModule {
17
+ static forRoot(configOrOptions) {
18
+ const options = (0, module_options_1.normalizeModuleOptions)(configOrOptions);
19
+ const optionsProvider = {
20
+ provide: constants_1.QELOS_MODULE_OPTIONS,
21
+ useValue: options,
22
+ };
23
+ return {
24
+ module: QelosModule_1,
25
+ providers: [optionsProvider, middleware_1.QelosMiddleware, QelosModule_1.sdkProvider],
26
+ exports: [optionsProvider, middleware_1.QelosMiddleware, constants_1.QELOS_SDK],
27
+ global: true,
28
+ };
29
+ }
30
+ /**
31
+ * Register the module with options resolved asynchronously (e.g. via
32
+ * `ConfigService`).
33
+ */
34
+ static forRootAsync(options) {
35
+ const providers = [
36
+ ...this.createAsyncProviders(options),
37
+ middleware_1.QelosMiddleware,
38
+ QelosModule_1.sdkProvider,
39
+ ];
40
+ return {
41
+ module: QelosModule_1,
42
+ imports: options.imports || [],
43
+ providers,
44
+ exports: [constants_1.QELOS_MODULE_OPTIONS, middleware_1.QelosMiddleware, constants_1.QELOS_SDK],
45
+ global: true,
46
+ };
47
+ }
48
+ static createAsyncProviders(options) {
49
+ if (options.useFactory) {
50
+ const factoryProvider = {
51
+ provide: constants_1.QELOS_MODULE_OPTIONS,
52
+ useFactory: async (...args) => (0, module_options_1.normalizeModuleOptions)(await options.useFactory(...args)),
53
+ inject: options.inject || [],
54
+ };
55
+ return [factoryProvider];
56
+ }
57
+ const factoryClass = options.useExisting || options.useClass;
58
+ if (!factoryClass) {
59
+ throw new Error('@qelos/integrator-nest: forRootAsync requires useFactory, useClass or useExisting');
60
+ }
61
+ const optionsProvider = {
62
+ provide: constants_1.QELOS_MODULE_OPTIONS,
63
+ useFactory: async (factory) => (0, module_options_1.normalizeModuleOptions)(await factory.createQelosOptions()),
64
+ inject: [factoryClass],
65
+ };
66
+ if (options.useClass) {
67
+ return [
68
+ optionsProvider,
69
+ { provide: factoryClass, useClass: factoryClass },
70
+ ];
71
+ }
72
+ return [optionsProvider];
73
+ }
74
+ configure(consumer) {
75
+ consumer.apply(middleware_1.QelosMiddleware).forRoutes('*');
76
+ }
77
+ };
78
+ exports.QelosModule = QelosModule;
79
+ QelosModule.sdkProvider = {
80
+ provide: constants_1.QELOS_SDK,
81
+ scope: common_1.Scope.REQUEST,
82
+ useFactory: (request) => request.qelos?.sdk,
83
+ inject: [core_1.REQUEST],
84
+ };
85
+ exports.QelosModule = QelosModule = QelosModule_1 = __decorate([
86
+ (0, common_1.Module)({})
87
+ ], QelosModule);
88
+ //# sourceMappingURL=module.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"module.js","sourceRoot":"","sources":["../src/module.ts"],"names":[],"mappings":";;;;;;;;;;AAAA,2CAUwB;AACxB,uCAAuC;AACvC,2CAA8D;AAC9D,qDAA0D;AAC1D,6CAA+C;AAwBxC,IAAM,WAAW,mBAAjB,MAAM,WAAW;IAoBtB,MAAM,CAAC,OAAO,CAAC,eAAqD;QAClE,MAAM,OAAO,GAAG,IAAA,uCAAsB,EAAC,eAAe,CAAC,CAAC;QACxD,MAAM,eAAe,GAAa;YAChC,OAAO,EAAE,gCAAoB;YAC7B,QAAQ,EAAE,OAAO;SAClB,CAAC;QACF,OAAO;YACL,MAAM,EAAE,aAAW;YACnB,SAAS,EAAE,CAAC,eAAe,EAAE,4BAAe,EAAE,aAAW,CAAC,WAAW,CAAC;YACtE,OAAO,EAAE,CAAC,eAAe,EAAE,4BAAe,EAAE,qBAAS,CAAC;YACtD,MAAM,EAAE,IAAI;SACb,CAAC;IACJ,CAAC;IAED;;;OAGG;IACH,MAAM,CAAC,YAAY,CAAC,OAAgC;QAClD,MAAM,SAAS,GAAe;YAC5B,GAAG,IAAI,CAAC,oBAAoB,CAAC,OAAO,CAAC;YACrC,4BAAe;YACf,aAAW,CAAC,WAAW;SACxB,CAAC;QACF,OAAO;YACL,MAAM,EAAE,aAAW;YACnB,OAAO,EAAE,OAAO,CAAC,OAAO,IAAI,EAAE;YAC9B,SAAS;YACT,OAAO,EAAE,CAAC,gCAAoB,EAAE,4BAAe,EAAE,qBAAS,CAAC;YAC3D,MAAM,EAAE,IAAI;SACb,CAAC;IACJ,CAAC;IAEO,MAAM,CAAC,oBAAoB,CACjC,OAAgC;QAEhC,IAAI,OAAO,CAAC,UAAU,EAAE,CAAC;YACvB,MAAM,eAAe,GAAwC;gBAC3D,OAAO,EAAE,gCAAoB;gBAC7B,UAAU,EAAE,KAAK,EAAE,GAAG,IAAe,EAAE,EAAE,CACvC,IAAA,uCAAsB,EAAC,MAAM,OAAO,CAAC,UAAW,CAAC,GAAG,IAAI,CAAC,CAAC;gBAC5D,MAAM,EAAE,OAAO,CAAC,MAAM,IAAI,EAAE;aAC7B,CAAC;YACF,OAAO,CAAC,eAAe,CAAC,CAAC;QAC3B,CAAC;QACD,MAAM,YAAY,GAAG,OAAO,CAAC,WAAW,IAAI,OAAO,CAAC,QAAQ,CAAC;QAC7D,IAAI,CAAC,YAAY,EAAE,CAAC;YAClB,MAAM,IAAI,KAAK,CACb,mFAAmF,CACpF,CAAC;QACJ,CAAC;QACD,MAAM,eAAe,GAAwC;YAC3D,OAAO,EAAE,gCAAoB;YAC7B,UAAU,EAAE,KAAK,EAAE,OAA4B,EAAE,EAAE,CACjD,IAAA,uCAAsB,EAAC,MAAM,OAAO,CAAC,kBAAkB,EAAE,CAAC;YAC5D,MAAM,EAAE,CAAC,YAAY,CAAC;SACvB,CAAC;QACF,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC;YACrB,OAAO;gBACL,eAAe;gBACf,EAAE,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,YAAY,EAAc;aAC9D,CAAC;QACJ,CAAC;QACD,OAAO,CAAC,eAAe,CAAC,CAAC;IAC3B,CAAC;IAED,SAAS,CAAC,QAA4B;QACpC,QAAQ,CAAC,KAAK,CAAC,4BAAe,CAAC,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;IACjD,CAAC;;AAxFU,kCAAW;AACE,uBAAW,GAAa;IAC9C,OAAO,EAAE,qBAAS;IAClB,KAAK,EAAE,cAAK,CAAC,OAAO;IACpB,UAAU,EAAE,CAAC,OAAmB,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG;IACvD,MAAM,EAAE,CAAC,cAAO,CAAC;CAClB,AALkC,CAKjC;sBANS,WAAW;IADvB,IAAA,eAAM,EAAC,EAAE,CAAC;GACE,WAAW,CAyFvB"}
@@ -0,0 +1,7 @@
1
+ import type { AnyRequest, AnyResponse, QelosNestConfig, QelosTokenPair, ResolvedTokens } from './types';
2
+ export declare const DEFAULT_ACCESS_COOKIE = "q_access_token";
3
+ export declare const DEFAULT_REFRESH_COOKIE = "q_refresh_token";
4
+ export declare function readCookie(request: AnyRequest, name: string): string | undefined;
5
+ export declare function readTokens(request: AnyRequest, config: QelosNestConfig): QelosTokenPair;
6
+ export declare function writeTokensToCookies(response: AnyResponse, config: QelosNestConfig, tokens: ResolvedTokens): void;
7
+ export declare function shouldSkip(request: AnyRequest, config: QelosNestConfig): boolean;
@@ -0,0 +1,116 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DEFAULT_REFRESH_COOKIE = exports.DEFAULT_ACCESS_COOKIE = void 0;
4
+ exports.readCookie = readCookie;
5
+ exports.readTokens = readTokens;
6
+ exports.writeTokensToCookies = writeTokensToCookies;
7
+ exports.shouldSkip = shouldSkip;
8
+ exports.DEFAULT_ACCESS_COOKIE = 'q_access_token';
9
+ exports.DEFAULT_REFRESH_COOKIE = 'q_refresh_token';
10
+ function readCookie(request, name) {
11
+ // Prefer cookie-parser / @fastify/cookie's parsed output when available.
12
+ if (request.cookies && typeof request.cookies[name] === 'string') {
13
+ return request.cookies[name];
14
+ }
15
+ const cookieHeader = request.headers.cookie;
16
+ const header = Array.isArray(cookieHeader) ? cookieHeader.join('; ') : cookieHeader;
17
+ if (!header)
18
+ return undefined;
19
+ const prefix = name + '=';
20
+ for (const part of header.split(';')) {
21
+ const trimmed = part.trim();
22
+ if (trimmed.startsWith(prefix)) {
23
+ try {
24
+ return decodeURIComponent(trimmed.slice(prefix.length));
25
+ }
26
+ catch {
27
+ return trimmed.slice(prefix.length);
28
+ }
29
+ }
30
+ }
31
+ return undefined;
32
+ }
33
+ function readTokens(request, config) {
34
+ const accessCookie = config.accessTokenCookie || exports.DEFAULT_ACCESS_COOKIE;
35
+ const refreshCookie = config.refreshTokenCookie || exports.DEFAULT_REFRESH_COOKIE;
36
+ const cookieAccess = readCookie(request, accessCookie);
37
+ const cookieRefresh = readCookie(request, refreshCookie);
38
+ const rawAuth = request.headers.authorization;
39
+ const authHeader = Array.isArray(rawAuth) ? rawAuth[0] : rawAuth;
40
+ const headerAccess = authHeader && authHeader.toLowerCase().startsWith('bearer ')
41
+ ? authHeader.slice(7).trim()
42
+ : undefined;
43
+ return {
44
+ accessToken: headerAccess || cookieAccess || undefined,
45
+ refreshToken: cookieRefresh || undefined,
46
+ };
47
+ }
48
+ function serializeCookie(name, value, secure) {
49
+ const parts = [
50
+ `${name}=${encodeURIComponent(value)}`,
51
+ 'Path=/',
52
+ 'HttpOnly',
53
+ 'SameSite=Lax',
54
+ ];
55
+ if (secure)
56
+ parts.push('Secure');
57
+ return parts.join('; ');
58
+ }
59
+ function appendSetCookie(response, value) {
60
+ const setHeader = response.setHeader || response.header;
61
+ const getHeader = response.getHeader;
62
+ const existing = typeof getHeader === 'function' ? getHeader.call(response, 'set-cookie') : undefined;
63
+ let next;
64
+ if (Array.isArray(existing)) {
65
+ next = [...existing, value];
66
+ }
67
+ else if (typeof existing === 'string') {
68
+ next = [existing, value];
69
+ }
70
+ else {
71
+ next = [value];
72
+ }
73
+ if (typeof setHeader === 'function') {
74
+ setHeader.call(response, 'set-cookie', next);
75
+ }
76
+ }
77
+ function writeTokensToCookies(response, config, tokens) {
78
+ const accessCookie = config.accessTokenCookie || exports.DEFAULT_ACCESS_COOKIE;
79
+ const refreshCookie = config.refreshTokenCookie || exports.DEFAULT_REFRESH_COOKIE;
80
+ const secure = !/^http:\/\//i.test(config.appUrl);
81
+ const cookieOptions = {
82
+ httpOnly: true,
83
+ secure,
84
+ sameSite: 'lax',
85
+ path: '/',
86
+ };
87
+ // Express
88
+ if (typeof response.cookie === 'function') {
89
+ response.cookie(accessCookie, tokens.accessToken, cookieOptions);
90
+ if (tokens.refreshToken) {
91
+ response.cookie(refreshCookie, tokens.refreshToken, cookieOptions);
92
+ }
93
+ return;
94
+ }
95
+ // Fastify (with @fastify/cookie)
96
+ if (typeof response.setCookie === 'function') {
97
+ response.setCookie(accessCookie, tokens.accessToken, cookieOptions);
98
+ if (tokens.refreshToken) {
99
+ response.setCookie(refreshCookie, tokens.refreshToken, cookieOptions);
100
+ }
101
+ return;
102
+ }
103
+ appendSetCookie(response, serializeCookie(accessCookie, tokens.accessToken, secure));
104
+ if (tokens.refreshToken) {
105
+ appendSetCookie(response, serializeCookie(refreshCookie, tokens.refreshToken, secure));
106
+ }
107
+ }
108
+ function shouldSkip(request, config) {
109
+ if (!config.skipPaths?.length)
110
+ return false;
111
+ const url = request.path || request.url || '';
112
+ const queryIdx = url.indexOf('?');
113
+ const path = queryIdx >= 0 ? url.slice(0, queryIdx) : url;
114
+ return config.skipPaths.some((prefix) => path.startsWith(prefix));
115
+ }
116
+ //# sourceMappingURL=request-utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"request-utils.js","sourceRoot":"","sources":["../src/request-utils.ts"],"names":[],"mappings":";;;AAWA,gCAoBC;AAED,gCAkBC;AA+BD,oDAsCC;AAED,gCASC;AA3HY,QAAA,qBAAqB,GAAG,gBAAgB,CAAC;AACzC,QAAA,sBAAsB,GAAG,iBAAiB,CAAC;AAExD,SAAgB,UAAU,CAAC,OAAmB,EAAE,IAAY;IAC1D,yEAAyE;IACzE,IAAI,OAAO,CAAC,OAAO,IAAI,OAAO,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,QAAQ,EAAE,CAAC;QACjE,OAAO,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/B,CAAC;IACD,MAAM,YAAY,GAAG,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC;IAC5C,MAAM,MAAM,GAAG,KAAK,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC;IACpF,IAAI,CAAC,MAAM;QAAE,OAAO,SAAS,CAAC;IAC9B,MAAM,MAAM,GAAG,IAAI,GAAG,GAAG,CAAC;IAC1B,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;QACrC,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QAC5B,IAAI,OAAO,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;YAC/B,IAAI,CAAC;gBACH,OAAO,kBAAkB,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;YAC1D,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;YACtC,CAAC;QACH,CAAC;IACH,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,SAAgB,UAAU,CACxB,OAAmB,EACnB,MAAuB;IAEvB,MAAM,YAAY,GAAG,MAAM,CAAC,iBAAiB,IAAI,6BAAqB,CAAC;IACvE,MAAM,aAAa,GAAG,MAAM,CAAC,kBAAkB,IAAI,8BAAsB,CAAC;IAC1E,MAAM,YAAY,GAAG,UAAU,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC;IACvD,MAAM,aAAa,GAAG,UAAU,CAAC,OAAO,EAAE,aAAa,CAAC,CAAC;IACzD,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,aAAa,CAAC;IAC9C,MAAM,UAAU,GAAG,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;IACjE,MAAM,YAAY,GAChB,UAAU,IAAI,UAAU,CAAC,WAAW,EAAE,CAAC,UAAU,CAAC,SAAS,CAAC;QAC1D,CAAC,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE;QAC5B,CAAC,CAAC,SAAS,CAAC;IAChB,OAAO;QACL,WAAW,EAAE,YAAY,IAAI,YAAY,IAAI,SAAS;QACtD,YAAY,EAAE,aAAa,IAAI,SAAS;KACzC,CAAC;AACJ,CAAC;AAED,SAAS,eAAe,CAAC,IAAY,EAAE,KAAa,EAAE,MAAe;IACnE,MAAM,KAAK,GAAG;QACZ,GAAG,IAAI,IAAI,kBAAkB,CAAC,KAAK,CAAC,EAAE;QACtC,QAAQ;QACR,UAAU;QACV,cAAc;KACf,CAAC;IACF,IAAI,MAAM;QAAE,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACjC,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC;AAED,SAAS,eAAe,CAAC,QAAqB,EAAE,KAAa;IAC3D,MAAM,SAAS,GAAG,QAAQ,CAAC,SAAS,IAAI,QAAQ,CAAC,MAAM,CAAC;IACxD,MAAM,SAAS,GAAG,QAAQ,CAAC,SAAS,CAAC;IACrC,MAAM,QAAQ,GACZ,OAAO,SAAS,KAAK,UAAU,CAAC,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC,QAAQ,EAAE,YAAY,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IACvF,IAAI,IAAc,CAAC;IACnB,IAAI,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC5B,IAAI,GAAG,CAAC,GAAG,QAAQ,EAAE,KAAK,CAAC,CAAC;IAC9B,CAAC;SAAM,IAAI,OAAO,QAAQ,KAAK,QAAQ,EAAE,CAAC;QACxC,IAAI,GAAG,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;IAC3B,CAAC;SAAM,CAAC;QACN,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC;IACjB,CAAC;IACD,IAAI,OAAO,SAAS,KAAK,UAAU,EAAE,CAAC;QACpC,SAAS,CAAC,IAAI,CAAC,QAAQ,EAAE,YAAY,EAAE,IAAI,CAAC,CAAC;IAC/C,CAAC;AACH,CAAC;AAED,SAAgB,oBAAoB,CAClC,QAAqB,EACrB,MAAuB,EACvB,MAAsB;IAEtB,MAAM,YAAY,GAAG,MAAM,CAAC,iBAAiB,IAAI,6BAAqB,CAAC;IACvE,MAAM,aAAa,GAAG,MAAM,CAAC,kBAAkB,IAAI,8BAAsB,CAAC;IAC1E,MAAM,MAAM,GAAG,CAAC,aAAa,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IAElD,MAAM,aAAa,GAAG;QACpB,QAAQ,EAAE,IAAI;QACd,MAAM;QACN,QAAQ,EAAE,KAAc;QACxB,IAAI,EAAE,GAAG;KACV,CAAC;IAEF,UAAU;IACV,IAAI,OAAO,QAAQ,CAAC,MAAM,KAAK,UAAU,EAAE,CAAC;QAC1C,QAAQ,CAAC,MAAM,CAAC,YAAY,EAAE,MAAM,CAAC,WAAW,EAAE,aAAa,CAAC,CAAC;QACjE,IAAI,MAAM,CAAC,YAAY,EAAE,CAAC;YACxB,QAAQ,CAAC,MAAM,CAAC,aAAa,EAAE,MAAM,CAAC,YAAY,EAAE,aAAa,CAAC,CAAC;QACrE,CAAC;QACD,OAAO;IACT,CAAC;IAED,iCAAiC;IACjC,IAAI,OAAO,QAAQ,CAAC,SAAS,KAAK,UAAU,EAAE,CAAC;QAC7C,QAAQ,CAAC,SAAS,CAAC,YAAY,EAAE,MAAM,CAAC,WAAW,EAAE,aAAa,CAAC,CAAC;QACpE,IAAI,MAAM,CAAC,YAAY,EAAE,CAAC;YACxB,QAAQ,CAAC,SAAS,CAAC,aAAa,EAAE,MAAM,CAAC,YAAY,EAAE,aAAa,CAAC,CAAC;QACxE,CAAC;QACD,OAAO;IACT,CAAC;IAED,eAAe,CAAC,QAAQ,EAAE,eAAe,CAAC,YAAY,EAAE,MAAM,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC,CAAC;IACrF,IAAI,MAAM,CAAC,YAAY,EAAE,CAAC;QACxB,eAAe,CAAC,QAAQ,EAAE,eAAe,CAAC,aAAa,EAAE,MAAM,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC,CAAC;IACzF,CAAC;AACH,CAAC;AAED,SAAgB,UAAU,CACxB,OAAmB,EACnB,MAAuB;IAEvB,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE,MAAM;QAAE,OAAO,KAAK,CAAC;IAC5C,MAAM,GAAG,GAAG,OAAO,CAAC,IAAI,IAAI,OAAO,CAAC,GAAG,IAAI,EAAE,CAAC;IAC9C,MAAM,QAAQ,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IAClC,MAAM,IAAI,GAAG,QAAQ,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;IAC1D,OAAO,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC;AACpE,CAAC"}
@@ -0,0 +1,15 @@
1
+ import QelosSDK from '@qelos/sdk';
2
+ import type { AnyRequest, AnyResponse, QelosNestConfig, QelosTokenPair, TokenRefreshHook } from './types';
3
+ export interface CreateSdkParams {
4
+ config: QelosNestConfig;
5
+ /**
6
+ * Tokens for the current request. The factory mutates this object in place
7
+ * when a token refresh occurs, so callers can read the latest pair after
8
+ * the SDK has been used.
9
+ */
10
+ tokens: QelosTokenPair;
11
+ request: AnyRequest;
12
+ response: AnyResponse;
13
+ onTokenRefresh?: TokenRefreshHook;
14
+ }
15
+ export declare function createRequestSdk({ config, tokens, request, response, 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, request, response, 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
+ request,
45
+ response,
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,122 @@
1
+ import type QelosSDK from '@qelos/sdk';
2
+ import type { IUser } from '@qelos/sdk/dist/authentication';
3
+ import type { IWorkspace } from '@qelos/sdk/workspaces';
4
+ import type { QelosSDKOptions } from '@qelos/sdk/types';
5
+ export interface QelosTokenPair {
6
+ accessToken?: string;
7
+ refreshToken?: string;
8
+ }
9
+ export interface ResolvedTokens {
10
+ accessToken: string;
11
+ refreshToken?: string;
12
+ }
13
+ /**
14
+ * A request-like object. NestJS can run on either Express or Fastify, so we
15
+ * type the bits we touch generically and feature-detect at runtime.
16
+ */
17
+ export interface AnyRequest {
18
+ url?: string;
19
+ path?: string;
20
+ headers: Record<string, string | string[] | undefined>;
21
+ cookies?: Record<string, string | undefined>;
22
+ qelos?: QelosRequestContext;
23
+ [key: string]: unknown;
24
+ }
25
+ /**
26
+ * A response-like object (Express `Response` or Fastify `FastifyReply`).
27
+ */
28
+ export interface AnyResponse {
29
+ cookie?: (name: string, value: string, options?: Record<string, unknown>) => unknown;
30
+ setCookie?: (name: string, value: string, options?: Record<string, unknown>) => unknown;
31
+ setHeader?: (name: string, value: string | string[]) => unknown;
32
+ header?: (name: string, value: string | string[]) => unknown;
33
+ getHeader?: (name: string) => string | string[] | number | undefined;
34
+ [key: string]: unknown;
35
+ }
36
+ export interface QelosNestConfig {
37
+ /**
38
+ * Base URL of the Qelos backend (e.g. https://yourdomain.com).
39
+ */
40
+ appUrl: string;
41
+ /**
42
+ * Static API token used for service-to-service calls. When provided, no
43
+ * cookie/refresh-token handling is performed.
44
+ */
45
+ apiToken?: string;
46
+ /**
47
+ * Cookie name carrying the Qelos access token. Defaults to `q_access_token`.
48
+ */
49
+ accessTokenCookie?: string;
50
+ /**
51
+ * Cookie name carrying the Qelos refresh token. Defaults to `q_refresh_token`.
52
+ */
53
+ refreshTokenCookie?: string;
54
+ /**
55
+ * If true, the middleware short-circuits with 401 when the user cannot be
56
+ * resolved. Defaults to `false` — anonymous requests pass through with
57
+ * `request.qelos.user = null`.
58
+ *
59
+ * For more granular control, prefer `QelosAuthGuard` on the routes that
60
+ * need authentication.
61
+ */
62
+ requireAuth?: boolean;
63
+ /**
64
+ * Skip the middleware entirely for requests whose path starts with any of
65
+ * these prefixes. Useful for `/health`, `/api/_auth`, etc.
66
+ */
67
+ skipPaths?: string[];
68
+ /**
69
+ * Optional extra options merged into the per-request SDK instance.
70
+ */
71
+ sdkOptions?: Partial<QelosSDKOptions>;
72
+ }
73
+ export type WorkspaceResolver = (params: {
74
+ request: AnyRequest;
75
+ user: IUser;
76
+ workspaces: IWorkspace[];
77
+ }) => IWorkspace | null | Promise<IWorkspace | null>;
78
+ export interface TokenRefreshContext {
79
+ request: AnyRequest;
80
+ response: AnyResponse;
81
+ oldTokens: QelosTokenPair;
82
+ newTokens: ResolvedTokens;
83
+ sdk: QelosSDK;
84
+ }
85
+ export type TokenRefreshHook = (ctx: TokenRefreshContext) => void | Promise<void>;
86
+ export interface QelosRequestContext {
87
+ /**
88
+ * The authenticated user, or `null` when anonymous.
89
+ */
90
+ user: IUser | null;
91
+ /**
92
+ * The active workspace for the request, or `null` when none is active /
93
+ * the user is anonymous.
94
+ */
95
+ workspace: IWorkspace | null;
96
+ /**
97
+ * The full list of workspaces the user has access to.
98
+ */
99
+ workspaces: IWorkspace[];
100
+ /**
101
+ * SDK instance bound to the current request's tokens.
102
+ */
103
+ sdk: QelosSDK;
104
+ /**
105
+ * Tokens read from the request. Mutated in place when a refresh occurs so
106
+ * later code can read the current pair.
107
+ */
108
+ tokens: QelosTokenPair;
109
+ }
110
+ export interface QelosModuleOptions {
111
+ config: QelosNestConfig;
112
+ /**
113
+ * Hook invoked after a successful token refresh. The default implementation
114
+ * writes the new tokens back to the response cookies.
115
+ */
116
+ onTokenRefresh?: TokenRefreshHook;
117
+ /**
118
+ * Resolve the active workspace for a request. Defaults to picking the first
119
+ * workspace returned from `sdk.workspaces.getList()`.
120
+ */
121
+ resolveWorkspace?: WorkspaceResolver;
122
+ }
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,80 @@
1
+ {
2
+ "name": "@qelos/integrator-nest",
3
+ "version": "4.0.0",
4
+ "description": "NestJS module 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
+ "nest",
29
+ "nestjs",
30
+ "middleware",
31
+ "guard",
32
+ "integrator",
33
+ "auth"
34
+ ],
35
+ "author": "David Meir-Levy <davidmeirlevy@gmail.com>",
36
+ "license": "MIT",
37
+ "publishConfig": {
38
+ "access": "public"
39
+ },
40
+ "engines": {
41
+ "node": ">=18"
42
+ },
43
+ "peerDependencies": {
44
+ "@nestjs/common": "^9.0.0 || ^10.0.0 || ^11.0.0",
45
+ "@nestjs/core": "^9.0.0 || ^10.0.0 || ^11.0.0",
46
+ "reflect-metadata": "^0.1.13 || ^0.2.0",
47
+ "rxjs": "^7.0.0"
48
+ },
49
+ "peerDependenciesMeta": {
50
+ "@nestjs/common": {
51
+ "optional": false
52
+ },
53
+ "@nestjs/core": {
54
+ "optional": false
55
+ },
56
+ "reflect-metadata": {
57
+ "optional": false
58
+ },
59
+ "rxjs": {
60
+ "optional": false
61
+ }
62
+ },
63
+ "dependencies": {
64
+ "@qelos/sdk": "^4.0.0"
65
+ },
66
+ "devDependencies": {
67
+ "@nestjs/common": "^10.4.5",
68
+ "@nestjs/core": "^10.4.5",
69
+ "reflect-metadata": "^0.2.2",
70
+ "rxjs": "^7.8.1",
71
+ "tsx": "^4.21.0",
72
+ "typescript": "^5.6.3"
73
+ },
74
+ "scripts": {
75
+ "type-check": "tsc --noEmit",
76
+ "build": "tsc",
77
+ "pre-build": "tsc",
78
+ "test": "node --import tsx --test test/**/*.test.ts"
79
+ }
80
+ }