najm-guard 0.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,201 @@
1
+ import * as najm_core from 'najm-core';
2
+ import { Constructor } from 'najm-core';
3
+ export { GUARD_CODES, GuardError } from 'najm-core';
4
+ import * as diject from 'diject';
5
+
6
+ /**
7
+ * Structured guard result for automatic ALS token setting.
8
+ * Return this from canActivate() to automatically set tokens.
9
+ *
10
+ * @example
11
+ * ```ts
12
+ * async canActivate(@Headers('auth') token: string) {
13
+ * const user = await this.auth.verify(token);
14
+ * if (!user) return false;
15
+ * return { user, role: 'admin', permissions: ['read', 'write'] }; // Auto-sets tokens
16
+ * }
17
+ * ```
18
+ */
19
+ interface GuardResult {
20
+ /** User data → accessible via @User() decorator */
21
+ user?: any;
22
+ /** Owner data → accessible via @Owner() decorator */
23
+ owner?: any;
24
+ /** Additional info → accessible via @Info() decorator */
25
+ info?: any;
26
+ /** Generic data storage → accessible via @Data() decorator */
27
+ data?: any;
28
+ /** Filter criteria → accessible via @Filter() decorator */
29
+ filter?: any;
30
+ /** User role → accessible via @Role() decorator */
31
+ role?: string;
32
+ /** User permissions → accessible via @Permissions() decorator */
33
+ permissions?: string[];
34
+ }
35
+ /**
36
+ * Guard method return type
37
+ * - `false` → Guard failed, request rejected
38
+ * - `true` → Guard passed, no tokens set
39
+ * - `GuardResult` → Guard passed, tokens auto-set
40
+ */
41
+ type GuardReturnType = boolean | GuardResult;
42
+ /**
43
+ * Guard metadata stored by decorators
44
+ */
45
+ interface GuardMetadata {
46
+ guardClass: Constructor;
47
+ methodName: string;
48
+ params?: any;
49
+ }
50
+ /**
51
+ * Plugin configuration
52
+ */
53
+ type GuardPluginConfig = boolean | {
54
+ /** Enable/disable guard plugin */
55
+ enabled?: boolean;
56
+ /** Paths to exclude from guards (supports wildcards) */
57
+ exclude?: string[];
58
+ };
59
+ /**
60
+ * Composite guard params for AND/OR logic
61
+ */
62
+ interface CompositeGuardParams {
63
+ type: 'AND' | 'OR';
64
+ guards: GuardMetadata[];
65
+ }
66
+ /**
67
+ * Options for createGuard factory
68
+ */
69
+ interface CreateGuardOptions {
70
+ methodName?: string;
71
+ }
72
+
73
+ /**
74
+ * User data populated by authentication guards
75
+ * Use generic type to maintain type safety in your app:
76
+ *
77
+ * @example
78
+ * ```ts
79
+ * // In your app
80
+ * interface UserData { id: string; roles: string[]; }
81
+ *
82
+ * @Service()
83
+ * class AuthGuard {
84
+ * canActivate(@Headers('auth') token: string, @Ctx() ctx: Context): boolean {
85
+ * const user = verifyToken(token);
86
+ * ctx.set(USER.name, user); // Populate user context
87
+ * return !!user;
88
+ * }
89
+ * }
90
+ *
91
+ * // In your controller
92
+ * @Get('/profile')
93
+ * getProfile(@User() user: UserData) { // Type-safe!
94
+ * return { id: user.id };
95
+ * }
96
+ * ```
97
+ */
98
+ declare const USER: diject.AlsToken<any>;
99
+ /**
100
+ * User role populated by authorization guards
101
+ * Use to get the current user's role
102
+ *
103
+ * @example
104
+ * ```ts
105
+ * @Get('/admin')
106
+ * async getAdmin(@Role() role: string) {
107
+ * if (role !== 'admin') throw new HttpError(403, 'Forbidden');
108
+ * return { data: 'admin-only' };
109
+ * }
110
+ * ```
111
+ */
112
+ declare const ROLE: diject.AlsToken<string>;
113
+ /**
114
+ * User permissions populated by authorization guards
115
+ * Use to get the current user's permissions
116
+ *
117
+ * @example
118
+ * ```ts
119
+ * @Get('/edit/:id')
120
+ * async editResource(
121
+ * @Params('id') id: string,
122
+ * @Permissions() permissions: string[]
123
+ * ) {
124
+ * if (!permissions.includes('edit:resource')) {
125
+ * throw new HttpError(403, 'Forbidden');
126
+ * }
127
+ * return { id };
128
+ * }
129
+ * ```
130
+ */
131
+ declare const PERMISSIONS: diject.AlsToken<string[]>;
132
+ /**
133
+ * Resource owner data (for ownership-based guards)
134
+ */
135
+ declare const OWNER: diject.AlsToken<any>;
136
+ /**
137
+ * Additional info populated by guards
138
+ */
139
+ declare const INFO: diject.AlsToken<any>;
140
+ /**
141
+ * Generic data storage for guards
142
+ */
143
+ declare const DATA: diject.AlsToken<any>;
144
+ /**
145
+ * Filter criteria (for query/permission filtering)
146
+ */
147
+ declare const FILTER: diject.AlsToken<any>;
148
+ /**
149
+ * Guard parameters passed to guard decorators
150
+ * This is populated automatically by the guard system
151
+ */
152
+ declare const GUARD_PARAMS: diject.AlsToken<any>;
153
+ /**
154
+ * Metadata key for guards applied to class/method
155
+ */
156
+ declare const GUARDS_META: unique symbol;
157
+ /**
158
+ * Plugin config token
159
+ */
160
+ declare const GUARD_CONFIG: unique symbol;
161
+
162
+ declare function createGuard<TParams = void>(guardClass: Constructor, options?: CreateGuardOptions): TParams extends void ? () => ClassDecorator & MethodDecorator : (params: TParams) => ClassDecorator & MethodDecorator;
163
+ /**
164
+ * Compose multiple guard decorators to run sequentially.
165
+ * All guards must pass for access to be granted.
166
+ *
167
+ * @example
168
+ * export const isAdmin = composeGuards(isAuth(), Role(ROLES.ADMIN));
169
+ *
170
+ * @usage
171
+ * @isAdmin() // Note: called as a function
172
+ * method() { ... }
173
+ */
174
+ declare function composeGuards(...decorators: (ClassDecorator & MethodDecorator)[]): () => ClassDecorator & MethodDecorator;
175
+ declare function getGuardMetadata(target: any, methodName?: string): GuardMetadata[];
176
+ declare function hasGuards(target: any, methodName?: string): boolean;
177
+
178
+ declare const guards: (config?: GuardPluginConfig) => najm_core.NajmPlugin;
179
+ declare const GuardPlugin: (config?: GuardPluginConfig) => najm_core.NajmPlugin;
180
+
181
+ declare class GuardService {
182
+ private container;
183
+ private scanner;
184
+ private config;
185
+ private log;
186
+ private guardCount;
187
+ private resolver;
188
+ private enabled;
189
+ private excludePaths;
190
+ onInit(): void;
191
+ scan(): Promise<void>;
192
+ configure(): Promise<void>;
193
+ activate(): Promise<void>;
194
+ onReady(): Promise<void>;
195
+ private createGuardsMiddleware;
196
+ private isPathExcluded;
197
+ private executeGuard;
198
+ private processGuardResult;
199
+ }
200
+
201
+ export { type CompositeGuardParams, type CreateGuardOptions, DATA, FILTER, GUARDS_META, GUARD_CONFIG, GUARD_PARAMS, type GuardMetadata, GuardPlugin, type GuardPluginConfig, type GuardResult, type GuardReturnType, GuardService, INFO, OWNER, PERMISSIONS, ROLE, USER, composeGuards, createGuard, getGuardMetadata, guards, hasGuards };
package/dist/index.mjs ADDED
@@ -0,0 +1,277 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
3
+
4
+ // src/tokens.ts
5
+ import { createAlsToken } from "najm-core";
6
+ var USER = createAlsToken("user");
7
+ var ROLE = createAlsToken("role");
8
+ var PERMISSIONS = createAlsToken("permissions");
9
+ var OWNER = createAlsToken("owner");
10
+ var INFO = createAlsToken("info");
11
+ var DATA = createAlsToken("data");
12
+ var FILTER = createAlsToken("filter");
13
+ var GUARD_PARAMS = createAlsToken("guardParams");
14
+ var GUARDS_META = /* @__PURE__ */ Symbol("guards:meta");
15
+ var GUARD_CONFIG = /* @__PURE__ */ Symbol("GUARD_CONFIG");
16
+
17
+ // src/decorator.ts
18
+ import { MetaHelper } from "najm-core";
19
+ function createGuard(guardClass, options) {
20
+ const methodName = options?.methodName ?? "canActivate";
21
+ return ((params) => {
22
+ return function(target, propertyKey) {
23
+ const metadata = {
24
+ guardClass,
25
+ methodName,
26
+ params
27
+ };
28
+ if (propertyKey) {
29
+ MetaHelper.appendMany(GUARDS_META, [metadata], target, propertyKey);
30
+ } else {
31
+ MetaHelper.appendMany(GUARDS_META, [metadata], target);
32
+ }
33
+ };
34
+ });
35
+ }
36
+ __name(createGuard, "createGuard");
37
+ function composeGuards(...decorators) {
38
+ return () => {
39
+ return function(target, propertyKey, descriptor) {
40
+ for (const decoratorFn of decorators) {
41
+ if (propertyKey !== void 0) {
42
+ decoratorFn(target, propertyKey, descriptor);
43
+ } else {
44
+ decoratorFn(target);
45
+ }
46
+ }
47
+ };
48
+ };
49
+ }
50
+ __name(composeGuards, "composeGuards");
51
+ function getGuardMetadata(target, methodName) {
52
+ if (methodName) {
53
+ return MetaHelper.get(GUARDS_META, target.prototype, methodName) || [];
54
+ }
55
+ return MetaHelper.get(GUARDS_META, target) || [];
56
+ }
57
+ __name(getGuardMetadata, "getGuardMetadata");
58
+ function hasGuards(target, methodName) {
59
+ return getGuardMetadata(target, methodName).length > 0;
60
+ }
61
+ __name(hasGuards, "hasGuards");
62
+
63
+ // src/GuardPlugin.ts
64
+ import { plugin } from "najm-core";
65
+
66
+ // src/GuardService.ts
67
+ import { Service, Meta, DI, Container, Inject } from "najm-core";
68
+ import { ScannerService, Scan, ScanType, INJECTION_TYPES, Err, LOGGER } from "najm-core";
69
+ import { ParamResolver } from "najm-core";
70
+ var __decorate = function(decorators, target, key, desc) {
71
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
72
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
73
+ 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;
74
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
75
+ };
76
+ var __metadata = function(k, v) {
77
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
78
+ };
79
+ var _a;
80
+ var _b;
81
+ var GuardService = class GuardService2 {
82
+ static {
83
+ __name(this, "GuardService");
84
+ }
85
+ container;
86
+ scanner;
87
+ config;
88
+ log;
89
+ guardCount = 0;
90
+ resolver;
91
+ enabled = true;
92
+ excludePaths = [];
93
+ // ============================================================================
94
+ // LIFECYCLE: SCAN
95
+ // ============================================================================
96
+ onInit() {
97
+ this.enabled = typeof this.config === "boolean" ? this.config : this.config.enabled ?? true;
98
+ this.excludePaths = typeof this.config === "object" ? this.config.exclude || [] : [];
99
+ this.guardCount = 0;
100
+ }
101
+ async scan() {
102
+ if (!this.enabled)
103
+ return;
104
+ this.scanner.scan(ScanType.CONTROLLER, {
105
+ onClass: /* @__PURE__ */ __name((controller) => {
106
+ const guards2 = getGuardMetadata(controller);
107
+ if (guards2?.length) {
108
+ this.container.setInjection({
109
+ type: INJECTION_TYPES.MIDDLEWARE,
110
+ target: controller,
111
+ handler: this.createGuardsMiddleware(guards2),
112
+ order: 50,
113
+ source: "guard"
114
+ });
115
+ this.guardCount += guards2.length;
116
+ }
117
+ }, "onClass"),
118
+ onMethod: /* @__PURE__ */ __name((controller, methodName) => {
119
+ const guards2 = getGuardMetadata(controller, methodName);
120
+ if (guards2?.length) {
121
+ this.container.setInjection({
122
+ type: INJECTION_TYPES.MIDDLEWARE,
123
+ target: controller,
124
+ methodName,
125
+ handler: this.createGuardsMiddleware(guards2),
126
+ order: 50,
127
+ source: "guard"
128
+ });
129
+ this.guardCount += guards2.length;
130
+ }
131
+ }, "onMethod")
132
+ });
133
+ }
134
+ // ============================================================================
135
+ // LIFECYCLE: CONFIGURE, ACTIVATE, READY
136
+ // ============================================================================
137
+ async configure() {
138
+ if (!this.enabled)
139
+ return;
140
+ this.resolver = new ParamResolver(this.container);
141
+ }
142
+ async activate() {
143
+ }
144
+ async onReady() {
145
+ if (!this.enabled) {
146
+ this.log.info("Guard plugin disabled");
147
+ return;
148
+ }
149
+ this.log.info(`Guard plugin ready: ${this.guardCount} guard(s) registered`);
150
+ }
151
+ // ============================================================================
152
+ // MIDDLEWARE
153
+ // ============================================================================
154
+ createGuardsMiddleware(guards2) {
155
+ return async (context, next) => {
156
+ const path = context.req.path;
157
+ if (this.isPathExcluded(path)) {
158
+ return next();
159
+ }
160
+ try {
161
+ for (const guard of guards2) {
162
+ const canActivate = await this.executeGuard(guard);
163
+ if (!canActivate) {
164
+ throw Err.unauthorized();
165
+ }
166
+ }
167
+ await next();
168
+ } catch (error) {
169
+ return Err.handle(error);
170
+ }
171
+ };
172
+ }
173
+ // ============================================================================
174
+ // PATH EXCLUSION
175
+ // ============================================================================
176
+ isPathExcluded(path) {
177
+ return this.excludePaths.some((pattern) => {
178
+ if (pattern === path)
179
+ return true;
180
+ if (pattern.endsWith("/*")) {
181
+ const prefix = pattern.slice(0, -2);
182
+ return path.startsWith(prefix);
183
+ }
184
+ if (pattern.endsWith("/**")) {
185
+ const prefix = pattern.slice(0, -3);
186
+ return path.startsWith(prefix);
187
+ }
188
+ return false;
189
+ });
190
+ }
191
+ // ============================================================================
192
+ // GUARD EXECUTION (unchanged)
193
+ // ============================================================================
194
+ async executeGuard(guard) {
195
+ const { guardClass, methodName = "canActivate", params } = guard;
196
+ const instance = await this.container.resolve(guardClass);
197
+ const method = instance[methodName];
198
+ const result = await this.container.run({ guardParams: params }, async () => {
199
+ const args = await this.resolver.resolveArgs(method);
200
+ return method.call(instance, ...args);
201
+ });
202
+ return this.processGuardResult(result);
203
+ }
204
+ processGuardResult(result) {
205
+ if (typeof result === "boolean")
206
+ return result;
207
+ if (result == null)
208
+ return false;
209
+ if (typeof result === "object") {
210
+ const { user, owner, info, data, filter, role, permissions } = result;
211
+ if (user !== void 0)
212
+ this.container.set(USER, user);
213
+ if (owner !== void 0)
214
+ this.container.set(OWNER, owner);
215
+ if (info !== void 0)
216
+ this.container.set(INFO, info);
217
+ if (data !== void 0)
218
+ this.container.set(DATA, data);
219
+ if (filter !== void 0)
220
+ this.container.set(FILTER, filter);
221
+ if (role !== void 0)
222
+ this.container.set(ROLE, role);
223
+ if (permissions !== void 0)
224
+ this.container.set(PERMISSIONS, permissions);
225
+ return true;
226
+ }
227
+ return false;
228
+ }
229
+ };
230
+ __decorate([
231
+ DI(),
232
+ __metadata("design:type", typeof (_a = typeof Container !== "undefined" && Container) === "function" ? _a : Object)
233
+ ], GuardService.prototype, "container", void 0);
234
+ __decorate([
235
+ Scan(),
236
+ __metadata("design:type", typeof (_b = typeof ScannerService !== "undefined" && ScannerService) === "function" ? _b : Object)
237
+ ], GuardService.prototype, "scanner", void 0);
238
+ __decorate([
239
+ Inject(GUARD_CONFIG),
240
+ __metadata("design:type", Object)
241
+ ], GuardService.prototype, "config", void 0);
242
+ __decorate([
243
+ Inject(LOGGER),
244
+ __metadata("design:type", Object)
245
+ ], GuardService.prototype, "log", void 0);
246
+ GuardService = __decorate([
247
+ Service(),
248
+ Meta({ layer: "plugin" })
249
+ ], GuardService);
250
+
251
+ // src/GuardPlugin.ts
252
+ var guards = /* @__PURE__ */ __name((config = true) => plugin("guards").services(GuardService).config(GUARD_CONFIG, config).build(), "guards");
253
+ var GuardPlugin = guards;
254
+
255
+ // src/index.ts
256
+ import { GuardError, GUARD_CODES } from "najm-core";
257
+ export {
258
+ DATA,
259
+ FILTER,
260
+ GUARDS_META,
261
+ GUARD_CODES,
262
+ GUARD_CONFIG,
263
+ GUARD_PARAMS,
264
+ GuardError,
265
+ GuardPlugin,
266
+ GuardService,
267
+ INFO,
268
+ OWNER,
269
+ PERMISSIONS,
270
+ ROLE,
271
+ USER,
272
+ composeGuards,
273
+ createGuard,
274
+ getGuardMetadata,
275
+ guards,
276
+ hasGuards
277
+ };
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "najm-guard",
3
+ "version": "0.1.1",
4
+ "type": "module",
5
+ "files": [
6
+ "dist"
7
+ ],
8
+ "main": "./dist/index.mjs",
9
+ "types": "./dist/index.d.ts",
10
+ "exports": {
11
+ ".": {
12
+ "bun": "./src/index.ts",
13
+ "types": "./dist/index.d.ts",
14
+ "import": "./dist/index.mjs",
15
+ "default": "./dist/index.mjs"
16
+ },
17
+ "./*": {
18
+ "bun": "./src/*.ts",
19
+ "types": "./src/*.ts",
20
+ "import": "./src/*.ts",
21
+ "default": "./src/*.ts"
22
+ }
23
+ },
24
+ "scripts": {
25
+ "build": "tsup",
26
+ "test": "bun test",
27
+ "test:watch": "bun test --watch",
28
+ "clean": "rimraf dist tsconfig.tsbuildinfo"
29
+ },
30
+ "dependencies": {
31
+ "najm-core": "^0.1.1"
32
+ },
33
+ "peerDependencies": {
34
+ "hono": "^4.0.0"
35
+ },
36
+ "devDependencies": {
37
+ "typescript": "^5.7.3",
38
+ "rimraf": "^6.0.1"
39
+ }
40
+ }