@flusys/nestjs-auth 9.1.0 → 9.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.
package/README.md CHANGED
@@ -49,6 +49,8 @@ AuthModule.forRoot({
49
49
  });
50
50
  ```
51
51
 
52
+ Browser logins get the refresh token as an HttpOnly cookie lasting `refreshTokenExpiration`. Sending `rememberMe: false` in the body of `POST /auth/login` or `POST /auth/select` makes it a session cookie instead (dropped when the browser closes).
53
+
52
54
  #### Mode 2: Multi-Tenant
53
55
 
54
56
  In multi-tenant mode you provide:
@@ -333,6 +335,22 @@ then works. Signing **in** is never affected by either — only account creation
333
335
  A cache instance is required: `AuthModule` imports `CacheModule`, so this is
334
336
  satisfied by default.
335
337
 
338
+ ## 6. Branch Hierarchy
339
+
340
+ Branches nest through `parentId`. A parent must be in the same company and cannot create a cycle, and deleting a branch also deletes every branch under it. `BranchService` exposes the hierarchy, each lookup taking two queries whatever the depth:
341
+
342
+ - `getDescendantIds(branchIds, { includeSelf?, maxDepth? })` - every branch under the given one(s), level by level
343
+ - `getAncestorIds(branchId, { includeSelf?, maxDepth? })` - the parent chain, nearest parent first
344
+ - `isWithinBranch(branchId, ancestorId)`
345
+
346
+ Only existing, not soft-deleted branches are returned (with `includeSelf` too); empty or non-UUID ids match nothing and never reach the database.
347
+
348
+ With the company feature on, the module also provides and exports `BRANCH_HIERARCHY_RESOLVER` (`BranchHierarchyResolverAdapter`), which adds `getGrantedBranchIds(userId, companyId?)`, `getGrantedDescendantIds(userId, branchIds, options?)` and `hasBranchAccess(userId, branchId)`. Branch grants are explicit: each checkbox in the permission dialog grants exactly that branch, and a grant on a parent never reaches a child that is not granted itself (Parent and Level 2 granted, Level 1 not: picking Parent scopes to Parent + Level 2, never Level 1). Granted branches are exactly what company select offers. `auth.module.scope.spec.ts` keeps both resolvers singletons. Login, company select and switch offer and accept only granted branches. The login selection response, `get-my-company-branches` (empty for an inactive company, like company select) and `administration/permissions/get-user-branch` carry each branch's `parentId`, `serial` and `displayParentId` - the nearest ancestor that is also in the list (granted), or null (`loadDisplayParentIds`). Clients nest by it, so a granted branch under an ungranted parent sits under its nearest granted ancestor, and the ids of ungranted branches in between are never sent; `get-user-branch` filters to the `companyId` sent, or to the caller's current company when it is omitted or empty. See the nestjs-shared README (Branch Hierarchy) for the helpers and an example.
349
+
350
+ ## 7. Deactivating a User
351
+
352
+ With the company feature off, `UserService.updateStatus` sets `AppUser.isActive`, which blocks login, token refresh and every request. With it on, it deactivates the user in the admin's current company only, by setting `isActive` on that company grant (`UserCompanyPermission`, `permissionType: company`). Login, company select, select/switch, token refresh, `get-my-companies` / `get-my-company-branches` and `COMPANY_ACCESS_RESOLVER` all skip an inactive company grant, and deactivating (in either mode) also ends every session of the user at once, the same way logout does (`revokeUserSessions`, checked on refresh and by `JwtStrategy` on every request). The user can log straight back in to the companies they are still active in. A user with company grants never gets a company-less login (such a token is not filtered by company): login is refused when they are deactivated in every company (`auth.login.account.deactivated`) or none of their companies is active (`auth.company.not.found`), and a company-less refresh token is not renewed once the user has company grants. Branch grants carry no status of their own.
353
+
336
354
  ## License
337
355
 
338
356
  MIT © FLUSYS
@@ -88,6 +88,8 @@ export declare const COMPANY_MESSAGES: {
88
88
  };
89
89
  export declare const BRANCH_MESSAGES: {
90
90
  readonly READONLY_DELETE_FORBIDDEN: "branch.readonly.delete.forbidden";
91
+ readonly INVALID_PARENT: "branch.invalid.parent";
92
+ readonly CIRCULAR_PARENT: "branch.circular.parent";
91
93
  };
92
94
  export declare const USER_PERMISSION_MESSAGES: {
93
95
  readonly COMPANY_LIST_SUCCESS: "user.permission.company.list.success";
@@ -6,16 +6,18 @@ export declare class CompanySelectionController {
6
6
  constructor(authService: AuthenticationService);
7
7
  select(dto: SelectDto): Promise<SingleResponseDto<LoginResponseDto>>;
8
8
  switchCompany(userId: string, dto: SwitchCompanyDto): Promise<SingleResponseDto<LoginResponseDto>>;
9
- getUserCompanies(userId: string): Promise<{
9
+ getMyCompanies(userId: string): Promise<{
10
10
  success: boolean;
11
11
  message: string;
12
12
  messageKey: "auth.company.list.success";
13
13
  data: import("../index.js").Company[];
14
14
  }>;
15
- getUserBranches(userId: string, companyId: string): Promise<{
15
+ getMyCompanyBranches(userId: string, companyId: string): Promise<{
16
16
  success: boolean;
17
17
  message: string;
18
18
  messageKey: "auth.branch.list.success";
19
- data: import("../index.js").CompanyBranch[];
19
+ data: (import("../index.js").CompanyBranch & {
20
+ displayParentId: string | null;
21
+ })[];
20
22
  }>;
21
23
  }
@@ -4,20 +4,20 @@ import { UserPermissionService } from '../services/user-permission.service.js';
4
4
  export declare class UserPermissionController {
5
5
  private readonly permissionService;
6
6
  constructor(permissionService: UserPermissionService);
7
- assignUserCompany(dto: AssignUserCompanyDto, user: ILoggedUserInfo): Promise<{
7
+ assignUserCompany(dto: AssignUserCompanyDto): Promise<{
8
8
  success: boolean;
9
9
  message: string;
10
10
  messageKey: string;
11
11
  messageVariables: Record<string, number>;
12
12
  }>;
13
13
  getUserCompanies(userId: string): Promise<UserCompaniesResponseDto>;
14
- assignUserBranch(dto: AssignUserBranchDto, user: ILoggedUserInfo): Promise<{
14
+ assignUserBranch(dto: AssignUserBranchDto): Promise<{
15
15
  success: boolean;
16
16
  message: string;
17
17
  messageKey: string;
18
18
  messageVariables: Record<string, number>;
19
19
  }>;
20
- getUserBranches(userId: string, companyId?: string): Promise<UserBranchesResponseDto>;
20
+ getUserBranches(userId: string, user: ILoggedUserInfo, companyId?: string): Promise<UserBranchesResponseDto>;
21
21
  getCompanyUsers(dto: GetCompanyUsersDto): Promise<CompanyPermittedUsersResponseDto>;
22
22
  getBranchUsers(dto: GetBranchUsersDto): Promise<BranchPermittedUsersResponseDto>;
23
23
  private processBatchPermissions;
@@ -1,6 +1,7 @@
1
1
  export declare class LoginDto {
2
2
  email: string;
3
3
  password: string;
4
+ rememberMe?: boolean;
4
5
  }
5
6
  export declare class RegistrationDto {
6
7
  name: string;
@@ -25,6 +26,7 @@ export declare class SelectDto {
25
26
  sessionId: string;
26
27
  companyId: string;
27
28
  branchId: string;
29
+ rememberMe?: boolean;
28
30
  }
29
31
  export declare class SwitchCompanyDto {
30
32
  companyId: string;
@@ -53,6 +55,9 @@ export declare class BranchSelectionInfoDto {
53
55
  name: string;
54
56
  slug: string;
55
57
  logoId?: string | null;
58
+ parentId?: string | null;
59
+ serial?: number | null;
60
+ displayParentId?: string | null;
56
61
  }
57
62
  export declare class CompanyWithBranchesDto {
58
63
  id: string;
@@ -30,6 +30,9 @@ export declare class UserBranchPermissionDto {
30
30
  branchName: string;
31
31
  branchSlug: string;
32
32
  companyId: string;
33
+ parentId?: string | null;
34
+ serial?: number | null;
35
+ displayParentId?: string | null;
33
36
  }
34
37
  export declare class UserCompaniesResponseDto {
35
38
  success: boolean;
package/fesm/118.js CHANGED
@@ -1086,6 +1086,9 @@ _ts_decorate([
1086
1086
  branchName;
1087
1087
  branchSlug;
1088
1088
  companyId;
1089
+ parentId;
1090
+ serial;
1091
+ displayParentId;
1089
1092
  }
1090
1093
  _ts_decorate([
1091
1094
  (0,_nestjs_swagger__rspack_import_0.ApiProperty)({
@@ -1129,6 +1132,33 @@ _ts_decorate([
1129
1132
  }),
1130
1133
  _ts_metadata("design:type", String)
1131
1134
  ], UserBranchPermissionDto.prototype, "companyId", void 0);
1135
+ _ts_decorate([
1136
+ (0,_nestjs_swagger__rspack_import_0.ApiPropertyOptional)({
1137
+ description: 'Parent branch ID (null for a root branch)',
1138
+ example: null,
1139
+ nullable: true,
1140
+ type: String
1141
+ }),
1142
+ _ts_metadata("design:type", Object)
1143
+ ], UserBranchPermissionDto.prototype, "parentId", void 0);
1144
+ _ts_decorate([
1145
+ (0,_nestjs_swagger__rspack_import_0.ApiPropertyOptional)({
1146
+ description: 'Sort order among sibling branches',
1147
+ example: 1,
1148
+ nullable: true,
1149
+ type: Number
1150
+ }),
1151
+ _ts_metadata("design:type", Object)
1152
+ ], UserBranchPermissionDto.prototype, "serial", void 0);
1153
+ _ts_decorate([
1154
+ (0,_nestjs_swagger__rspack_import_0.ApiPropertyOptional)({
1155
+ description: 'Tree parent within this list: the nearest ancestor that is also listed (granted), null when none is - ungranted branches in between are skipped',
1156
+ example: '550e8400-e29b-41d4-a716-446655440003',
1157
+ nullable: true,
1158
+ type: String
1159
+ }),
1160
+ _ts_metadata("design:type", Object)
1161
+ ], UserBranchPermissionDto.prototype, "displayParentId", void 0);
1132
1162
  /**
1133
1163
  * User companies response
1134
1164
  */ class UserCompaniesResponseDto {
package/fesm/120.js CHANGED
@@ -517,7 +517,7 @@ class CompanySelectionController {
517
517
  data
518
518
  };
519
519
  }
520
- async getUserCompanies(userId) {
520
+ async getMyCompanies(userId) {
521
521
  const companies = await this.authService.getUserCompanies(userId);
522
522
  return {
523
523
  success: true,
@@ -526,7 +526,7 @@ class CompanySelectionController {
526
526
  data: companies
527
527
  };
528
528
  }
529
- async getUserBranches(userId, companyId) {
529
+ async getMyCompanyBranches(userId, companyId) {
530
530
  const branches = await this.authService.getUserBranches(userId, companyId);
531
531
  return {
532
532
  success: true,
@@ -586,7 +586,7 @@ company_selection_controller_ts_decorate([
586
586
  ], CompanySelectionController.prototype, "switchCompany", null);
587
587
  company_selection_controller_ts_decorate([
588
588
  (0,common_.UseGuards)(guards_.JwtAuthGuard),
589
- (0,common_.Post)('get-companies'),
589
+ (0,common_.Post)('get-my-companies'),
590
590
  (0,common_.HttpCode)(common_.HttpStatus.OK),
591
591
  (0,swagger_.ApiBearerAuth)(),
592
592
  (0,swagger_.ApiOperation)({
@@ -603,10 +603,10 @@ company_selection_controller_ts_decorate([
603
603
  String
604
604
  ]),
605
605
  company_selection_controller_ts_metadata("design:returntype", Promise)
606
- ], CompanySelectionController.prototype, "getUserCompanies", null);
606
+ ], CompanySelectionController.prototype, "getMyCompanies", null);
607
607
  company_selection_controller_ts_decorate([
608
608
  (0,common_.UseGuards)(guards_.JwtAuthGuard),
609
- (0,common_.Post)('get-company-branches'),
609
+ (0,common_.Post)('get-my-company-branches'),
610
610
  (0,common_.HttpCode)(common_.HttpStatus.OK),
611
611
  (0,swagger_.ApiBearerAuth)(),
612
612
  (0,swagger_.ApiOperation)({
@@ -637,7 +637,7 @@ company_selection_controller_ts_decorate([
637
637
  String
638
638
  ]),
639
639
  company_selection_controller_ts_metadata("design:returntype", Promise)
640
- ], CompanySelectionController.prototype, "getUserBranches", null);
640
+ ], CompanySelectionController.prototype, "getMyCompanyBranches", null);
641
641
  CompanySelectionController = company_selection_controller_ts_decorate([
642
642
  (0,swagger_.ApiTags)('Company Selection'),
643
643
  (0,common_.Controller)('auth'),
@@ -1815,8 +1815,8 @@ class UserPermissionController {
1815
1815
  constructor(permissionService){
1816
1816
  this.permissionService = permissionService;
1817
1817
  }
1818
- async assignUserCompany(dto, user) {
1819
- return this.processBatchPermissions(dto.userId, dto.items, user_company_permission_entity/* .PermissionType.COMPANY */.e.COMPANY, user);
1818
+ async assignUserCompany(dto) {
1819
+ return this.processBatchPermissions(dto.userId, dto.items, user_company_permission_entity/* .PermissionType.COMPANY */.e.COMPANY);
1820
1820
  }
1821
1821
  async getUserCompanies(userId) {
1822
1822
  const permissions = await this.permissionService.findUserCompanies(userId);
@@ -1833,11 +1833,12 @@ class UserPermissionController {
1833
1833
  }))
1834
1834
  };
1835
1835
  }
1836
- async assignUserBranch(dto, user) {
1837
- return this.processBatchPermissions(dto.userId, dto.items, user_company_permission_entity/* .PermissionType.BRANCH */.e.BRANCH, user);
1836
+ async assignUserBranch(dto) {
1837
+ return this.processBatchPermissions(dto.userId, dto.items, user_company_permission_entity/* .PermissionType.BRANCH */.e.BRANCH);
1838
1838
  }
1839
- async getUserBranches(userId, companyId) {
1840
- const permissions = await this.permissionService.findUserBranches(userId, companyId);
1839
+ async getUserBranches(userId, user, companyId) {
1840
+ const permissions = await this.permissionService.findUserBranches(userId, companyId || user.companyId);
1841
+ const displayParentIds = await this.permissionService.findDisplayParentIds(permissions.map((p)=>p.targetId));
1841
1842
  return {
1842
1843
  success: true,
1843
1844
  message: 'User branches retrieved successfully',
@@ -1848,7 +1849,10 @@ class UserPermissionController {
1848
1849
  branchId: p.branch?.id || p.targetId,
1849
1850
  branchName: p.branch?.name || '',
1850
1851
  branchSlug: p.branch?.slug || '',
1851
- companyId: p.branch?.companyId || ''
1852
+ companyId: p.branch?.companyId || '',
1853
+ parentId: p.branch?.parentId ?? null,
1854
+ serial: p.branch?.serial ?? null,
1855
+ displayParentId: displayParentIds.get(p.targetId) ?? null
1852
1856
  }))
1853
1857
  };
1854
1858
  }
@@ -1880,7 +1884,7 @@ class UserPermissionController {
1880
1884
  }))
1881
1885
  };
1882
1886
  }
1883
- /** Process batch permission assignments/revocations */ async processBatchPermissions(userId, items, type, user) {
1887
+ /** Process batch permission assignments/revocations */ async processBatchPermissions(userId, items, type) {
1884
1888
  let added = 0, removed = 0;
1885
1889
  for (const item of items){
1886
1890
  if (item.isAdd) {
@@ -1916,11 +1920,9 @@ user_permission_controller_ts_decorate([
1916
1920
  type: user_permission_dto/* .AssignUserCompanyDto */.DU
1917
1921
  }),
1918
1922
  user_permission_controller_ts_param(0, (0,common_.Body)()),
1919
- user_permission_controller_ts_param(1, (0,__rspack_external__flusys_nestjs_shared_decorators_c129b601.CurrentUser)()),
1920
1923
  user_permission_controller_ts_metadata("design:type", Function),
1921
1924
  user_permission_controller_ts_metadata("design:paramtypes", [
1922
- typeof user_permission_dto/* .AssignUserCompanyDto */.DU === "undefined" ? Object : user_permission_dto/* .AssignUserCompanyDto */.DU,
1923
- typeof ILoggedUserInfo === "undefined" ? Object : ILoggedUserInfo
1925
+ typeof user_permission_dto/* .AssignUserCompanyDto */.DU === "undefined" ? Object : user_permission_dto/* .AssignUserCompanyDto */.DU
1924
1926
  ]),
1925
1927
  user_permission_controller_ts_metadata("design:returntype", Promise)
1926
1928
  ], UserPermissionController.prototype, "assignUserCompany", null);
@@ -1967,11 +1969,9 @@ user_permission_controller_ts_decorate([
1967
1969
  type: user_permission_dto/* .AssignUserBranchDto */.qW
1968
1970
  }),
1969
1971
  user_permission_controller_ts_param(0, (0,common_.Body)()),
1970
- user_permission_controller_ts_param(1, (0,__rspack_external__flusys_nestjs_shared_decorators_c129b601.CurrentUser)()),
1971
1972
  user_permission_controller_ts_metadata("design:type", Function),
1972
1973
  user_permission_controller_ts_metadata("design:paramtypes", [
1973
- typeof user_permission_dto/* .AssignUserBranchDto */.qW === "undefined" ? Object : user_permission_dto/* .AssignUserBranchDto */.qW,
1974
- typeof ILoggedUserInfo === "undefined" ? Object : ILoggedUserInfo
1974
+ typeof user_permission_dto/* .AssignUserBranchDto */.qW === "undefined" ? Object : user_permission_dto/* .AssignUserBranchDto */.qW
1975
1975
  ]),
1976
1976
  user_permission_controller_ts_metadata("design:returntype", Promise)
1977
1977
  ], UserPermissionController.prototype, "assignUserBranch", null);
@@ -1989,7 +1989,7 @@ user_permission_controller_ts_decorate([
1989
1989
  },
1990
1990
  companyId: {
1991
1991
  type: 'string',
1992
- description: 'Optional company ID to filter branches'
1992
+ description: 'Company ID to filter branches (defaults to the current company)'
1993
1993
  }
1994
1994
  },
1995
1995
  required: [
@@ -2002,10 +2002,12 @@ user_permission_controller_ts_decorate([
2002
2002
  type: user_permission_dto/* .UserBranchesResponseDto */.wc
2003
2003
  }),
2004
2004
  user_permission_controller_ts_param(0, (0,common_.Body)('userId')),
2005
- user_permission_controller_ts_param(1, (0,common_.Body)('companyId')),
2005
+ user_permission_controller_ts_param(1, (0,__rspack_external__flusys_nestjs_shared_decorators_c129b601.CurrentUser)()),
2006
+ user_permission_controller_ts_param(2, (0,common_.Body)('companyId')),
2006
2007
  user_permission_controller_ts_metadata("design:type", Function),
2007
2008
  user_permission_controller_ts_metadata("design:paramtypes", [
2008
2009
  String,
2010
+ typeof ILoggedUserInfo === "undefined" ? Object : ILoggedUserInfo,
2009
2011
  String
2010
2012
  ]),
2011
2013
  user_permission_controller_ts_metadata("design:returntype", Promise)
@@ -2249,7 +2251,9 @@ class SetToken {
2249
2251
  res.cookie(this.cookieName, tokenValue, {
2250
2252
  httpOnly: true,
2251
2253
  path: '/',
2252
- maxAge: this.maxAgeMs,
2254
+ ...req.body?.rememberMe !== false && {
2255
+ maxAge: this.maxAgeMs
2256
+ },
2253
2257
  ...(0,utils_.buildCookieOptions)(req)
2254
2258
  });
2255
2259
  }