@hazeljs/auth 0.2.0-beta.8 → 0.2.0-beta.81

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,26 @@
1
+ import 'reflect-metadata';
2
+ /**
3
+ * Parameter decorator that injects the authenticated user from the request
4
+ * context into a controller method parameter.
5
+ *
6
+ * Requires JwtAuthGuard (or any guard that sets `req.user`) to run before
7
+ * the route handler. The value injected is the `AuthUser` object attached
8
+ * by the guard.
9
+ *
10
+ * @example
11
+ * ```ts
12
+ * @UseGuards(JwtAuthGuard)
13
+ * @Get('/profile')
14
+ * getProfile(@CurrentUser() user: AuthUser) {
15
+ * return user;
16
+ * }
17
+ *
18
+ * // Access a specific field:
19
+ * @Get('/me')
20
+ * whoAmI(@CurrentUser('role') role: string) {
21
+ * return { role };
22
+ * }
23
+ * ```
24
+ */
25
+ export declare function CurrentUser(field?: string): ParameterDecorator;
26
+ //# sourceMappingURL=current-user.decorator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"current-user.decorator.d.ts","sourceRoot":"","sources":["../../src/decorators/current-user.decorator.ts"],"names":[],"mappings":"AAAA,OAAO,kBAAkB,CAAC;AAI1B;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,WAAW,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,kBAAkB,CAe9D"}
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CurrentUser = CurrentUser;
4
+ require("reflect-metadata");
5
+ const INJECT_METADATA_KEY = 'hazel:inject';
6
+ /**
7
+ * Parameter decorator that injects the authenticated user from the request
8
+ * context into a controller method parameter.
9
+ *
10
+ * Requires JwtAuthGuard (or any guard that sets `req.user`) to run before
11
+ * the route handler. The value injected is the `AuthUser` object attached
12
+ * by the guard.
13
+ *
14
+ * @example
15
+ * ```ts
16
+ * @UseGuards(JwtAuthGuard)
17
+ * @Get('/profile')
18
+ * getProfile(@CurrentUser() user: AuthUser) {
19
+ * return user;
20
+ * }
21
+ *
22
+ * // Access a specific field:
23
+ * @Get('/me')
24
+ * whoAmI(@CurrentUser('role') role: string) {
25
+ * return { role };
26
+ * }
27
+ * ```
28
+ */
29
+ function CurrentUser(field) {
30
+ return (target, propertyKey, parameterIndex) => {
31
+ if (!propertyKey) {
32
+ throw new Error('@CurrentUser() must be used on a method parameter');
33
+ }
34
+ const constructor = target.constructor;
35
+ const injections = Reflect.getMetadata(INJECT_METADATA_KEY, constructor, propertyKey) ?? [];
36
+ injections[parameterIndex] = { type: 'user', field };
37
+ Reflect.defineMetadata(INJECT_METADATA_KEY, injections, constructor, propertyKey);
38
+ };
39
+ }
@@ -0,0 +1,24 @@
1
+ import { CanActivate, ExecutionContext } from '@hazeljs/core';
2
+ import { AuthService } from '../auth.service';
3
+ /**
4
+ * Guard that verifies a Bearer JWT token and attaches the decoded user to
5
+ * the request object. Use this with @UseGuards() on controllers or methods.
6
+ *
7
+ * @example
8
+ * ```ts
9
+ * @UseGuards(JwtAuthGuard)
10
+ * @Controller('/profile')
11
+ * export class ProfileController {
12
+ * @Get('/')
13
+ * getProfile(@CurrentUser() user: AuthUser) {
14
+ * return user;
15
+ * }
16
+ * }
17
+ * ```
18
+ */
19
+ export declare class JwtAuthGuard implements CanActivate {
20
+ private readonly authService;
21
+ constructor(authService: AuthService);
22
+ canActivate(context: ExecutionContext): Promise<boolean>;
23
+ }
24
+ //# sourceMappingURL=jwt-auth.guard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jwt-auth.guard.d.ts","sourceRoot":"","sources":["../../src/guards/jwt-auth.guard.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,WAAW,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAC1E,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAE9C;;;;;;;;;;;;;;;GAeG;AACH,qBACa,YAAa,YAAW,WAAW;IAClC,OAAO,CAAC,QAAQ,CAAC,WAAW;gBAAX,WAAW,EAAE,WAAW;IAE/C,WAAW,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,OAAO,CAAC;CA2B/D"}
@@ -0,0 +1,61 @@
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
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.JwtAuthGuard = void 0;
13
+ const core_1 = require("@hazeljs/core");
14
+ const auth_service_1 = require("../auth.service");
15
+ /**
16
+ * Guard that verifies a Bearer JWT token and attaches the decoded user to
17
+ * the request object. Use this with @UseGuards() on controllers or methods.
18
+ *
19
+ * @example
20
+ * ```ts
21
+ * @UseGuards(JwtAuthGuard)
22
+ * @Controller('/profile')
23
+ * export class ProfileController {
24
+ * @Get('/')
25
+ * getProfile(@CurrentUser() user: AuthUser) {
26
+ * return user;
27
+ * }
28
+ * }
29
+ * ```
30
+ */
31
+ let JwtAuthGuard = class JwtAuthGuard {
32
+ constructor(authService) {
33
+ this.authService = authService;
34
+ }
35
+ async canActivate(context) {
36
+ const req = context.switchToHttp().getRequest();
37
+ const authHeader = req.headers?.['authorization'];
38
+ if (!authHeader) {
39
+ const err = Object.assign(new Error('No authorization header'), { status: 400 });
40
+ throw err;
41
+ }
42
+ const [scheme, token] = authHeader.split(' ');
43
+ if (scheme?.toLowerCase() !== 'bearer' || !token) {
44
+ const err = Object.assign(new Error('Invalid authorization header format'), { status: 400 });
45
+ throw err;
46
+ }
47
+ const user = await this.authService.verifyToken(token);
48
+ if (!user) {
49
+ const err = Object.assign(new Error('Invalid or expired token'), { status: 401 });
50
+ throw err;
51
+ }
52
+ // Attach to req so the router propagates it to context.user and @CurrentUser() can read it.
53
+ req.user = user;
54
+ return true;
55
+ }
56
+ };
57
+ exports.JwtAuthGuard = JwtAuthGuard;
58
+ exports.JwtAuthGuard = JwtAuthGuard = __decorate([
59
+ (0, core_1.Injectable)(),
60
+ __metadata("design:paramtypes", [auth_service_1.AuthService])
61
+ ], JwtAuthGuard);
@@ -0,0 +1,36 @@
1
+ import { CanActivate, Type } from '@hazeljs/core';
2
+ import { RoleHierarchy, RoleHierarchyMap } from '../utils/role-hierarchy';
3
+ export interface RoleGuardOptions {
4
+ /**
5
+ * Custom role hierarchy to use instead of DEFAULT_ROLE_HIERARCHY.
6
+ * Pass a plain map or a RoleHierarchy instance.
7
+ */
8
+ hierarchy?: RoleHierarchyMap | RoleHierarchy;
9
+ }
10
+ /**
11
+ * Factory that returns a guard allowing only users whose role satisfies at
12
+ * least one of the provided roles — directly or via the role hierarchy.
13
+ *
14
+ * By default uses DEFAULT_ROLE_HIERARCHY so `admin` automatically passes a
15
+ * `manager` check, `manager` passes a `user` check, etc.
16
+ *
17
+ * Must be used after JwtAuthGuard (or any guard that attaches `user` to the
18
+ * request).
19
+ *
20
+ * @example
21
+ * ```ts
22
+ * // admin, manager, AND user can access this:
23
+ * @UseGuards(JwtAuthGuard, RoleGuard('user'))
24
+ *
25
+ * // Only admin and above (superadmin) can access this:
26
+ * @UseGuards(JwtAuthGuard, RoleGuard('admin'))
27
+ *
28
+ * // Custom hierarchy (no inheritance):
29
+ * @UseGuards(JwtAuthGuard, RoleGuard('admin', { hierarchy: {} }))
30
+ *
31
+ * // Multiple accepted roles:
32
+ * @UseGuards(JwtAuthGuard, RoleGuard('admin', 'moderator'))
33
+ * ```
34
+ */
35
+ export declare function RoleGuard(...args: Array<string | RoleGuardOptions>): Type<CanActivate>;
36
+ //# sourceMappingURL=role.guard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"role.guard.d.ts","sourceRoot":"","sources":["../../src/guards/role.guard.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,WAAW,EAAoB,IAAI,EAAE,MAAM,eAAe,CAAC;AAEhF,OAAO,EAAE,aAAa,EAA0B,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAElG,MAAM,WAAW,gBAAgB;IAC/B;;;OAGG;IACH,SAAS,CAAC,EAAE,gBAAgB,GAAG,aAAa,CAAC;CAC9C;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,SAAS,CAAC,GAAG,IAAI,EAAE,KAAK,CAAC,MAAM,GAAG,gBAAgB,CAAC,GAAG,IAAI,CAAC,WAAW,CAAC,CAwCtF"}
@@ -0,0 +1,66 @@
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.RoleGuard = RoleGuard;
10
+ const core_1 = require("@hazeljs/core");
11
+ const role_hierarchy_1 = require("../utils/role-hierarchy");
12
+ /**
13
+ * Factory that returns a guard allowing only users whose role satisfies at
14
+ * least one of the provided roles — directly or via the role hierarchy.
15
+ *
16
+ * By default uses DEFAULT_ROLE_HIERARCHY so `admin` automatically passes a
17
+ * `manager` check, `manager` passes a `user` check, etc.
18
+ *
19
+ * Must be used after JwtAuthGuard (or any guard that attaches `user` to the
20
+ * request).
21
+ *
22
+ * @example
23
+ * ```ts
24
+ * // admin, manager, AND user can access this:
25
+ * @UseGuards(JwtAuthGuard, RoleGuard('user'))
26
+ *
27
+ * // Only admin and above (superadmin) can access this:
28
+ * @UseGuards(JwtAuthGuard, RoleGuard('admin'))
29
+ *
30
+ * // Custom hierarchy (no inheritance):
31
+ * @UseGuards(JwtAuthGuard, RoleGuard('admin', { hierarchy: {} }))
32
+ *
33
+ * // Multiple accepted roles:
34
+ * @UseGuards(JwtAuthGuard, RoleGuard('admin', 'moderator'))
35
+ * ```
36
+ */
37
+ function RoleGuard(...args) {
38
+ // Separate trailing options object from role strings
39
+ const lastArg = args[args.length - 1];
40
+ const hasOptions = typeof lastArg === 'object' && lastArg !== null;
41
+ const roles = (hasOptions ? args.slice(0, -1) : args);
42
+ const options = (hasOptions ? lastArg : {});
43
+ const hierarchyInstance = options.hierarchy instanceof role_hierarchy_1.RoleHierarchy
44
+ ? options.hierarchy
45
+ : new role_hierarchy_1.RoleHierarchy(options.hierarchy ?? role_hierarchy_1.DEFAULT_ROLE_HIERARCHY);
46
+ let RoleGuardMixin = class RoleGuardMixin {
47
+ canActivate(context) {
48
+ const req = context.switchToHttp().getRequest();
49
+ const user = req.user;
50
+ if (!user) {
51
+ const err = Object.assign(new Error('Unauthorized'), { status: 401 });
52
+ throw err;
53
+ }
54
+ const permitted = roles.some((required) => hierarchyInstance.satisfies(user.role, required));
55
+ if (!permitted) {
56
+ const err = Object.assign(new Error(`Requires one of the following roles: ${roles.join(', ')}`), { status: 403 });
57
+ throw err;
58
+ }
59
+ return true;
60
+ }
61
+ };
62
+ RoleGuardMixin = __decorate([
63
+ (0, core_1.Injectable)()
64
+ ], RoleGuardMixin);
65
+ return RoleGuardMixin;
66
+ }
@@ -0,0 +1,54 @@
1
+ import { CanActivate, Type } from '@hazeljs/core';
2
+ export interface TenantGuardOptions {
3
+ /**
4
+ * Where to read the expected tenant ID from the incoming request.
5
+ *
6
+ * - `'param'` — URL segment, e.g. `/orgs/:tenantId/products` (default)
7
+ * - `'header'` — HTTP header, e.g. `X-Tenant-ID`
8
+ * - `'query'` — Query string, e.g. `?tenantId=acme`
9
+ *
10
+ * @default 'param'
11
+ */
12
+ source?: 'param' | 'header' | 'query';
13
+ /**
14
+ * Name of the param / header / query key that carries the tenant ID.
15
+ * @default 'tenantId'
16
+ */
17
+ key?: string;
18
+ /**
19
+ * Field on the authenticated user object that holds the user's tenant ID.
20
+ * @default 'tenantId'
21
+ */
22
+ userField?: string;
23
+ }
24
+ /**
25
+ * Factory that returns a guard enforcing tenant-level isolation.
26
+ *
27
+ * Compares the tenant ID carried by the request (from a URL param, header,
28
+ * or query param) against the tenant ID stored on the authenticated user
29
+ * (from the JWT payload). Returns 403 if they do not match.
30
+ *
31
+ * Must be used after JwtAuthGuard so that `req.user` is populated.
32
+ *
33
+ * @example
34
+ * ```ts
35
+ * // URL param: GET /orgs/:tenantId/invoices
36
+ * @UseGuards(JwtAuthGuard, TenantGuard())
37
+ * @Controller('/orgs/:tenantId/invoices')
38
+ * export class InvoicesController {}
39
+ *
40
+ * // Header: X-Org-ID
41
+ * @UseGuards(JwtAuthGuard, TenantGuard({ source: 'header', key: 'x-org-id' }))
42
+ * @Controller('/invoices')
43
+ * export class InvoicesController {}
44
+ *
45
+ * // Superadmins bypass tenant check:
46
+ * @UseGuards(JwtAuthGuard, TenantGuard({ bypassRoles: ['superadmin'] }))
47
+ * @Controller('/orgs/:tenantId/invoices')
48
+ * export class InvoicesController {}
49
+ * ```
50
+ */
51
+ export declare function TenantGuard(options?: TenantGuardOptions & {
52
+ bypassRoles?: string[];
53
+ }): Type<CanActivate>;
54
+ //# sourceMappingURL=tenant.guard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tenant.guard.d.ts","sourceRoot":"","sources":["../../src/guards/tenant.guard.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,WAAW,EAAoB,IAAI,EAAkB,MAAM,eAAe,CAAC;AAIhG,MAAM,WAAW,kBAAkB;IACjC;;;;;;;;OAQG;IACH,MAAM,CAAC,EAAE,OAAO,GAAG,QAAQ,GAAG,OAAO,CAAC;IAEtC;;;OAGG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;IAEb;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,WAAW,CACzB,OAAO,GAAE,kBAAkB,GAAG;IAAE,WAAW,CAAC,EAAE,MAAM,EAAE,CAAA;CAAO,GAC5D,IAAI,CAAC,WAAW,CAAC,CAsEnB"}
@@ -0,0 +1,96 @@
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.TenantGuard = TenantGuard;
10
+ const core_1 = require("@hazeljs/core");
11
+ const tenant_context_1 = require("../tenant/tenant-context");
12
+ /**
13
+ * Factory that returns a guard enforcing tenant-level isolation.
14
+ *
15
+ * Compares the tenant ID carried by the request (from a URL param, header,
16
+ * or query param) against the tenant ID stored on the authenticated user
17
+ * (from the JWT payload). Returns 403 if they do not match.
18
+ *
19
+ * Must be used after JwtAuthGuard so that `req.user` is populated.
20
+ *
21
+ * @example
22
+ * ```ts
23
+ * // URL param: GET /orgs/:tenantId/invoices
24
+ * @UseGuards(JwtAuthGuard, TenantGuard())
25
+ * @Controller('/orgs/:tenantId/invoices')
26
+ * export class InvoicesController {}
27
+ *
28
+ * // Header: X-Org-ID
29
+ * @UseGuards(JwtAuthGuard, TenantGuard({ source: 'header', key: 'x-org-id' }))
30
+ * @Controller('/invoices')
31
+ * export class InvoicesController {}
32
+ *
33
+ * // Superadmins bypass tenant check:
34
+ * @UseGuards(JwtAuthGuard, TenantGuard({ bypassRoles: ['superadmin'] }))
35
+ * @Controller('/orgs/:tenantId/invoices')
36
+ * export class InvoicesController {}
37
+ * ```
38
+ */
39
+ function TenantGuard(options = {}) {
40
+ const { source = 'param', key = 'tenantId', userField = 'tenantId', bypassRoles = [] } = options;
41
+ let TenantGuardMixin = class TenantGuardMixin {
42
+ canActivate(context) {
43
+ const req = context.switchToHttp().getRequest();
44
+ const ctx = context.switchToHttp().getContext();
45
+ const user = req.user;
46
+ if (!user) {
47
+ const err = Object.assign(new Error('Unauthorized'), { status: 401 });
48
+ throw err;
49
+ }
50
+ // Privileged roles can bypass the tenant check entirely.
51
+ // Still seed the context so their own queries are naturally scoped.
52
+ if (bypassRoles.includes(user.role)) {
53
+ const bypassTenantId = user[userField];
54
+ if (bypassTenantId)
55
+ tenant_context_1.TenantContext.enterWith(bypassTenantId);
56
+ return true;
57
+ }
58
+ const userTenantId = user[userField];
59
+ if (!userTenantId) {
60
+ const err = Object.assign(new Error(`User is not associated with any tenant (missing "${userField}")`), { status: 403 });
61
+ throw err;
62
+ }
63
+ // Extract the request-level tenant ID from the configured source.
64
+ let requestTenantId;
65
+ switch (source) {
66
+ case 'param':
67
+ requestTenantId = ctx.params?.[key];
68
+ break;
69
+ case 'header':
70
+ requestTenantId = ctx.headers?.[key.toLowerCase()];
71
+ break;
72
+ case 'query':
73
+ requestTenantId = ctx.query?.[key];
74
+ break;
75
+ }
76
+ if (!requestTenantId) {
77
+ const err = Object.assign(new Error(`Tenant ID not found in request ${source} "${key}"`), {
78
+ status: 400,
79
+ });
80
+ throw err;
81
+ }
82
+ if (userTenantId !== requestTenantId) {
83
+ const err = Object.assign(new Error('Access denied: resource belongs to a different tenant'), { status: 403 });
84
+ throw err;
85
+ }
86
+ // Seed AsyncLocalStorage so repositories can call tenantCtx.requireId()
87
+ // without needing tenantId passed through every function parameter.
88
+ tenant_context_1.TenantContext.enterWith(userTenantId);
89
+ return true;
90
+ }
91
+ };
92
+ TenantGuardMixin = __decorate([
93
+ (0, core_1.Injectable)()
94
+ ], TenantGuardMixin);
95
+ return TenantGuardMixin;
96
+ }
package/dist/index.d.ts CHANGED
@@ -1,8 +1,18 @@
1
1
  /**
2
2
  * @hazeljs/auth - Authentication module for HazelJS
3
3
  */
4
- export { AuthGuard } from './auth.guard';
4
+ export { AuthGuard, Auth } from './auth.guard';
5
5
  export { AuthService } from './auth.service';
6
+ export type { AuthUser } from './auth.service';
6
7
  export { JwtModule } from './jwt/jwt.module';
7
8
  export { JwtService } from './jwt/jwt.service';
9
+ export { JwtAuthGuard } from './guards/jwt-auth.guard';
10
+ export { RoleGuard } from './guards/role.guard';
11
+ export type { RoleGuardOptions } from './guards/role.guard';
12
+ export { TenantGuard } from './guards/tenant.guard';
13
+ export type { TenantGuardOptions } from './guards/tenant.guard';
14
+ export { TenantContext } from './tenant/tenant-context';
15
+ export { RoleHierarchy, DEFAULT_ROLE_HIERARCHY } from './utils/role-hierarchy';
16
+ export type { RoleHierarchyMap } from './utils/role-hierarchy';
17
+ export { CurrentUser } from './decorators/current-user.decorator';
8
18
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC7C,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC7C,YAAY,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC/C,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC7C,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AACvD,OAAO,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAChD,YAAY,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AACpD,YAAY,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AACxD,OAAO,EAAE,aAAa,EAAE,sBAAsB,EAAE,MAAM,wBAAwB,CAAC;AAC/E,YAAY,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC/D,OAAO,EAAE,WAAW,EAAE,MAAM,qCAAqC,CAAC"}
package/dist/index.js CHANGED
@@ -3,12 +3,26 @@
3
3
  * @hazeljs/auth - Authentication module for HazelJS
4
4
  */
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.JwtService = exports.JwtModule = exports.AuthService = exports.AuthGuard = void 0;
6
+ exports.CurrentUser = exports.DEFAULT_ROLE_HIERARCHY = exports.RoleHierarchy = exports.TenantContext = exports.TenantGuard = exports.RoleGuard = exports.JwtAuthGuard = exports.JwtService = exports.JwtModule = exports.AuthService = exports.Auth = exports.AuthGuard = void 0;
7
7
  var auth_guard_1 = require("./auth.guard");
8
8
  Object.defineProperty(exports, "AuthGuard", { enumerable: true, get: function () { return auth_guard_1.AuthGuard; } });
9
+ Object.defineProperty(exports, "Auth", { enumerable: true, get: function () { return auth_guard_1.Auth; } });
9
10
  var auth_service_1 = require("./auth.service");
10
11
  Object.defineProperty(exports, "AuthService", { enumerable: true, get: function () { return auth_service_1.AuthService; } });
11
12
  var jwt_module_1 = require("./jwt/jwt.module");
12
13
  Object.defineProperty(exports, "JwtModule", { enumerable: true, get: function () { return jwt_module_1.JwtModule; } });
13
14
  var jwt_service_1 = require("./jwt/jwt.service");
14
15
  Object.defineProperty(exports, "JwtService", { enumerable: true, get: function () { return jwt_service_1.JwtService; } });
16
+ var jwt_auth_guard_1 = require("./guards/jwt-auth.guard");
17
+ Object.defineProperty(exports, "JwtAuthGuard", { enumerable: true, get: function () { return jwt_auth_guard_1.JwtAuthGuard; } });
18
+ var role_guard_1 = require("./guards/role.guard");
19
+ Object.defineProperty(exports, "RoleGuard", { enumerable: true, get: function () { return role_guard_1.RoleGuard; } });
20
+ var tenant_guard_1 = require("./guards/tenant.guard");
21
+ Object.defineProperty(exports, "TenantGuard", { enumerable: true, get: function () { return tenant_guard_1.TenantGuard; } });
22
+ var tenant_context_1 = require("./tenant/tenant-context");
23
+ Object.defineProperty(exports, "TenantContext", { enumerable: true, get: function () { return tenant_context_1.TenantContext; } });
24
+ var role_hierarchy_1 = require("./utils/role-hierarchy");
25
+ Object.defineProperty(exports, "RoleHierarchy", { enumerable: true, get: function () { return role_hierarchy_1.RoleHierarchy; } });
26
+ Object.defineProperty(exports, "DEFAULT_ROLE_HIERARCHY", { enumerable: true, get: function () { return role_hierarchy_1.DEFAULT_ROLE_HIERARCHY; } });
27
+ var current_user_decorator_1 = require("./decorators/current-user.decorator");
28
+ Object.defineProperty(exports, "CurrentUser", { enumerable: true, get: function () { return current_user_decorator_1.CurrentUser; } });
@@ -56,6 +56,6 @@ let JwtService = JwtService_1 = class JwtService {
56
56
  exports.JwtService = JwtService;
57
57
  JwtService.moduleOptions = {};
58
58
  exports.JwtService = JwtService = JwtService_1 = __decorate([
59
- (0, core_1.Injectable)(),
59
+ (0, core_1.Service)(),
60
60
  __metadata("design:paramtypes", [])
61
61
  ], JwtService);
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Provides request-scoped tenant context via AsyncLocalStorage.
3
+ *
4
+ * ─── Why two layers? ──────────────────────────────────────────────────────────
5
+ * TenantGuard enforces isolation at the HTTP layer — it rejects requests where
6
+ * the JWT tenant doesn't match the route tenant. That's necessary but not
7
+ * sufficient: a bug in service code could still query another tenant's rows if
8
+ * the guard is misconfigured or skipped.
9
+ *
10
+ * TenantContext closes that gap at the DATA layer. After the guard validates
11
+ * the request, it calls TenantContext.enterWith(tenantId) which seeds an
12
+ * AsyncLocalStorage store for the remainder of the request's async call chain.
13
+ * Every repository/service that injects TenantContext can then call
14
+ * requireId() without receiving tenantId as a function parameter.
15
+ *
16
+ * ─── Usage in a repository ───────────────────────────────────────────────────
17
+ * ```ts
18
+ * @Service()
19
+ * export class OrdersRepository {
20
+ * constructor(private readonly tenantCtx: TenantContext) {}
21
+ *
22
+ * findAll() {
23
+ * const tenantId = this.tenantCtx.requireId();
24
+ * return db.query('SELECT * FROM orders WHERE tenant_id = $1', [tenantId]);
25
+ * }
26
+ *
27
+ * findById(id: string) {
28
+ * const tenantId = this.tenantCtx.requireId();
29
+ * // Even a direct ID lookup is scoped — prevents horizontal privilege escalation
30
+ * return db.query(
31
+ * 'SELECT * FROM orders WHERE id = $1 AND tenant_id = $2',
32
+ * [id, tenantId]
33
+ * );
34
+ * }
35
+ * }
36
+ * ```
37
+ *
38
+ * ─── How it propagates ────────────────────────────────────────────────────────
39
+ * Node.js AsyncLocalStorage propagates through the async call graph.
40
+ * TenantGuard calls enterWith() during guard execution; because the route
41
+ * handler is called afterwards in the same async chain, it (and everything
42
+ * it awaits) automatically has access to the stored tenant ID.
43
+ */
44
+ export declare class TenantContext {
45
+ /**
46
+ * Returns the current tenant ID, or `undefined` if called outside a
47
+ * request context (e.g. during startup or in a background job).
48
+ */
49
+ getId(): string | undefined;
50
+ /**
51
+ * Returns the current tenant ID and throws if it is not set.
52
+ *
53
+ * Use this in any repository or service method that must never run without
54
+ * a tenant — it acts as a last-resort safety net even if the guard was
55
+ * accidentally omitted on a route.
56
+ */
57
+ requireId(): string;
58
+ /**
59
+ * Seeds the current async context with the given tenant ID.
60
+ *
61
+ * Called internally by TenantGuard after it validates the request.
62
+ * Unlike `run()`, `enterWith` propagates through the rest of the current
63
+ * async execution chain without requiring a wrapping callback — which
64
+ * makes it the right tool for seeding from within a guard.
65
+ */
66
+ static enterWith(tenantId: string): void;
67
+ /**
68
+ * Wraps a callback so that everything inside it (and all async operations
69
+ * it spawns) runs with the given tenant ID in context.
70
+ *
71
+ * Use this when you need explicit scoping, e.g. in background jobs or tests:
72
+ *
73
+ * ```ts
74
+ * await TenantContext.run('acme', async () => {
75
+ * await ordersService.processPendingOrders();
76
+ * });
77
+ * ```
78
+ */
79
+ static run<T>(tenantId: string, fn: () => T): T;
80
+ }
81
+ //# sourceMappingURL=tenant-context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tenant-context.d.ts","sourceRoot":"","sources":["../../src/tenant/tenant-context.ts"],"names":[],"mappings":"AASA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AACH,qBACa,aAAa;IACxB;;;OAGG;IACH,KAAK,IAAI,MAAM,GAAG,SAAS;IAI3B;;;;;;OAMG;IACH,SAAS,IAAI,MAAM;IAanB;;;;;;;OAOG;IACH,MAAM,CAAC,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAIxC;;;;;;;;;;;OAWG;IACH,MAAM,CAAC,GAAG,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC;CAGhD"}