@sync-in/server 1.3.8 → 1.4.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.
Files changed (128) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/README.md +3 -2
  3. package/environment/environment.dist.yaml +2 -0
  4. package/migrations/0001_square_mauler.sql +4 -0
  5. package/migrations/meta/0001_snapshot.json +2417 -0
  6. package/migrations/meta/_journal.json +7 -0
  7. package/package.json +20 -2
  8. package/server/app.bootstrap.js +9 -0
  9. package/server/app.bootstrap.js.map +1 -1
  10. package/server/app.service.spec.js +48 -23
  11. package/server/app.service.spec.js.map +1 -1
  12. package/server/applications/files/constants/files.js +0 -23
  13. package/server/applications/files/constants/files.js.map +1 -1
  14. package/server/applications/files/constants/only-office.js +8 -0
  15. package/server/applications/files/constants/only-office.js.map +1 -1
  16. package/server/applications/files/files.config.js +5 -0
  17. package/server/applications/files/files.config.js.map +1 -1
  18. package/server/applications/files/guards/files-only-office.strategy.js +0 -1
  19. package/server/applications/files/guards/files-only-office.strategy.js.map +1 -1
  20. package/server/applications/spaces/guards/space.guard.spec.js +153 -18
  21. package/server/applications/spaces/guards/space.guard.spec.js.map +1 -1
  22. package/server/applications/spaces/services/spaces-browser.service.js +7 -7
  23. package/server/applications/spaces/services/spaces-browser.service.js.map +1 -1
  24. package/server/applications/spaces/services/spaces-manager.service.js +17 -17
  25. package/server/applications/spaces/services/spaces-manager.service.js.map +1 -1
  26. package/server/applications/sync/schemas/sync-clients.schema.js +4 -4
  27. package/server/applications/sync/schemas/sync-clients.schema.js.map +1 -1
  28. package/server/applications/sync/utils/routes.js +1 -1
  29. package/server/applications/sync/utils/routes.js.map +1 -1
  30. package/server/applications/users/guards/permissions.guard.js +4 -4
  31. package/server/applications/users/guards/permissions.guard.js.map +1 -1
  32. package/server/applications/users/guards/permissions.guard.spec.js +6 -6
  33. package/server/applications/users/guards/permissions.guard.spec.js.map +1 -1
  34. package/server/applications/users/guards/roles.guard.js +1 -1
  35. package/server/applications/users/guards/roles.guard.js.map +1 -1
  36. package/server/applications/users/models/user.model.js +1 -1
  37. package/server/applications/users/models/user.model.js.map +1 -1
  38. package/server/applications/users/schemas/users.schema.js +4 -4
  39. package/server/applications/users/schemas/users.schema.js.map +1 -1
  40. package/server/authentication/guards/auth-basic.guard.spec.js +38 -2
  41. package/server/authentication/guards/auth-basic.guard.spec.js.map +1 -1
  42. package/server/authentication/guards/auth-basic.strategy.js +0 -1
  43. package/server/authentication/guards/auth-basic.strategy.js.map +1 -1
  44. package/server/authentication/guards/auth-local.guard.spec.js +7 -5
  45. package/server/authentication/guards/auth-local.guard.spec.js.map +1 -1
  46. package/server/authentication/guards/auth-local.strategy.js +0 -1
  47. package/server/authentication/guards/auth-local.strategy.js.map +1 -1
  48. package/server/authentication/guards/auth-token-access.guard.spec.js +30 -0
  49. package/server/authentication/guards/auth-token-access.guard.spec.js.map +1 -1
  50. package/server/authentication/guards/auth-token-access.strategy.js +0 -1
  51. package/server/authentication/guards/auth-token-access.strategy.js.map +1 -1
  52. package/server/authentication/guards/auth-token-refresh.strategy.js +0 -1
  53. package/server/authentication/guards/auth-token-refresh.strategy.js.map +1 -1
  54. package/server/authentication/services/auth-methods/auth-method-database.service.js +1 -1
  55. package/server/authentication/services/auth-methods/auth-method-database.service.js.map +1 -1
  56. package/server/authentication/services/auth-methods/auth-method-database.service.spec.js +8 -6
  57. package/server/authentication/services/auth-methods/auth-method-database.service.spec.js.map +1 -1
  58. package/server/authentication/services/auth-methods/auth-method-ldap.service.js +2 -2
  59. package/server/authentication/services/auth-methods/auth-method-ldap.service.js.map +1 -1
  60. package/server/authentication/services/auth-methods/auth-method-ldap.service.spec.js +500 -5
  61. package/server/authentication/services/auth-methods/auth-method-ldap.service.spec.js.map +1 -1
  62. package/server/configuration/config.constants.js +0 -4
  63. package/server/configuration/config.constants.js.map +1 -1
  64. package/server/configuration/config.loader.js +2 -5
  65. package/server/configuration/config.loader.js.map +1 -1
  66. package/server/infrastructure/context/services/context-manager.service.spec.js +98 -0
  67. package/server/infrastructure/context/services/context-manager.service.spec.js.map +1 -0
  68. package/server/infrastructure/database/configuration.js +1 -1
  69. package/server/infrastructure/database/configuration.js.map +1 -1
  70. package/server/infrastructure/database/constants.js +18 -5
  71. package/server/infrastructure/database/constants.js.map +1 -1
  72. package/server/infrastructure/database/scripts/seed/usersgroups.js +3 -3
  73. package/server/infrastructure/database/scripts/seed/usersgroups.js.map +1 -1
  74. package/server/infrastructure/mailer/mailer.service.js +20 -19
  75. package/server/infrastructure/mailer/mailer.service.js.map +1 -1
  76. package/server/infrastructure/mailer/mailer.service.spec.js +176 -0
  77. package/server/infrastructure/mailer/mailer.service.spec.js.map +1 -0
  78. package/static/{chunk-EVYNK2XG.js → chunk-3GC2BQZD.js} +1 -1
  79. package/static/{chunk-XN4TXQKE.js → chunk-5YKWZT33.js} +1 -1
  80. package/static/{chunk-VSBD6246.js → chunk-6F55D74O.js} +1 -1
  81. package/static/{chunk-MWPC5XQW.js → chunk-B2Y2RNFP.js} +1 -1
  82. package/static/{chunk-YTROXGRM.js → chunk-C23BPTJZ.js} +1 -1
  83. package/static/chunk-DGVNNICG.js +1 -0
  84. package/static/{chunk-WSVE6EWB.js → chunk-FQ4AFNGE.js} +1 -1
  85. package/static/{chunk-4YWOE6HM.js → chunk-GBCYYDCI.js} +1 -1
  86. package/static/{chunk-Z6S7RFGN.js → chunk-HZA7R43P.js} +1 -1
  87. package/static/{chunk-VO5W2VKC.js → chunk-KZQCFEPT.js} +1 -1
  88. package/static/{chunk-BLRDUWOI.js → chunk-LJIGRUEF.js} +1 -1
  89. package/static/chunk-MOVWEZ7J.js +1 -0
  90. package/static/{chunk-3UFSZ24R.js → chunk-MTRNPGS4.js} +1 -1
  91. package/static/{chunk-JM5PB2NQ.js → chunk-N2LYWNTC.js} +1 -1
  92. package/static/{chunk-L4F2K2VQ.js → chunk-PF4K7MVG.js} +1 -1
  93. package/static/{chunk-F2KKCIDO.js → chunk-PY3BGNJN.js} +1 -1
  94. package/static/chunk-RSNLYAN6.js +560 -0
  95. package/static/{chunk-VCXIQZT4.js → chunk-SH5EVL4E.js} +4 -4
  96. package/static/{chunk-BEJ62KA7.js → chunk-T55FAU2O.js} +1 -1
  97. package/static/{chunk-BEC2WQVF.js → chunk-TCFKH6K6.js} +1 -1
  98. package/static/{chunk-LATG3ZVP.js → chunk-TDQAEVZN.js} +1 -1
  99. package/static/{chunk-FNO4KAYG.js → chunk-TNW2CGK6.js} +1 -1
  100. package/static/{chunk-OWYD7F64.js → chunk-TTQ37MUV.js} +1 -1
  101. package/static/chunk-TXPODW5Q.js +7 -0
  102. package/static/chunk-VHYIXL7R.js +4 -0
  103. package/static/{chunk-UEN7RH33.js → chunk-VMQMD36Z.js} +1 -1
  104. package/static/{chunk-6E5YHOWA.js → chunk-VMUOUCEI.js} +1 -1
  105. package/static/{chunk-I6ACCGIM.js → chunk-W3QXNDI5.js} +1 -1
  106. package/static/chunk-WHMS3PJ3.js +24 -0
  107. package/static/{chunk-3PB73RWL.js → chunk-WWIC7UW3.js} +1 -1
  108. package/static/{chunk-6A5R4OPV.js → chunk-X43VWRFP.js} +1 -1
  109. package/static/chunk-XE5YHU5J.js +1 -0
  110. package/static/chunk-Y2CDUS4J.js +4 -0
  111. package/static/chunk-YCINY2YI.js +1 -0
  112. package/static/index.html +2 -2
  113. package/static/main-7LDKYVXO.js +10 -0
  114. package/static/{polyfills-GNKGQMRJ.js → polyfills-IA5XWQYN.js} +1 -1
  115. package/static/{scripts-263WP5VB.js → scripts-VZVAP2P4.js} +4 -4
  116. package/static/styles-FYUSO6OJ.css +1 -0
  117. package/sync-in-server.js +1 -1
  118. package/static/chunk-CIGVDQPA.js +0 -1
  119. package/static/chunk-CY3GGCEX.js +0 -7
  120. package/static/chunk-DJRILQTU.js +0 -1
  121. package/static/chunk-EDLO4Y5E.js +0 -4
  122. package/static/chunk-ENDRPUCI.js +0 -4
  123. package/static/chunk-HXRRMHEX.js +0 -1
  124. package/static/chunk-IJDFKCZM.js +0 -24
  125. package/static/chunk-J6YSVODR.js +0 -1
  126. package/static/chunk-JQOMI5VI.js +0 -560
  127. package/static/main-7LVJ2JSP.js +0 -10
  128. package/static/styles-3DONJ2Z4.css +0 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../backend/src/applications/users/guards/permissions.guard.ts"],"sourcesContent":["/*\n * Copyright (C) 2012-2025 Johan Legrand <johan.legrand@sync-in.com>\n * This file is part of Sync-in | The open source file sync and share solution\n * See the LICENSE file for licensing details\n */\n\nimport { CanActivate, ExecutionContext, HttpException, HttpStatus, Injectable, Logger } from '@nestjs/common'\nimport { Reflector } from '@nestjs/core'\nimport { FastifyAuthenticatedRequest } from '../../../authentication/interfaces/auth-request.interface'\nimport { USER_PERMISSION } from '../constants/user'\nimport { UserHavePermission } from '../decorators/permissions.decorator'\n\n@Injectable()\nexport class UserPermissionsGuard implements CanActivate {\n private readonly logger = new Logger(UserPermissionsGuard.name)\n\n constructor(private readonly reflector: Reflector) {}\n\n canActivate(ctx: ExecutionContext): boolean {\n const permissions: USER_PERMISSION | USER_PERMISSION[] = this.reflector.getAllAndOverride(UserHavePermission, [ctx.getHandler(), ctx.getClass()])\n if (typeof permissions === 'object') {\n // used to bypass the check, the guard is called without argument, the value is '{}'\n return true\n }\n if (permissions === undefined) {\n this.logger.warn(`no application defined on ${ctx.getClass().name}:${ctx.getHandler().name}`)\n return false\n }\n const req: FastifyAuthenticatedRequest = ctx.switchToHttp().getRequest()\n if (req.user.isAdmin) {\n return true\n }\n let authorized = false\n if (Array.isArray(permissions)) {\n // if any of the apps are allowed, proceed\n authorized = permissions.some((p: string) => req.user.havePermission(p))\n } else {\n authorized = req.user.havePermission(permissions)\n }\n if (!authorized) {\n this.logger.warn(`does not have permissions : ${permissions}`)\n throw new HttpException('You are not allowed to do this action', HttpStatus.FORBIDDEN)\n }\n return authorized\n }\n}\n"],"names":["UserPermissionsGuard","canActivate","ctx","permissions","reflector","getAllAndOverride","UserHavePermission","getHandler","getClass","undefined","logger","warn","name","req","switchToHttp","getRequest","user","isAdmin","authorized","Array","isArray","some","p","havePermission","HttpException","HttpStatus","FORBIDDEN","Logger"],"mappings":"AAAA;;;;CAIC;;;;+BASYA;;;eAAAA;;;wBAPgF;sBACnE;sCAGS;;;;;;;;;;AAG5B,IAAA,AAAMA,uBAAN,MAAMA;IAKXC,YAAYC,GAAqB,EAAW;QAC1C,MAAMC,cAAmD,IAAI,CAACC,SAAS,CAACC,iBAAiB,CAACC,wCAAkB,EAAE;YAACJ,IAAIK,UAAU;YAAIL,IAAIM,QAAQ;SAAG;QAChJ,IAAI,OAAOL,gBAAgB,UAAU;YACnC,oFAAoF;YACpF,OAAO;QACT;QACA,IAAIA,gBAAgBM,WAAW;YAC7B,IAAI,CAACC,MAAM,CAACC,IAAI,CAAC,CAAC,0BAA0B,EAAET,IAAIM,QAAQ,GAAGI,IAAI,CAAC,CAAC,EAAEV,IAAIK,UAAU,GAAGK,IAAI,EAAE;YAC5F,OAAO;QACT;QACA,MAAMC,MAAmCX,IAAIY,YAAY,GAAGC,UAAU;QACtE,IAAIF,IAAIG,IAAI,CAACC,OAAO,EAAE;YACpB,OAAO;QACT;QACA,IAAIC,aAAa;QACjB,IAAIC,MAAMC,OAAO,CAACjB,cAAc;YAC9B,0CAA0C;YAC1Ce,aAAaf,YAAYkB,IAAI,CAAC,CAACC,IAAcT,IAAIG,IAAI,CAACO,cAAc,CAACD;QACvE,OAAO;YACLJ,aAAaL,IAAIG,IAAI,CAACO,cAAc,CAACpB;QACvC;QACA,IAAI,CAACe,YAAY;YACf,IAAI,CAACR,MAAM,CAACC,IAAI,CAAC,CAAC,4BAA4B,EAAER,aAAa;YAC7D,MAAM,IAAIqB,qBAAa,CAAC,yCAAyCC,kBAAU,CAACC,SAAS;QACvF;QACA,OAAOR;IACT;IA5BA,YAAY,AAAiBd,SAAoB,CAAE;aAAtBA,YAAAA;aAFZM,SAAS,IAAIiB,cAAM,CAAC3B,qBAAqBY,IAAI;IAEV;AA6BtD"}
1
+ {"version":3,"sources":["../../../../../backend/src/applications/users/guards/permissions.guard.ts"],"sourcesContent":["/*\n * Copyright (C) 2012-2025 Johan Legrand <johan.legrand@sync-in.com>\n * This file is part of Sync-in | The open source file sync and share solution\n * See the LICENSE file for licensing details\n */\n\nimport { CanActivate, ExecutionContext, HttpException, HttpStatus, Injectable, Logger } from '@nestjs/common'\nimport { Reflector } from '@nestjs/core'\nimport { FastifyAuthenticatedRequest } from '../../../authentication/interfaces/auth-request.interface'\nimport { USER_PERMISSION } from '../constants/user'\nimport { UserHavePermission } from '../decorators/permissions.decorator'\n\n@Injectable()\nexport class UserPermissionsGuard implements CanActivate {\n private readonly logger = new Logger(UserPermissionsGuard.name)\n\n constructor(private readonly reflector: Reflector) {}\n\n canActivate(ctx: ExecutionContext): boolean {\n const permissions: USER_PERMISSION | USER_PERMISSION[] = this.reflector.getAllAndOverride(UserHavePermission, [ctx.getHandler(), ctx.getClass()])\n if (permissions === undefined) {\n this.logger.warn(`no application defined on ${ctx.getClass().name}:${ctx.getHandler().name}`)\n return false\n }\n if (Object.keys(permissions).length === 0) {\n // used to bypass the check, the guard is called without argument, the value is '{}'\n return true\n }\n const req: FastifyAuthenticatedRequest = ctx.switchToHttp().getRequest()\n if (req.user.isAdmin) {\n return true\n }\n let authorized = false\n if (Array.isArray(permissions)) {\n // if any of the apps are allowed, proceed\n authorized = permissions.some((p: string) => req.user.havePermission(p))\n } else {\n authorized = req.user.havePermission(permissions)\n }\n if (!authorized) {\n this.logger.warn(`does not have permissions : ${permissions}`)\n throw new HttpException('You are not allowed to do this action', HttpStatus.FORBIDDEN)\n }\n return authorized\n }\n}\n"],"names":["UserPermissionsGuard","canActivate","ctx","permissions","reflector","getAllAndOverride","UserHavePermission","getHandler","getClass","undefined","logger","warn","name","Object","keys","length","req","switchToHttp","getRequest","user","isAdmin","authorized","Array","isArray","some","p","havePermission","HttpException","HttpStatus","FORBIDDEN","Logger"],"mappings":"AAAA;;;;CAIC;;;;+BASYA;;;eAAAA;;;wBAPgF;sBACnE;sCAGS;;;;;;;;;;AAG5B,IAAA,AAAMA,uBAAN,MAAMA;IAKXC,YAAYC,GAAqB,EAAW;QAC1C,MAAMC,cAAmD,IAAI,CAACC,SAAS,CAACC,iBAAiB,CAACC,wCAAkB,EAAE;YAACJ,IAAIK,UAAU;YAAIL,IAAIM,QAAQ;SAAG;QAChJ,IAAIL,gBAAgBM,WAAW;YAC7B,IAAI,CAACC,MAAM,CAACC,IAAI,CAAC,CAAC,0BAA0B,EAAET,IAAIM,QAAQ,GAAGI,IAAI,CAAC,CAAC,EAAEV,IAAIK,UAAU,GAAGK,IAAI,EAAE;YAC5F,OAAO;QACT;QACA,IAAIC,OAAOC,IAAI,CAACX,aAAaY,MAAM,KAAK,GAAG;YACzC,oFAAoF;YACpF,OAAO;QACT;QACA,MAAMC,MAAmCd,IAAIe,YAAY,GAAGC,UAAU;QACtE,IAAIF,IAAIG,IAAI,CAACC,OAAO,EAAE;YACpB,OAAO;QACT;QACA,IAAIC,aAAa;QACjB,IAAIC,MAAMC,OAAO,CAACpB,cAAc;YAC9B,0CAA0C;YAC1CkB,aAAalB,YAAYqB,IAAI,CAAC,CAACC,IAAcT,IAAIG,IAAI,CAACO,cAAc,CAACD;QACvE,OAAO;YACLJ,aAAaL,IAAIG,IAAI,CAACO,cAAc,CAACvB;QACvC;QACA,IAAI,CAACkB,YAAY;YACf,IAAI,CAACX,MAAM,CAACC,IAAI,CAAC,CAAC,4BAA4B,EAAER,aAAa;YAC7D,MAAM,IAAIwB,qBAAa,CAAC,yCAAyCC,kBAAU,CAACC,SAAS;QACvF;QACA,OAAOR;IACT;IA5BA,YAAY,AAAiBjB,SAAoB,CAAE;aAAtBA,YAAAA;aAFZM,SAAS,IAAIoB,cAAM,CAAC9B,qBAAqBY,IAAI;IAEV;AA6BtD"}
@@ -31,7 +31,7 @@ describe(_permissionsguard.UserPermissionsGuard.name, ()=>{
31
31
  expect(permissionsGuard).toBeDefined();
32
32
  expect(userTest).toBeDefined();
33
33
  });
34
- it('should pass with a valid permission', async ()=>{
34
+ it('should pass with a valid permission', ()=>{
35
35
  userTest.applications = [
36
36
  _user.USER_PERMISSION.PERSONAL_SPACE
37
37
  ];
@@ -42,7 +42,7 @@ describe(_permissionsguard.UserPermissionsGuard.name, ()=>{
42
42
  });
43
43
  expect(permissionsGuard.canActivate(context)).toBe(true);
44
44
  });
45
- it('should pass if any of the permissions are granted', async ()=>{
45
+ it('should pass if any of the permissions are granted', ()=>{
46
46
  userTest.applications = [
47
47
  _user.USER_PERMISSION.PERSONAL_SPACE
48
48
  ];
@@ -56,7 +56,7 @@ describe(_permissionsguard.UserPermissionsGuard.name, ()=>{
56
56
  });
57
57
  expect(permissionsGuard.canActivate(context)).toBe(true);
58
58
  });
59
- it('should not pass with a bad permission', async ()=>{
59
+ it('should not pass with a bad permission', ()=>{
60
60
  userTest.applications = [];
61
61
  context = (0, _tsjest.createMock)();
62
62
  (0, _permissionsdecorator.UserHavePermission)(_user.USER_PERMISSION.PERSONAL_SPACE)(context.getHandler());
@@ -65,7 +65,7 @@ describe(_permissionsguard.UserPermissionsGuard.name, ()=>{
65
65
  });
66
66
  expect(()=>permissionsGuard.canActivate(context)).toThrow(_common.HttpException);
67
67
  });
68
- it('should pass with no permissions but with the admin role', async ()=>{
68
+ it('should pass with no permissions but with the admin role', ()=>{
69
69
  userTest.applications = [];
70
70
  userTest.role = _user.USER_ROLE.ADMINISTRATOR;
71
71
  context = (0, _tsjest.createMock)();
@@ -77,7 +77,7 @@ describe(_permissionsguard.UserPermissionsGuard.name, ()=>{
77
77
  // reset
78
78
  userTest.role = _user.USER_ROLE.USER;
79
79
  });
80
- it('should not pass with a missing decorator', async ()=>{
80
+ it('should not pass with a missing decorator', ()=>{
81
81
  userTest.applications = [
82
82
  _user.USER_PERMISSION.PERSONAL_SPACE
83
83
  ];
@@ -87,7 +87,7 @@ describe(_permissionsguard.UserPermissionsGuard.name, ()=>{
87
87
  });
88
88
  expect(permissionsGuard.canActivate(context)).toBeFalsy();
89
89
  });
90
- it('should pass with an empty decorator', async ()=>{
90
+ it('should pass with an empty decorator', ()=>{
91
91
  userTest.applications = [
92
92
  _user.USER_PERMISSION.PERSONAL_SPACE
93
93
  ];
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../backend/src/applications/users/guards/permissions.guard.spec.ts"],"sourcesContent":["/*\n * Copyright (C) 2012-2025 Johan Legrand <johan.legrand@sync-in.com>\n * This file is part of Sync-in | The open source file sync and share solution\n * See the LICENSE file for licensing details\n */\n\nimport { createMock, DeepMocked } from '@golevelup/ts-jest'\nimport { ExecutionContext, HttpException, Logger } from '@nestjs/common'\nimport { Reflector } from '@nestjs/core'\nimport { USER_PERMISSION, USER_ROLE } from '../constants/user'\nimport { UserHavePermission } from '../decorators/permissions.decorator'\nimport { UserModel } from '../models/user.model'\nimport { generateUserTest } from '../utils/test'\nimport { UserPermissionsGuard } from './permissions.guard'\n\ndescribe(UserPermissionsGuard.name, () => {\n let reflector: Reflector\n let permissionsGuard: UserPermissionsGuard\n let userTest: UserModel\n let context: DeepMocked<ExecutionContext>\n\n beforeAll(async () => {\n reflector = new Reflector()\n permissionsGuard = new UserPermissionsGuard(reflector)\n userTest = new UserModel(generateUserTest())\n Logger.overrideLogger(['fatal'])\n })\n\n it('should be defined', () => {\n expect(permissionsGuard).toBeDefined()\n expect(userTest).toBeDefined()\n })\n\n it('should pass with a valid permission', async () => {\n userTest.applications = [USER_PERMISSION.PERSONAL_SPACE]\n context = createMock<ExecutionContext>()\n UserHavePermission(USER_PERMISSION.PERSONAL_SPACE)(context.getHandler())\n context.switchToHttp().getRequest.mockReturnValue({\n user: userTest\n })\n expect(permissionsGuard.canActivate(context)).toBe(true)\n })\n\n it('should pass if any of the permissions are granted', async () => {\n userTest.applications = [USER_PERMISSION.PERSONAL_SPACE]\n context = createMock<ExecutionContext>()\n UserHavePermission([USER_PERMISSION.SPACES, USER_PERMISSION.PERSONAL_SPACE])(context.getHandler())\n context.switchToHttp().getRequest.mockReturnValue({\n user: userTest\n })\n expect(permissionsGuard.canActivate(context)).toBe(true)\n })\n\n it('should not pass with a bad permission', async () => {\n userTest.applications = []\n context = createMock<ExecutionContext>()\n UserHavePermission(USER_PERMISSION.PERSONAL_SPACE)(context.getHandler())\n context.switchToHttp().getRequest.mockReturnValue({\n user: userTest\n })\n expect(() => permissionsGuard.canActivate(context)).toThrow(HttpException)\n })\n\n it('should pass with no permissions but with the admin role', async () => {\n userTest.applications = []\n userTest.role = USER_ROLE.ADMINISTRATOR\n context = createMock<ExecutionContext>()\n UserHavePermission(USER_PERMISSION.PERSONAL_SPACE)(context.getHandler())\n context.switchToHttp().getRequest.mockReturnValue({\n user: userTest\n })\n expect(permissionsGuard.canActivate(context)).toBe(true)\n // reset\n userTest.role = USER_ROLE.USER\n })\n\n it('should not pass with a missing decorator', async () => {\n userTest.applications = [USER_PERMISSION.PERSONAL_SPACE]\n context = createMock<ExecutionContext>()\n context.switchToHttp().getRequest.mockReturnValue({\n user: userTest\n })\n expect(permissionsGuard.canActivate(context)).toBeFalsy()\n })\n\n it('should pass with an empty decorator', async () => {\n userTest.applications = [USER_PERMISSION.PERSONAL_SPACE]\n context = createMock<ExecutionContext>()\n UserHavePermission()(context.getHandler())\n context.switchToHttp().getRequest.mockReturnValue({\n user: userTest\n })\n expect(permissionsGuard.canActivate(context)).toBeTruthy()\n })\n})\n"],"names":["describe","UserPermissionsGuard","name","reflector","permissionsGuard","userTest","context","beforeAll","Reflector","UserModel","generateUserTest","Logger","overrideLogger","it","expect","toBeDefined","applications","USER_PERMISSION","PERSONAL_SPACE","createMock","UserHavePermission","getHandler","switchToHttp","getRequest","mockReturnValue","user","canActivate","toBe","SPACES","toThrow","HttpException","role","USER_ROLE","ADMINISTRATOR","USER","toBeFalsy","toBeTruthy"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../backend/src/applications/users/guards/permissions.guard.spec.ts"],"sourcesContent":["/*\n * Copyright (C) 2012-2025 Johan Legrand <johan.legrand@sync-in.com>\n * This file is part of Sync-in | The open source file sync and share solution\n * See the LICENSE file for licensing details\n */\n\nimport { createMock, DeepMocked } from '@golevelup/ts-jest'\nimport { ExecutionContext, HttpException, Logger } from '@nestjs/common'\nimport { Reflector } from '@nestjs/core'\nimport { USER_PERMISSION, USER_ROLE } from '../constants/user'\nimport { UserHavePermission } from '../decorators/permissions.decorator'\nimport { UserModel } from '../models/user.model'\nimport { generateUserTest } from '../utils/test'\nimport { UserPermissionsGuard } from './permissions.guard'\n\ndescribe(UserPermissionsGuard.name, () => {\n let reflector: Reflector\n let permissionsGuard: UserPermissionsGuard\n let userTest: UserModel\n let context: DeepMocked<ExecutionContext>\n\n beforeAll(async () => {\n reflector = new Reflector()\n permissionsGuard = new UserPermissionsGuard(reflector)\n userTest = new UserModel(generateUserTest())\n Logger.overrideLogger(['fatal'])\n })\n\n it('should be defined', () => {\n expect(permissionsGuard).toBeDefined()\n expect(userTest).toBeDefined()\n })\n\n it('should pass with a valid permission', () => {\n userTest.applications = [USER_PERMISSION.PERSONAL_SPACE]\n context = createMock<ExecutionContext>()\n UserHavePermission(USER_PERMISSION.PERSONAL_SPACE)(context.getHandler())\n context.switchToHttp().getRequest.mockReturnValue({\n user: userTest\n })\n expect(permissionsGuard.canActivate(context)).toBe(true)\n })\n\n it('should pass if any of the permissions are granted', () => {\n userTest.applications = [USER_PERMISSION.PERSONAL_SPACE]\n context = createMock<ExecutionContext>()\n UserHavePermission([USER_PERMISSION.SPACES, USER_PERMISSION.PERSONAL_SPACE])(context.getHandler())\n context.switchToHttp().getRequest.mockReturnValue({\n user: userTest\n })\n expect(permissionsGuard.canActivate(context)).toBe(true)\n })\n\n it('should not pass with a bad permission', () => {\n userTest.applications = []\n context = createMock<ExecutionContext>()\n UserHavePermission(USER_PERMISSION.PERSONAL_SPACE)(context.getHandler())\n context.switchToHttp().getRequest.mockReturnValue({\n user: userTest\n })\n expect(() => permissionsGuard.canActivate(context)).toThrow(HttpException)\n })\n\n it('should pass with no permissions but with the admin role', () => {\n userTest.applications = []\n userTest.role = USER_ROLE.ADMINISTRATOR\n context = createMock<ExecutionContext>()\n UserHavePermission(USER_PERMISSION.PERSONAL_SPACE)(context.getHandler())\n context.switchToHttp().getRequest.mockReturnValue({\n user: userTest\n })\n expect(permissionsGuard.canActivate(context)).toBe(true)\n // reset\n userTest.role = USER_ROLE.USER\n })\n\n it('should not pass with a missing decorator', () => {\n userTest.applications = [USER_PERMISSION.PERSONAL_SPACE]\n context = createMock<ExecutionContext>()\n context.switchToHttp().getRequest.mockReturnValue({\n user: userTest\n })\n expect(permissionsGuard.canActivate(context)).toBeFalsy()\n })\n\n it('should pass with an empty decorator', () => {\n userTest.applications = [USER_PERMISSION.PERSONAL_SPACE]\n context = createMock<ExecutionContext>()\n UserHavePermission()(context.getHandler())\n context.switchToHttp().getRequest.mockReturnValue({\n user: userTest\n })\n expect(permissionsGuard.canActivate(context)).toBeTruthy()\n })\n})\n"],"names":["describe","UserPermissionsGuard","name","reflector","permissionsGuard","userTest","context","beforeAll","Reflector","UserModel","generateUserTest","Logger","overrideLogger","it","expect","toBeDefined","applications","USER_PERMISSION","PERSONAL_SPACE","createMock","UserHavePermission","getHandler","switchToHttp","getRequest","mockReturnValue","user","canActivate","toBe","SPACES","toThrow","HttpException","role","USER_ROLE","ADMINISTRATOR","USER","toBeFalsy","toBeTruthy"],"mappings":"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"}
@@ -35,7 +35,7 @@ let UserRolesGuard = class UserRolesGuard {
35
35
  // used to bypass the check, the guard is called without argument, the value is '{}'
36
36
  return true;
37
37
  }
38
- if (role === undefined) {
38
+ if (role === undefined || !Number.isFinite(role)) {
39
39
  this.logger.warn(`no role defined on : ${ctx.getClass().name}:${ctx.getHandler().name}`);
40
40
  return false;
41
41
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../backend/src/applications/users/guards/roles.guard.ts"],"sourcesContent":["/*\n * Copyright (C) 2012-2025 Johan Legrand <johan.legrand@sync-in.com>\n * This file is part of Sync-in | The open source file sync and share solution\n * See the LICENSE file for licensing details\n */\n\nimport { CanActivate, ExecutionContext, Injectable, Logger } from '@nestjs/common'\nimport { Reflector } from '@nestjs/core'\nimport { FastifyAuthenticatedRequest } from '../../../authentication/interfaces/auth-request.interface'\nimport { USER_ROLE } from '../constants/user'\nimport { UserHaveRole } from '../decorators/roles.decorator'\n\n@Injectable()\nexport class UserRolesGuard implements CanActivate {\n private readonly logger = new Logger(UserRolesGuard.name)\n\n constructor(private readonly reflector: Reflector) {}\n\n canActivate(ctx: ExecutionContext): boolean {\n const role: USER_ROLE = this.reflector.getAllAndOverride(UserHaveRole, [ctx.getHandler(), ctx.getClass()])\n if (typeof role === 'object') {\n // used to bypass the check, the guard is called without argument, the value is '{}'\n return true\n }\n if (role === undefined) {\n this.logger.warn(`no role defined on : ${ctx.getClass().name}:${ctx.getHandler().name}`)\n return false\n }\n const req: FastifyAuthenticatedRequest = ctx.switchToHttp().getRequest()\n const authorized: boolean = req.user.haveRole(role)\n if (!authorized) {\n this.logger.warn(`does not have role : ${USER_ROLE[role]}`)\n }\n return authorized\n }\n}\n"],"names":["UserRolesGuard","canActivate","ctx","role","reflector","getAllAndOverride","UserHaveRole","getHandler","getClass","undefined","logger","warn","name","req","switchToHttp","getRequest","authorized","user","haveRole","USER_ROLE","Logger"],"mappings":"AAAA;;;;CAIC;;;;+BASYA;;;eAAAA;;;wBAPqD;sBACxC;sBAEA;gCACG;;;;;;;;;;AAGtB,IAAA,AAAMA,iBAAN,MAAMA;IAKXC,YAAYC,GAAqB,EAAW;QAC1C,MAAMC,OAAkB,IAAI,CAACC,SAAS,CAACC,iBAAiB,CAACC,4BAAY,EAAE;YAACJ,IAAIK,UAAU;YAAIL,IAAIM,QAAQ;SAAG;QACzG,IAAI,OAAOL,SAAS,UAAU;YAC5B,oFAAoF;YACpF,OAAO;QACT;QACA,IAAIA,SAASM,WAAW;YACtB,IAAI,CAACC,MAAM,CAACC,IAAI,CAAC,CAAC,qBAAqB,EAAET,IAAIM,QAAQ,GAAGI,IAAI,CAAC,CAAC,EAAEV,IAAIK,UAAU,GAAGK,IAAI,EAAE;YACvF,OAAO;QACT;QACA,MAAMC,MAAmCX,IAAIY,YAAY,GAAGC,UAAU;QACtE,MAAMC,aAAsBH,IAAII,IAAI,CAACC,QAAQ,CAACf;QAC9C,IAAI,CAACa,YAAY;YACf,IAAI,CAACN,MAAM,CAACC,IAAI,CAAC,CAAC,qBAAqB,EAAEQ,eAAS,CAAChB,KAAK,EAAE;QAC5D;QACA,OAAOa;IACT;IAlBA,YAAY,AAAiBZ,SAAoB,CAAE;aAAtBA,YAAAA;aAFZM,SAAS,IAAIU,cAAM,CAACpB,eAAeY,IAAI;IAEJ;AAmBtD"}
1
+ {"version":3,"sources":["../../../../../backend/src/applications/users/guards/roles.guard.ts"],"sourcesContent":["/*\n * Copyright (C) 2012-2025 Johan Legrand <johan.legrand@sync-in.com>\n * This file is part of Sync-in | The open source file sync and share solution\n * See the LICENSE file for licensing details\n */\n\nimport { CanActivate, ExecutionContext, Injectable, Logger } from '@nestjs/common'\nimport { Reflector } from '@nestjs/core'\nimport { FastifyAuthenticatedRequest } from '../../../authentication/interfaces/auth-request.interface'\nimport { USER_ROLE } from '../constants/user'\nimport { UserHaveRole } from '../decorators/roles.decorator'\n\n@Injectable()\nexport class UserRolesGuard implements CanActivate {\n private readonly logger = new Logger(UserRolesGuard.name)\n\n constructor(private readonly reflector: Reflector) {}\n\n canActivate(ctx: ExecutionContext): boolean {\n const role: USER_ROLE = this.reflector.getAllAndOverride(UserHaveRole, [ctx.getHandler(), ctx.getClass()])\n if (typeof role === 'object') {\n // used to bypass the check, the guard is called without argument, the value is '{}'\n return true\n }\n if (role === undefined || !Number.isFinite(role)) {\n this.logger.warn(`no role defined on : ${ctx.getClass().name}:${ctx.getHandler().name}`)\n return false\n }\n const req: FastifyAuthenticatedRequest = ctx.switchToHttp().getRequest()\n const authorized: boolean = req.user.haveRole(role)\n if (!authorized) {\n this.logger.warn(`does not have role : ${USER_ROLE[role]}`)\n }\n return authorized\n }\n}\n"],"names":["UserRolesGuard","canActivate","ctx","role","reflector","getAllAndOverride","UserHaveRole","getHandler","getClass","undefined","Number","isFinite","logger","warn","name","req","switchToHttp","getRequest","authorized","user","haveRole","USER_ROLE","Logger"],"mappings":"AAAA;;;;CAIC;;;;+BASYA;;;eAAAA;;;wBAPqD;sBACxC;sBAEA;gCACG;;;;;;;;;;AAGtB,IAAA,AAAMA,iBAAN,MAAMA;IAKXC,YAAYC,GAAqB,EAAW;QAC1C,MAAMC,OAAkB,IAAI,CAACC,SAAS,CAACC,iBAAiB,CAACC,4BAAY,EAAE;YAACJ,IAAIK,UAAU;YAAIL,IAAIM,QAAQ;SAAG;QACzG,IAAI,OAAOL,SAAS,UAAU;YAC5B,oFAAoF;YACpF,OAAO;QACT;QACA,IAAIA,SAASM,aAAa,CAACC,OAAOC,QAAQ,CAACR,OAAO;YAChD,IAAI,CAACS,MAAM,CAACC,IAAI,CAAC,CAAC,qBAAqB,EAAEX,IAAIM,QAAQ,GAAGM,IAAI,CAAC,CAAC,EAAEZ,IAAIK,UAAU,GAAGO,IAAI,EAAE;YACvF,OAAO;QACT;QACA,MAAMC,MAAmCb,IAAIc,YAAY,GAAGC,UAAU;QACtE,MAAMC,aAAsBH,IAAII,IAAI,CAACC,QAAQ,CAACjB;QAC9C,IAAI,CAACe,YAAY;YACf,IAAI,CAACN,MAAM,CAACC,IAAI,CAAC,CAAC,qBAAqB,EAAEQ,eAAS,CAAClB,KAAK,EAAE;QAC5D;QACA,OAAOe;IACT;IAlBA,YAAY,AAAiBd,SAAoB,CAAE;aAAtBA,YAAAA;aAFZQ,SAAS,IAAIU,cAAM,CAACtB,eAAec,IAAI;IAEJ;AAmBtD"}
@@ -170,7 +170,7 @@ let UserModel = class UserModel {
170
170
  return UserModel.getFilesPath(userLogin);
171
171
  }
172
172
  constructor(props, removePassword = true){
173
- // permissions as list
173
+ // permissions as a list
174
174
  this.applications = [];
175
175
  Object.assign(this, props);
176
176
  if (removePassword) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../backend/src/applications/users/models/user.model.ts"],"sourcesContent":["/*\n * Copyright (C) 2012-2025 Johan Legrand <johan.legrand@sync-in.com>\n * This file is part of Sync-in | The open source file sync and share solution\n * See the LICENSE file for licensing details\n */\n\nimport { Exclude, Expose } from 'class-transformer'\nimport fs from 'node:fs/promises'\nimport path from 'node:path'\nimport { configuration } from '../../../configuration/config.environment'\nimport { SPACE_REPOSITORY } from '../../spaces/constants/spaces'\nimport { GUEST_PERMISSION, USER_PATH, USER_PERMISSION, USER_PERMS_SEP, USER_ROLE } from '../constants/user'\nimport type { Owner } from '../interfaces/owner.interface'\nimport type { User } from '../schemas/user.interface'\n\nexport class UserModel implements User {\n id: number\n login: string\n email: string\n firstName: string\n lastName: string\n role: number\n language: string\n notification: number\n onlineStatus: number\n permissions: string\n storageUsage: number\n storageQuota: number\n isActive: boolean\n passwordAttempts: number\n currentIp: string\n lastIp: string\n currentAccess: Date\n lastAccess: Date\n createdAt: Date\n // exclusions\n @Exclude()\n password: string\n @Exclude()\n // only used on backend\n impersonatedFromId?: number\n impersonatedClientId?: string\n // used for desktop|cmd app\n clientId?: string\n\n // outside db schema\n fullName: string\n impersonated?: boolean\n avatarBase64?: string\n // permissions as list\n applications: string[] = []\n private _homePath: string\n private _filesPath: string\n private _trashPath: string\n private _tmpPath: string\n private _tasksPath: string\n\n @Exclude({ toPlainOnly: true })\n exp?: number // refresh token expiration needed to refresh\n\n constructor(props: Partial<User> & { exp?: number }, removePassword = true) {\n Object.assign(this, props)\n if (removePassword) {\n // always remove the password field from model for obvious security reasons\n // do not remove it from `props` to not mutate the object\n this.removePassword()\n }\n this.setFullName()\n this.setApplications()\n this.setProfile()\n }\n\n toString(): string {\n return `*User <${this.login}> (${this.id})*`\n }\n\n removePassword() {\n delete this.password\n }\n\n setFullName() {\n if (!this.fullName) {\n this.fullName = `${this.firstName || ''} ${this.lastName || ''}`.trim()\n }\n }\n\n @Expose()\n get isAdmin(): boolean {\n return this.role === USER_ROLE.ADMINISTRATOR\n }\n\n @Expose()\n get isUser(): boolean {\n return this.role === USER_ROLE.USER || this.role === USER_ROLE.ADMINISTRATOR\n }\n\n @Expose()\n get isGuest(): boolean {\n return this.role === USER_ROLE.GUEST\n }\n\n @Expose()\n get isLink(): boolean {\n return this.role === USER_ROLE.LINK\n }\n\n @Expose()\n get quotaIsExceeded(): boolean {\n return this.storageQuota !== null && this.storageUsage >= this.storageQuota\n }\n\n get homePath(): string {\n if (this.isLink || this.isGuest) {\n return (this._homePath ||= path.join(configuration.applications.files.tmpPath, this.isGuest ? 'guests' : 'links', this.login))\n }\n return (this._homePath ||= path.join(configuration.applications.files.usersPath, this.login))\n }\n\n get filesPath(): string {\n return (this._filesPath ||= path.join(this.homePath, SPACE_REPOSITORY.FILES))\n }\n\n get trashPath(): string {\n return (this._trashPath ||= path.join(this.homePath, SPACE_REPOSITORY.TRASH))\n }\n\n get tmpPath(): string {\n return (this._tmpPath ||= path.join(this.homePath, USER_PATH.TMP))\n }\n\n get tasksPath(): string {\n return (this._tasksPath ||= path.join(this.tmpPath, USER_PATH.TASKS))\n }\n\n async makePaths(): Promise<void> {\n if (this.isGuest || this.isLink) {\n await fs.mkdir(this.tasksPath, { recursive: true })\n } else {\n for (const p of [this.filesPath, this.trashPath, this.tasksPath]) {\n await fs.mkdir(p, { recursive: true })\n }\n }\n }\n\n asOwner(): Owner {\n return { id: this.id, login: this.login, email: this.email, fullName: this.fullName }\n }\n\n getInitials(): string {\n let initials: { f: string; l: string }\n if (this.firstName) {\n if (this.lastName) {\n initials = { f: this.firstName.charAt(0), l: this.lastName.charAt(0) }\n }\n initials = { f: this.firstName.charAt(0), l: this.firstName.charAt(1) }\n } else {\n initials = { f: this.login.charAt(0), l: this.login.charAt(1) }\n }\n return `${initials.f.toUpperCase()}${initials.l.toLowerCase()}`\n }\n\n private setProfile() {\n if (this.isLink) {\n this.login = `Link (${this.id})`\n this.email = 'guest-link@sync-in'\n }\n }\n\n private setApplications() {\n if (this.isGuest) {\n // dynamically set the permissions\n this.applications = Object.values(GUEST_PERMISSION)\n } else if (this.permissions) {\n this.applications = this.permissions.split(USER_PERMS_SEP)\n }\n delete this.permissions\n }\n\n havePermission(permission: string): boolean {\n if (this.isAdmin) {\n return true\n }\n if (permission === USER_PERMISSION.PERSONAL_SPACE && this.isGuest) {\n return false\n }\n return this.applications.indexOf(permission) !== -1\n }\n\n haveRole(role: number): boolean {\n return this.role <= role\n }\n\n static getHomePath(userLogin: string, isGuest = false, isLink = false): string {\n if (isGuest || isLink) {\n return path.join(configuration.applications.files.tmpPath, isGuest ? 'guests' : 'links', userLogin)\n }\n return path.join(configuration.applications.files.usersPath, userLogin)\n }\n\n static getFilesPath(userLogin: string): string {\n return path.join(UserModel.getHomePath(userLogin), SPACE_REPOSITORY.FILES)\n }\n\n static getTrashPath(userLogin: string): string {\n return path.join(UserModel.getHomePath(userLogin), SPACE_REPOSITORY.TRASH)\n }\n\n static getTasksPath(userLogin: string, isGuest = false, isLink = false): string {\n return path.join(UserModel.getHomePath(userLogin, isGuest, isLink), USER_PATH.TMP, USER_PATH.TASKS)\n }\n\n static getRepositoryPath(userLogin: string, inTrash = false): string {\n if (inTrash) return UserModel.getTrashPath(userLogin)\n return UserModel.getFilesPath(userLogin)\n }\n}\n"],"names":["UserModel","toString","login","id","removePassword","password","setFullName","fullName","firstName","lastName","trim","isAdmin","role","USER_ROLE","ADMINISTRATOR","isUser","USER","isGuest","GUEST","isLink","LINK","quotaIsExceeded","storageQuota","storageUsage","homePath","_homePath","path","join","configuration","applications","files","tmpPath","usersPath","filesPath","_filesPath","SPACE_REPOSITORY","FILES","trashPath","_trashPath","TRASH","_tmpPath","USER_PATH","TMP","tasksPath","_tasksPath","TASKS","makePaths","fs","mkdir","recursive","p","asOwner","email","getInitials","initials","f","charAt","l","toUpperCase","toLowerCase","setProfile","setApplications","Object","values","GUEST_PERMISSION","permissions","split","USER_PERMS_SEP","havePermission","permission","USER_PERMISSION","PERSONAL_SPACE","indexOf","haveRole","getHomePath","userLogin","getFilesPath","getTrashPath","getTasksPath","getRepositoryPath","inTrash","props","assign","toPlainOnly"],"mappings":"AAAA;;;;CAIC;;;;+BAWYA;;;eAAAA;;;kCATmB;iEACjB;iEACE;mCACa;wBACG;sBACuD;;;;;;;;;;;;;;;AAIjF,IAAA,AAAMA,YAAN,MAAMA;IAyDXC,WAAmB;QACjB,OAAO,CAAC,OAAO,EAAE,IAAI,CAACC,KAAK,CAAC,GAAG,EAAE,IAAI,CAACC,EAAE,CAAC,EAAE,CAAC;IAC9C;IAEAC,iBAAiB;QACf,OAAO,IAAI,CAACC,QAAQ;IACtB;IAEAC,cAAc;QACZ,IAAI,CAAC,IAAI,CAACC,QAAQ,EAAE;YAClB,IAAI,CAACA,QAAQ,GAAG,GAAG,IAAI,CAACC,SAAS,IAAI,GAAG,CAAC,EAAE,IAAI,CAACC,QAAQ,IAAI,IAAI,CAACC,IAAI;QACvE;IACF;IAEA,IACIC,UAAmB;QACrB,OAAO,IAAI,CAACC,IAAI,KAAKC,eAAS,CAACC,aAAa;IAC9C;IAEA,IACIC,SAAkB;QACpB,OAAO,IAAI,CAACH,IAAI,KAAKC,eAAS,CAACG,IAAI,IAAI,IAAI,CAACJ,IAAI,KAAKC,eAAS,CAACC,aAAa;IAC9E;IAEA,IACIG,UAAmB;QACrB,OAAO,IAAI,CAACL,IAAI,KAAKC,eAAS,CAACK,KAAK;IACtC;IAEA,IACIC,SAAkB;QACpB,OAAO,IAAI,CAACP,IAAI,KAAKC,eAAS,CAACO,IAAI;IACrC;IAEA,IACIC,kBAA2B;QAC7B,OAAO,IAAI,CAACC,YAAY,KAAK,QAAQ,IAAI,CAACC,YAAY,IAAI,IAAI,CAACD,YAAY;IAC7E;IAEA,IAAIE,WAAmB;QACrB,IAAI,IAAI,CAACL,MAAM,IAAI,IAAI,CAACF,OAAO,EAAE;YAC/B,OAAQ,IAAI,CAACQ,SAAS,KAAKC,iBAAI,CAACC,IAAI,CAACC,gCAAa,CAACC,YAAY,CAACC,KAAK,CAACC,OAAO,EAAE,IAAI,CAACd,OAAO,GAAG,WAAW,SAAS,IAAI,CAACf,KAAK;QAC9H;QACA,OAAQ,IAAI,CAACuB,SAAS,KAAKC,iBAAI,CAACC,IAAI,CAACC,gCAAa,CAACC,YAAY,CAACC,KAAK,CAACE,SAAS,EAAE,IAAI,CAAC9B,KAAK;IAC7F;IAEA,IAAI+B,YAAoB;QACtB,OAAQ,IAAI,CAACC,UAAU,KAAKR,iBAAI,CAACC,IAAI,CAAC,IAAI,CAACH,QAAQ,EAAEW,wBAAgB,CAACC,KAAK;IAC7E;IAEA,IAAIC,YAAoB;QACtB,OAAQ,IAAI,CAACC,UAAU,KAAKZ,iBAAI,CAACC,IAAI,CAAC,IAAI,CAACH,QAAQ,EAAEW,wBAAgB,CAACI,KAAK;IAC7E;IAEA,IAAIR,UAAkB;QACpB,OAAQ,IAAI,CAACS,QAAQ,KAAKd,iBAAI,CAACC,IAAI,CAAC,IAAI,CAACH,QAAQ,EAAEiB,eAAS,CAACC,GAAG;IAClE;IAEA,IAAIC,YAAoB;QACtB,OAAQ,IAAI,CAACC,UAAU,KAAKlB,iBAAI,CAACC,IAAI,CAAC,IAAI,CAACI,OAAO,EAAEU,eAAS,CAACI,KAAK;IACrE;IAEA,MAAMC,YAA2B;QAC/B,IAAI,IAAI,CAAC7B,OAAO,IAAI,IAAI,CAACE,MAAM,EAAE;YAC/B,MAAM4B,iBAAE,CAACC,KAAK,CAAC,IAAI,CAACL,SAAS,EAAE;gBAAEM,WAAW;YAAK;QACnD,OAAO;YACL,KAAK,MAAMC,KAAK;gBAAC,IAAI,CAACjB,SAAS;gBAAE,IAAI,CAACI,SAAS;gBAAE,IAAI,CAACM,SAAS;aAAC,CAAE;gBAChE,MAAMI,iBAAE,CAACC,KAAK,CAACE,GAAG;oBAAED,WAAW;gBAAK;YACtC;QACF;IACF;IAEAE,UAAiB;QACf,OAAO;YAAEhD,IAAI,IAAI,CAACA,EAAE;YAAED,OAAO,IAAI,CAACA,KAAK;YAAEkD,OAAO,IAAI,CAACA,KAAK;YAAE7C,UAAU,IAAI,CAACA,QAAQ;QAAC;IACtF;IAEA8C,cAAsB;QACpB,IAAIC;QACJ,IAAI,IAAI,CAAC9C,SAAS,EAAE;YAClB,IAAI,IAAI,CAACC,QAAQ,EAAE;gBACjB6C,WAAW;oBAAEC,GAAG,IAAI,CAAC/C,SAAS,CAACgD,MAAM,CAAC;oBAAIC,GAAG,IAAI,CAAChD,QAAQ,CAAC+C,MAAM,CAAC;gBAAG;YACvE;YACAF,WAAW;gBAAEC,GAAG,IAAI,CAAC/C,SAAS,CAACgD,MAAM,CAAC;gBAAIC,GAAG,IAAI,CAACjD,SAAS,CAACgD,MAAM,CAAC;YAAG;QACxE,OAAO;YACLF,WAAW;gBAAEC,GAAG,IAAI,CAACrD,KAAK,CAACsD,MAAM,CAAC;gBAAIC,GAAG,IAAI,CAACvD,KAAK,CAACsD,MAAM,CAAC;YAAG;QAChE;QACA,OAAO,GAAGF,SAASC,CAAC,CAACG,WAAW,KAAKJ,SAASG,CAAC,CAACE,WAAW,IAAI;IACjE;IAEQC,aAAa;QACnB,IAAI,IAAI,CAACzC,MAAM,EAAE;YACf,IAAI,CAACjB,KAAK,GAAG,CAAC,MAAM,EAAE,IAAI,CAACC,EAAE,CAAC,CAAC,CAAC;YAChC,IAAI,CAACiD,KAAK,GAAG;QACf;IACF;IAEQS,kBAAkB;QACxB,IAAI,IAAI,CAAC5C,OAAO,EAAE;YAChB,kCAAkC;YAClC,IAAI,CAACY,YAAY,GAAGiC,OAAOC,MAAM,CAACC,sBAAgB;QACpD,OAAO,IAAI,IAAI,CAACC,WAAW,EAAE;YAC3B,IAAI,CAACpC,YAAY,GAAG,IAAI,CAACoC,WAAW,CAACC,KAAK,CAACC,oBAAc;QAC3D;QACA,OAAO,IAAI,CAACF,WAAW;IACzB;IAEAG,eAAeC,UAAkB,EAAW;QAC1C,IAAI,IAAI,CAAC1D,OAAO,EAAE;YAChB,OAAO;QACT;QACA,IAAI0D,eAAeC,qBAAe,CAACC,cAAc,IAAI,IAAI,CAACtD,OAAO,EAAE;YACjE,OAAO;QACT;QACA,OAAO,IAAI,CAACY,YAAY,CAAC2C,OAAO,CAACH,gBAAgB,CAAC;IACpD;IAEAI,SAAS7D,IAAY,EAAW;QAC9B,OAAO,IAAI,CAACA,IAAI,IAAIA;IACtB;IAEA,OAAO8D,YAAYC,SAAiB,EAAE1D,UAAU,KAAK,EAAEE,SAAS,KAAK,EAAU;QAC7E,IAAIF,WAAWE,QAAQ;YACrB,OAAOO,iBAAI,CAACC,IAAI,CAACC,gCAAa,CAACC,YAAY,CAACC,KAAK,CAACC,OAAO,EAAEd,UAAU,WAAW,SAAS0D;QAC3F;QACA,OAAOjD,iBAAI,CAACC,IAAI,CAACC,gCAAa,CAACC,YAAY,CAACC,KAAK,CAACE,SAAS,EAAE2C;IAC/D;IAEA,OAAOC,aAAaD,SAAiB,EAAU;QAC7C,OAAOjD,iBAAI,CAACC,IAAI,CAAC3B,UAAU0E,WAAW,CAACC,YAAYxC,wBAAgB,CAACC,KAAK;IAC3E;IAEA,OAAOyC,aAAaF,SAAiB,EAAU;QAC7C,OAAOjD,iBAAI,CAACC,IAAI,CAAC3B,UAAU0E,WAAW,CAACC,YAAYxC,wBAAgB,CAACI,KAAK;IAC3E;IAEA,OAAOuC,aAAaH,SAAiB,EAAE1D,UAAU,KAAK,EAAEE,SAAS,KAAK,EAAU;QAC9E,OAAOO,iBAAI,CAACC,IAAI,CAAC3B,UAAU0E,WAAW,CAACC,WAAW1D,SAASE,SAASsB,eAAS,CAACC,GAAG,EAAED,eAAS,CAACI,KAAK;IACpG;IAEA,OAAOkC,kBAAkBJ,SAAiB,EAAEK,UAAU,KAAK,EAAU;QACnE,IAAIA,SAAS,OAAOhF,UAAU6E,YAAY,CAACF;QAC3C,OAAO3E,UAAU4E,YAAY,CAACD;IAChC;IA1JA,YAAYM,KAAuC,EAAE7E,iBAAiB,IAAI,CAAE;QAX5E,sBAAsB;aACtByB,eAAyB,EAAE;QAWzBiC,OAAOoB,MAAM,CAAC,IAAI,EAAED;QACpB,IAAI7E,gBAAgB;YAClB,2EAA2E;YAC3E,yDAAyD;YACzD,IAAI,CAACA,cAAc;QACrB;QACA,IAAI,CAACE,WAAW;QAChB,IAAI,CAACuD,eAAe;QACpB,IAAI,CAACD,UAAU;IACjB;AAiJF;;;;;;;;;;;QA9JauB,aAAa"}
1
+ {"version":3,"sources":["../../../../../backend/src/applications/users/models/user.model.ts"],"sourcesContent":["/*\n * Copyright (C) 2012-2025 Johan Legrand <johan.legrand@sync-in.com>\n * This file is part of Sync-in | The open source file sync and share solution\n * See the LICENSE file for licensing details\n */\n\nimport { Exclude, Expose } from 'class-transformer'\nimport fs from 'node:fs/promises'\nimport path from 'node:path'\nimport { configuration } from '../../../configuration/config.environment'\nimport { SPACE_REPOSITORY } from '../../spaces/constants/spaces'\nimport { GUEST_PERMISSION, USER_PATH, USER_PERMISSION, USER_PERMS_SEP, USER_ROLE } from '../constants/user'\nimport type { Owner } from '../interfaces/owner.interface'\nimport type { User } from '../schemas/user.interface'\n\nexport class UserModel implements User {\n id: number\n login: string\n email: string\n firstName: string\n lastName: string\n role: number\n language: string\n notification: number\n onlineStatus: number\n permissions: string\n storageUsage: number\n storageQuota: number\n isActive: boolean\n passwordAttempts: number\n currentIp: string\n lastIp: string\n currentAccess: Date\n lastAccess: Date\n createdAt: Date\n // exclusions\n @Exclude()\n password: string\n @Exclude()\n // only used on backend\n impersonatedFromId?: number\n impersonatedClientId?: string\n // used for desktop|cmd app\n clientId?: string\n\n // outside db schema\n fullName: string\n impersonated?: boolean\n avatarBase64?: string\n // permissions as a list\n applications: string[] = []\n private _homePath: string\n private _filesPath: string\n private _trashPath: string\n private _tmpPath: string\n private _tasksPath: string\n\n @Exclude({ toPlainOnly: true })\n exp?: number // refresh token expiration needed to refresh\n\n constructor(props: Partial<User> & { exp?: number }, removePassword = true) {\n Object.assign(this, props)\n if (removePassword) {\n // always remove the password field from model for obvious security reasons\n // do not remove it from `props` to not mutate the object\n this.removePassword()\n }\n this.setFullName()\n this.setApplications()\n this.setProfile()\n }\n\n toString(): string {\n return `*User <${this.login}> (${this.id})*`\n }\n\n removePassword() {\n delete this.password\n }\n\n setFullName() {\n if (!this.fullName) {\n this.fullName = `${this.firstName || ''} ${this.lastName || ''}`.trim()\n }\n }\n\n @Expose()\n get isAdmin(): boolean {\n return this.role === USER_ROLE.ADMINISTRATOR\n }\n\n @Expose()\n get isUser(): boolean {\n return this.role === USER_ROLE.USER || this.role === USER_ROLE.ADMINISTRATOR\n }\n\n @Expose()\n get isGuest(): boolean {\n return this.role === USER_ROLE.GUEST\n }\n\n @Expose()\n get isLink(): boolean {\n return this.role === USER_ROLE.LINK\n }\n\n @Expose()\n get quotaIsExceeded(): boolean {\n return this.storageQuota !== null && this.storageUsage >= this.storageQuota\n }\n\n get homePath(): string {\n if (this.isLink || this.isGuest) {\n return (this._homePath ||= path.join(configuration.applications.files.tmpPath, this.isGuest ? 'guests' : 'links', this.login))\n }\n return (this._homePath ||= path.join(configuration.applications.files.usersPath, this.login))\n }\n\n get filesPath(): string {\n return (this._filesPath ||= path.join(this.homePath, SPACE_REPOSITORY.FILES))\n }\n\n get trashPath(): string {\n return (this._trashPath ||= path.join(this.homePath, SPACE_REPOSITORY.TRASH))\n }\n\n get tmpPath(): string {\n return (this._tmpPath ||= path.join(this.homePath, USER_PATH.TMP))\n }\n\n get tasksPath(): string {\n return (this._tasksPath ||= path.join(this.tmpPath, USER_PATH.TASKS))\n }\n\n async makePaths(): Promise<void> {\n if (this.isGuest || this.isLink) {\n await fs.mkdir(this.tasksPath, { recursive: true })\n } else {\n for (const p of [this.filesPath, this.trashPath, this.tasksPath]) {\n await fs.mkdir(p, { recursive: true })\n }\n }\n }\n\n asOwner(): Owner {\n return { id: this.id, login: this.login, email: this.email, fullName: this.fullName }\n }\n\n getInitials(): string {\n let initials: { f: string; l: string }\n if (this.firstName) {\n if (this.lastName) {\n initials = { f: this.firstName.charAt(0), l: this.lastName.charAt(0) }\n }\n initials = { f: this.firstName.charAt(0), l: this.firstName.charAt(1) }\n } else {\n initials = { f: this.login.charAt(0), l: this.login.charAt(1) }\n }\n return `${initials.f.toUpperCase()}${initials.l.toLowerCase()}`\n }\n\n private setProfile() {\n if (this.isLink) {\n this.login = `Link (${this.id})`\n this.email = 'guest-link@sync-in'\n }\n }\n\n private setApplications() {\n if (this.isGuest) {\n // dynamically set the permissions\n this.applications = Object.values(GUEST_PERMISSION)\n } else if (this.permissions) {\n this.applications = this.permissions.split(USER_PERMS_SEP)\n }\n delete this.permissions\n }\n\n havePermission(permission: string): boolean {\n if (this.isAdmin) {\n return true\n }\n if (permission === USER_PERMISSION.PERSONAL_SPACE && this.isGuest) {\n return false\n }\n return this.applications.indexOf(permission) !== -1\n }\n\n haveRole(role: number): boolean {\n return this.role <= role\n }\n\n static getHomePath(userLogin: string, isGuest = false, isLink = false): string {\n if (isGuest || isLink) {\n return path.join(configuration.applications.files.tmpPath, isGuest ? 'guests' : 'links', userLogin)\n }\n return path.join(configuration.applications.files.usersPath, userLogin)\n }\n\n static getFilesPath(userLogin: string): string {\n return path.join(UserModel.getHomePath(userLogin), SPACE_REPOSITORY.FILES)\n }\n\n static getTrashPath(userLogin: string): string {\n return path.join(UserModel.getHomePath(userLogin), SPACE_REPOSITORY.TRASH)\n }\n\n static getTasksPath(userLogin: string, isGuest = false, isLink = false): string {\n return path.join(UserModel.getHomePath(userLogin, isGuest, isLink), USER_PATH.TMP, USER_PATH.TASKS)\n }\n\n static getRepositoryPath(userLogin: string, inTrash = false): string {\n if (inTrash) return UserModel.getTrashPath(userLogin)\n return UserModel.getFilesPath(userLogin)\n }\n}\n"],"names":["UserModel","toString","login","id","removePassword","password","setFullName","fullName","firstName","lastName","trim","isAdmin","role","USER_ROLE","ADMINISTRATOR","isUser","USER","isGuest","GUEST","isLink","LINK","quotaIsExceeded","storageQuota","storageUsage","homePath","_homePath","path","join","configuration","applications","files","tmpPath","usersPath","filesPath","_filesPath","SPACE_REPOSITORY","FILES","trashPath","_trashPath","TRASH","_tmpPath","USER_PATH","TMP","tasksPath","_tasksPath","TASKS","makePaths","fs","mkdir","recursive","p","asOwner","email","getInitials","initials","f","charAt","l","toUpperCase","toLowerCase","setProfile","setApplications","Object","values","GUEST_PERMISSION","permissions","split","USER_PERMS_SEP","havePermission","permission","USER_PERMISSION","PERSONAL_SPACE","indexOf","haveRole","getHomePath","userLogin","getFilesPath","getTrashPath","getTasksPath","getRepositoryPath","inTrash","props","assign","toPlainOnly"],"mappings":"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"}
@@ -69,11 +69,11 @@ const users = (0, _mysqlcore.mysqlTable)('users', {
69
69
  onlineStatus: (0, _mysqlcore.tinyint)('onlineStatus', {
70
70
  unsigned: true
71
71
  }).default(0).notNull(),
72
- currentIp: (0, _mysqlcore.char)('currentIp', {
73
- length: 15
72
+ currentIp: (0, _mysqlcore.varchar)('currentIp', {
73
+ length: 45
74
74
  }),
75
- lastIp: (0, _mysqlcore.char)('lastIp', {
76
- length: 15
75
+ lastIp: (0, _mysqlcore.varchar)('lastIp', {
76
+ length: 45
77
77
  }),
78
78
  currentAccess: (0, _mysqlcore.datetime)('currentAccess', {
79
79
  mode: 'date'
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../backend/src/applications/users/schemas/users.schema.ts"],"sourcesContent":["/*\n * Copyright (C) 2012-2025 Johan Legrand <johan.legrand@sync-in.com>\n * This file is part of Sync-in | The open source file sync and share solution\n * See the LICENSE file for licensing details\n */\n\nimport { SQL, sql } from 'drizzle-orm'\nimport { bigint, boolean, char, datetime, index, mysqlTable, tinyint, uniqueIndex, varchar } from 'drizzle-orm/mysql-core'\n\n/*\n role:\n 0: administrator\n 1: user\n 2: guest\n 3: link\n notification:\n 0: application\n 1: application + mail\n onlineStatus:\n 0: available\n 1: busy\n 2: absent\n 3: offline\n storageQuota:\n 0 : no storage\n null : unlimited\n other: limited to value\n*/\n\nexport const users = mysqlTable(\n 'users',\n {\n id: bigint('id', { mode: 'number', unsigned: true }).autoincrement().primaryKey(),\n email: varchar('email', { length: 255 }).notNull(),\n login: varchar('login', { length: 255 }).notNull(),\n firstName: varchar('firstName', { length: 255 }),\n lastName: varchar('lastName', { length: 255 }),\n password: varchar('password', { length: 255 }).notNull(),\n passwordAttempts: tinyint('passwordAttempts', { unsigned: true }).default(0).notNull(),\n role: tinyint('role', { unsigned: true }).default(1).notNull(),\n isActive: boolean('isActive').default(true).notNull(),\n language: char('language', { length: 2 }),\n permissions: varchar('permissions', { length: 255 }).default('').notNull(),\n storageUsage: bigint('storageUsage', { mode: 'number', unsigned: true }).default(0),\n storageQuota: bigint('storageQuota', { mode: 'number', unsigned: true }),\n notification: tinyint('notification', { unsigned: true }).default(1).notNull(),\n onlineStatus: tinyint('onlineStatus', { unsigned: true }).default(0).notNull(),\n currentIp: char('currentIp', { length: 15 }),\n lastIp: char('lastIp', { length: 15 }),\n currentAccess: datetime('currentAccess', { mode: 'date' }),\n lastAccess: datetime('lastAccess', { mode: 'date' }),\n createdAt: datetime('createdAt', { mode: 'date' })\n .default(sql`CURRENT_TIMESTAMP`)\n .notNull()\n },\n (table) => [uniqueIndex('email_idx').on(table.email), uniqueIndex('login_idx').on(table.login), index('role_idx').on(table.role)]\n)\n\nexport const userFullNameSQL = (user: any): SQL<string> => sql`TRIM(CONCAT(${user.firstName}, ' ', ${user.lastName}))`\n"],"names":["userFullNameSQL","users","mysqlTable","id","bigint","mode","unsigned","autoincrement","primaryKey","email","varchar","length","notNull","login","firstName","lastName","password","passwordAttempts","tinyint","default","role","isActive","boolean","language","char","permissions","storageUsage","storageQuota","notification","onlineStatus","currentIp","lastIp","currentAccess","datetime","lastAccess","createdAt","sql","table","uniqueIndex","on","index","user"],"mappings":"AAAA;;;;CAIC;;;;;;;;;;;QAsDYA;eAAAA;;QA7BAC;eAAAA;;;4BAvBY;2BACyE;AAsB3F,MAAMA,QAAQC,IAAAA,qBAAU,EAC7B,SACA;IACEC,IAAIC,IAAAA,iBAAM,EAAC,MAAM;QAAEC,MAAM;QAAUC,UAAU;IAAK,GAAGC,aAAa,GAAGC,UAAU;IAC/EC,OAAOC,IAAAA,kBAAO,EAAC,SAAS;QAAEC,QAAQ;IAAI,GAAGC,OAAO;IAChDC,OAAOH,IAAAA,kBAAO,EAAC,SAAS;QAAEC,QAAQ;IAAI,GAAGC,OAAO;IAChDE,WAAWJ,IAAAA,kBAAO,EAAC,aAAa;QAAEC,QAAQ;IAAI;IAC9CI,UAAUL,IAAAA,kBAAO,EAAC,YAAY;QAAEC,QAAQ;IAAI;IAC5CK,UAAUN,IAAAA,kBAAO,EAAC,YAAY;QAAEC,QAAQ;IAAI,GAAGC,OAAO;IACtDK,kBAAkBC,IAAAA,kBAAO,EAAC,oBAAoB;QAAEZ,UAAU;IAAK,GAAGa,OAAO,CAAC,GAAGP,OAAO;IACpFQ,MAAMF,IAAAA,kBAAO,EAAC,QAAQ;QAAEZ,UAAU;IAAK,GAAGa,OAAO,CAAC,GAAGP,OAAO;IAC5DS,UAAUC,IAAAA,kBAAO,EAAC,YAAYH,OAAO,CAAC,MAAMP,OAAO;IACnDW,UAAUC,IAAAA,eAAI,EAAC,YAAY;QAAEb,QAAQ;IAAE;IACvCc,aAAaf,IAAAA,kBAAO,EAAC,eAAe;QAAEC,QAAQ;IAAI,GAAGQ,OAAO,CAAC,IAAIP,OAAO;IACxEc,cAActB,IAAAA,iBAAM,EAAC,gBAAgB;QAAEC,MAAM;QAAUC,UAAU;IAAK,GAAGa,OAAO,CAAC;IACjFQ,cAAcvB,IAAAA,iBAAM,EAAC,gBAAgB;QAAEC,MAAM;QAAUC,UAAU;IAAK;IACtEsB,cAAcV,IAAAA,kBAAO,EAAC,gBAAgB;QAAEZ,UAAU;IAAK,GAAGa,OAAO,CAAC,GAAGP,OAAO;IAC5EiB,cAAcX,IAAAA,kBAAO,EAAC,gBAAgB;QAAEZ,UAAU;IAAK,GAAGa,OAAO,CAAC,GAAGP,OAAO;IAC5EkB,WAAWN,IAAAA,eAAI,EAAC,aAAa;QAAEb,QAAQ;IAAG;IAC1CoB,QAAQP,IAAAA,eAAI,EAAC,UAAU;QAAEb,QAAQ;IAAG;IACpCqB,eAAeC,IAAAA,mBAAQ,EAAC,iBAAiB;QAAE5B,MAAM;IAAO;IACxD6B,YAAYD,IAAAA,mBAAQ,EAAC,cAAc;QAAE5B,MAAM;IAAO;IAClD8B,WAAWF,IAAAA,mBAAQ,EAAC,aAAa;QAAE5B,MAAM;IAAO,GAC7Cc,OAAO,CAACiB,IAAAA,eAAG,CAAA,CAAC,iBAAiB,CAAC,EAC9BxB,OAAO;AACZ,GACA,CAACyB,QAAU;QAACC,IAAAA,sBAAW,EAAC,aAAaC,EAAE,CAACF,MAAM5B,KAAK;QAAG6B,IAAAA,sBAAW,EAAC,aAAaC,EAAE,CAACF,MAAMxB,KAAK;QAAG2B,IAAAA,gBAAK,EAAC,YAAYD,EAAE,CAACF,MAAMjB,IAAI;KAAE;AAG5H,MAAMpB,kBAAkB,CAACyC,OAA2BL,IAAAA,eAAG,CAAA,CAAC,YAAY,EAAEK,KAAK3B,SAAS,CAAC,OAAO,EAAE2B,KAAK1B,QAAQ,CAAC,EAAE,CAAC"}
1
+ {"version":3,"sources":["../../../../../backend/src/applications/users/schemas/users.schema.ts"],"sourcesContent":["/*\n * Copyright (C) 2012-2025 Johan Legrand <johan.legrand@sync-in.com>\n * This file is part of Sync-in | The open source file sync and share solution\n * See the LICENSE file for licensing details\n */\n\nimport { SQL, sql } from 'drizzle-orm'\nimport { bigint, boolean, char, datetime, index, mysqlTable, tinyint, uniqueIndex, varchar } from 'drizzle-orm/mysql-core'\n\n/*\n role:\n 0: administrator\n 1: user\n 2: guest\n 3: link\n notification:\n 0: application\n 1: application + mail\n onlineStatus:\n 0: available\n 1: busy\n 2: absent\n 3: offline\n storageQuota:\n 0 : no storage\n null : unlimited\n other: limited to value\n*/\n\nexport const users = mysqlTable(\n 'users',\n {\n id: bigint('id', { mode: 'number', unsigned: true }).autoincrement().primaryKey(),\n email: varchar('email', { length: 255 }).notNull(),\n login: varchar('login', { length: 255 }).notNull(),\n firstName: varchar('firstName', { length: 255 }),\n lastName: varchar('lastName', { length: 255 }),\n password: varchar('password', { length: 255 }).notNull(),\n passwordAttempts: tinyint('passwordAttempts', { unsigned: true }).default(0).notNull(),\n role: tinyint('role', { unsigned: true }).default(1).notNull(),\n isActive: boolean('isActive').default(true).notNull(),\n language: char('language', { length: 2 }),\n permissions: varchar('permissions', { length: 255 }).default('').notNull(),\n storageUsage: bigint('storageUsage', { mode: 'number', unsigned: true }).default(0),\n storageQuota: bigint('storageQuota', { mode: 'number', unsigned: true }),\n notification: tinyint('notification', { unsigned: true }).default(1).notNull(),\n onlineStatus: tinyint('onlineStatus', { unsigned: true }).default(0).notNull(),\n currentIp: varchar('currentIp', { length: 45 }),\n lastIp: varchar('lastIp', { length: 45 }),\n currentAccess: datetime('currentAccess', { mode: 'date' }),\n lastAccess: datetime('lastAccess', { mode: 'date' }),\n createdAt: datetime('createdAt', { mode: 'date' })\n .default(sql`CURRENT_TIMESTAMP`)\n .notNull()\n },\n (table) => [uniqueIndex('email_idx').on(table.email), uniqueIndex('login_idx').on(table.login), index('role_idx').on(table.role)]\n)\n\nexport const userFullNameSQL = (user: any): SQL<string> => sql`TRIM(CONCAT(${user.firstName}, ' ', ${user.lastName}))`\n"],"names":["userFullNameSQL","users","mysqlTable","id","bigint","mode","unsigned","autoincrement","primaryKey","email","varchar","length","notNull","login","firstName","lastName","password","passwordAttempts","tinyint","default","role","isActive","boolean","language","char","permissions","storageUsage","storageQuota","notification","onlineStatus","currentIp","lastIp","currentAccess","datetime","lastAccess","createdAt","sql","table","uniqueIndex","on","index","user"],"mappings":"AAAA;;;;CAIC;;;;;;;;;;;QAsDYA;eAAAA;;QA7BAC;eAAAA;;;4BAvBY;2BACyE;AAsB3F,MAAMA,QAAQC,IAAAA,qBAAU,EAC7B,SACA;IACEC,IAAIC,IAAAA,iBAAM,EAAC,MAAM;QAAEC,MAAM;QAAUC,UAAU;IAAK,GAAGC,aAAa,GAAGC,UAAU;IAC/EC,OAAOC,IAAAA,kBAAO,EAAC,SAAS;QAAEC,QAAQ;IAAI,GAAGC,OAAO;IAChDC,OAAOH,IAAAA,kBAAO,EAAC,SAAS;QAAEC,QAAQ;IAAI,GAAGC,OAAO;IAChDE,WAAWJ,IAAAA,kBAAO,EAAC,aAAa;QAAEC,QAAQ;IAAI;IAC9CI,UAAUL,IAAAA,kBAAO,EAAC,YAAY;QAAEC,QAAQ;IAAI;IAC5CK,UAAUN,IAAAA,kBAAO,EAAC,YAAY;QAAEC,QAAQ;IAAI,GAAGC,OAAO;IACtDK,kBAAkBC,IAAAA,kBAAO,EAAC,oBAAoB;QAAEZ,UAAU;IAAK,GAAGa,OAAO,CAAC,GAAGP,OAAO;IACpFQ,MAAMF,IAAAA,kBAAO,EAAC,QAAQ;QAAEZ,UAAU;IAAK,GAAGa,OAAO,CAAC,GAAGP,OAAO;IAC5DS,UAAUC,IAAAA,kBAAO,EAAC,YAAYH,OAAO,CAAC,MAAMP,OAAO;IACnDW,UAAUC,IAAAA,eAAI,EAAC,YAAY;QAAEb,QAAQ;IAAE;IACvCc,aAAaf,IAAAA,kBAAO,EAAC,eAAe;QAAEC,QAAQ;IAAI,GAAGQ,OAAO,CAAC,IAAIP,OAAO;IACxEc,cAActB,IAAAA,iBAAM,EAAC,gBAAgB;QAAEC,MAAM;QAAUC,UAAU;IAAK,GAAGa,OAAO,CAAC;IACjFQ,cAAcvB,IAAAA,iBAAM,EAAC,gBAAgB;QAAEC,MAAM;QAAUC,UAAU;IAAK;IACtEsB,cAAcV,IAAAA,kBAAO,EAAC,gBAAgB;QAAEZ,UAAU;IAAK,GAAGa,OAAO,CAAC,GAAGP,OAAO;IAC5EiB,cAAcX,IAAAA,kBAAO,EAAC,gBAAgB;QAAEZ,UAAU;IAAK,GAAGa,OAAO,CAAC,GAAGP,OAAO;IAC5EkB,WAAWpB,IAAAA,kBAAO,EAAC,aAAa;QAAEC,QAAQ;IAAG;IAC7CoB,QAAQrB,IAAAA,kBAAO,EAAC,UAAU;QAAEC,QAAQ;IAAG;IACvCqB,eAAeC,IAAAA,mBAAQ,EAAC,iBAAiB;QAAE5B,MAAM;IAAO;IACxD6B,YAAYD,IAAAA,mBAAQ,EAAC,cAAc;QAAE5B,MAAM;IAAO;IAClD8B,WAAWF,IAAAA,mBAAQ,EAAC,aAAa;QAAE5B,MAAM;IAAO,GAC7Cc,OAAO,CAACiB,IAAAA,eAAG,CAAA,CAAC,iBAAiB,CAAC,EAC9BxB,OAAO;AACZ,GACA,CAACyB,QAAU;QAACC,IAAAA,sBAAW,EAAC,aAAaC,EAAE,CAACF,MAAM5B,KAAK;QAAG6B,IAAAA,sBAAW,EAAC,aAAaC,EAAE,CAACF,MAAMxB,KAAK;QAAG2B,IAAAA,gBAAK,EAAC,YAAYD,EAAE,CAACF,MAAMjB,IAAI;KAAE;AAG5H,MAAMpB,kBAAkB,CAACyC,OAA2BL,IAAAA,eAAG,CAAA,CAAC,YAAY,EAAEK,KAAK3B,SAAS,CAAC,OAAO,EAAE2B,KAAK1B,QAAQ,CAAC,EAAE,CAAC"}
@@ -18,6 +18,7 @@ const _authbasicguard = require("./auth-basic.guard");
18
18
  const _authbasicstrategy = require("./auth-basic.strategy");
19
19
  describe(_authbasicguard.AuthBasicGuard.name, ()=>{
20
20
  let authBasicGuard;
21
+ let authBasicStrategy;
21
22
  let authMethod;
22
23
  let cache;
23
24
  let userTest;
@@ -37,7 +38,8 @@ describe(_authbasicguard.AuthBasicGuard.name, ()=>{
37
38
  {
38
39
  provide: _nestjspino.PinoLogger,
39
40
  useValue: {
40
- assign: ()=>undefined
41
+ assign: ()=>undefined,
42
+ error: jest.fn()
41
43
  }
42
44
  },
43
45
  {
@@ -51,6 +53,7 @@ describe(_authbasicguard.AuthBasicGuard.name, ()=>{
51
53
  ]
52
54
  }).compile();
53
55
  authBasicGuard = module.get(_authbasicguard.AuthBasicGuard);
56
+ authBasicStrategy = module.get(_authbasicstrategy.AuthBasicStrategy);
54
57
  authMethod = module.get(_authmethod.AuthMethod);
55
58
  cache = module.get(_cacheservice.Cache);
56
59
  userTest = new _usermodel.UserModel((0, _test.generateUserTest)(), false);
@@ -59,10 +62,12 @@ describe(_authbasicguard.AuthBasicGuard.name, ()=>{
59
62
  });
60
63
  it('should be defined', ()=>{
61
64
  expect(authBasicGuard).toBeDefined();
65
+ expect(authBasicStrategy).toBeDefined();
62
66
  expect(authMethod).toBeDefined();
63
67
  expect(cache).toBeDefined();
64
- expect(userTest).toBeDefined();
65
68
  expect(encodedAuth).toBeDefined();
69
+ expect(userTest).toBeDefined();
70
+ expect(userTest.password).toBeDefined();
66
71
  });
67
72
  it('should validate the user authentication', async ()=>{
68
73
  authMethod.validateUser = jest.fn().mockReturnValueOnce(userTest);
@@ -75,6 +80,7 @@ describe(_authbasicguard.AuthBasicGuard.name, ()=>{
75
80
  }
76
81
  });
77
82
  expect(await authBasicGuard.canActivate(context)).toBe(true);
83
+ expect(userTest.password).toBeUndefined();
78
84
  });
79
85
  it('should validate the user authentication with cache', async ()=>{
80
86
  cache.get = jest.fn().mockReturnValueOnce(userTest);
@@ -88,6 +94,36 @@ describe(_authbasicguard.AuthBasicGuard.name, ()=>{
88
94
  });
89
95
  expect(await authBasicGuard.canActivate(context)).toBe(true);
90
96
  });
97
+ it('should not validate the user authentication when cache returns null (explicitly unauthorized)', async ()=>{
98
+ cache.get = jest.fn().mockReturnValueOnce(null);
99
+ context.switchToHttp().getRequest.mockReturnValue({
100
+ raw: {
101
+ user: ''
102
+ },
103
+ headers: {
104
+ authorization: `Basic ${encodedAuth}`
105
+ }
106
+ });
107
+ await expect(authBasicGuard.canActivate(context)).rejects.toThrow();
108
+ });
109
+ it('should not validate the user authentication when cache returns undefined and database return null', async ()=>{
110
+ cache.get = jest.fn().mockReturnValueOnce(undefined);
111
+ authMethod.validateUser = jest.fn().mockReturnValueOnce(null);
112
+ jest.spyOn(cache, 'set').mockRejectedValueOnce(new Error('cache failed'));
113
+ context.switchToHttp().getRequest.mockReturnValue({
114
+ raw: {
115
+ user: ''
116
+ },
117
+ headers: {
118
+ authorization: `Basic ${encodedAuth}`
119
+ }
120
+ });
121
+ const loggerSpy = jest.spyOn(authBasicStrategy['logger'], 'error') // <-- spy the SAME instance used in the class
122
+ .mockImplementation(()=>undefined);
123
+ await expect(authBasicGuard.canActivate(context)).rejects.toThrow();
124
+ expect(loggerSpy).toHaveBeenCalled();
125
+ expect(loggerSpy.mock.calls[0][0]).toEqual(expect.stringContaining('cache failed'));
126
+ });
91
127
  it('should not validate the user authentication', async ()=>{
92
128
  context.switchToHttp().getRequest.mockReturnValue({
93
129
  raw: {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../backend/src/authentication/guards/auth-basic.guard.spec.ts"],"sourcesContent":["/*\n * Copyright (C) 2012-2025 Johan Legrand <johan.legrand@sync-in.com>\n * This file is part of Sync-in | The open source file sync and share solution\n * See the LICENSE file for licensing details\n */\n\nimport { createMock, DeepMocked } from '@golevelup/ts-jest'\nimport { ExecutionContext } from '@nestjs/common'\nimport { Test, TestingModule } from '@nestjs/testing'\nimport { PinoLogger } from 'nestjs-pino'\nimport { UserModel } from '../../applications/users/models/user.model'\nimport { generateUserTest } from '../../applications/users/utils/test'\nimport { WEBDAV_BASE_PATH } from '../../applications/webdav/constants/routes'\nimport { Cache } from '../../infrastructure/cache/services/cache.service'\nimport { AuthMethod } from '../models/auth-method'\nimport { AuthBasicGuard } from './auth-basic.guard'\nimport { AuthBasicStrategy } from './auth-basic.strategy'\n\ndescribe(AuthBasicGuard.name, () => {\n let authBasicGuard: AuthBasicGuard\n let authMethod: AuthMethod\n let cache: Cache\n let userTest: UserModel\n let encodedAuth: string\n let context: DeepMocked<ExecutionContext>\n\n beforeAll(async () => {\n const module: TestingModule = await Test.createTestingModule({\n providers: [\n AuthBasicGuard,\n AuthBasicStrategy,\n {\n provide: AuthMethod,\n useValue: {\n validateUser: async () => null\n }\n },\n {\n provide: PinoLogger,\n useValue: {\n assign: () => undefined\n }\n },\n {\n provide: Cache,\n useValue: {\n get: (_key: string) => undefined,\n set: async (_key: string, _value: string, _ttl: number) => undefined,\n genSlugKey: () => 'test'\n }\n }\n ]\n }).compile()\n\n authBasicGuard = module.get<AuthBasicGuard>(AuthBasicGuard)\n authMethod = module.get<AuthMethod>(AuthMethod)\n cache = module.get<Cache>(Cache)\n userTest = new UserModel(generateUserTest(), false)\n encodedAuth = Buffer.from(`${userTest.login}:${userTest.password}`).toString('base64')\n context = createMock<ExecutionContext>()\n })\n\n it('should be defined', () => {\n expect(authBasicGuard).toBeDefined()\n expect(authMethod).toBeDefined()\n expect(cache).toBeDefined()\n expect(userTest).toBeDefined()\n expect(encodedAuth).toBeDefined()\n })\n\n it('should validate the user authentication', async () => {\n authMethod.validateUser = jest.fn().mockReturnValueOnce(userTest)\n context.switchToHttp().getRequest.mockReturnValue({\n raw: { user: '' },\n headers: { authorization: `Basic ${encodedAuth}` }\n })\n expect(await authBasicGuard.canActivate(context)).toBe(true)\n })\n\n it('should validate the user authentication with cache', async () => {\n cache.get = jest.fn().mockReturnValueOnce(userTest)\n context.switchToHttp().getRequest.mockReturnValue({\n raw: { user: '' },\n headers: { authorization: `Basic ${encodedAuth}` }\n })\n expect(await authBasicGuard.canActivate(context)).toBe(true)\n })\n\n it('should not validate the user authentication', async () => {\n context.switchToHttp().getRequest.mockReturnValue({\n raw: { user: '' },\n headers: { authorization: `Basic ${encodedAuth}` }\n })\n await expect(authBasicGuard.canActivate(context)).rejects.toThrow()\n })\n\n it('should throw error due to malformed authorization header', async () => {\n // headers with capitals not working\n context.switchToHttp().getRequest.mockReturnValueOnce({\n raw: { user: '' },\n headers: { AUTHORIZATION: 'Basic foo' }\n })\n await expect(authBasicGuard.canActivate(context)).rejects.toThrow()\n context.switchToHttp().getRequest.mockReturnValueOnce({\n raw: { user: '' }\n })\n await expect(authBasicGuard.canActivate(context)).rejects.toThrow()\n })\n\n it(`should valid OPTIONS method without authentication header on \"/\" and \"/${WEBDAV_BASE_PATH}/*\" paths `, async () => {\n for (const url of ['', `/${WEBDAV_BASE_PATH}`, `/${WEBDAV_BASE_PATH}/foo/bar`]) {\n context.switchToHttp().getRequest.mockReturnValueOnce({\n method: 'OPTIONS',\n originalUrl: url,\n raw: { user: '' }\n })\n expect(await authBasicGuard.canActivate(context)).toBe(true)\n }\n })\n\n it('should not valid OPTIONS method with other paths', async () => {\n context.switchToHttp().getRequest.mockReturnValueOnce({\n method: 'OPTIONS',\n originalUrl: '/foo',\n raw: { user: '' }\n })\n await expect(authBasicGuard.canActivate(context)).rejects.toThrow()\n })\n})\n"],"names":["describe","AuthBasicGuard","name","authBasicGuard","authMethod","cache","userTest","encodedAuth","context","beforeAll","module","Test","createTestingModule","providers","AuthBasicStrategy","provide","AuthMethod","useValue","validateUser","PinoLogger","assign","undefined","Cache","get","_key","set","_value","_ttl","genSlugKey","compile","UserModel","generateUserTest","Buffer","from","login","password","toString","createMock","it","expect","toBeDefined","jest","fn","mockReturnValueOnce","switchToHttp","getRequest","mockReturnValue","raw","user","headers","authorization","canActivate","toBe","rejects","toThrow","AUTHORIZATION","WEBDAV_BASE_PATH","url","method","originalUrl"],"mappings":"AAAA;;;;CAIC;;;;wBAEsC;yBAEH;4BACT;2BACD;sBACO;wBACA;8BACX;4BACK;gCACI;mCACG;AAElCA,SAASC,8BAAc,CAACC,IAAI,EAAE;IAC5B,IAAIC;IACJ,IAAIC;IACJ,IAAIC;IACJ,IAAIC;IACJ,IAAIC;IACJ,IAAIC;IAEJC,UAAU;QACR,MAAMC,SAAwB,MAAMC,aAAI,CAACC,mBAAmB,CAAC;YAC3DC,WAAW;gBACTZ,8BAAc;gBACda,oCAAiB;gBACjB;oBACEC,SAASC,sBAAU;oBACnBC,UAAU;wBACRC,cAAc,UAAY;oBAC5B;gBACF;gBACA;oBACEH,SAASI,sBAAU;oBACnBF,UAAU;wBACRG,QAAQ,IAAMC;oBAChB;gBACF;gBACA;oBACEN,SAASO,mBAAK;oBACdL,UAAU;wBACRM,KAAK,CAACC,OAAiBH;wBACvBI,KAAK,OAAOD,MAAcE,QAAgBC,OAAiBN;wBAC3DO,YAAY,IAAM;oBACpB;gBACF;aACD;QACH,GAAGC,OAAO;QAEV1B,iBAAiBO,OAAOa,GAAG,CAAiBtB,8BAAc;QAC1DG,aAAaM,OAAOa,GAAG,CAAaP,sBAAU;QAC9CX,QAAQK,OAAOa,GAAG,CAAQD,mBAAK;QAC/BhB,WAAW,IAAIwB,oBAAS,CAACC,IAAAA,sBAAgB,KAAI;QAC7CxB,cAAcyB,OAAOC,IAAI,CAAC,GAAG3B,SAAS4B,KAAK,CAAC,CAAC,EAAE5B,SAAS6B,QAAQ,EAAE,EAAEC,QAAQ,CAAC;QAC7E5B,UAAU6B,IAAAA,kBAAU;IACtB;IAEAC,GAAG,qBAAqB;QACtBC,OAAOpC,gBAAgBqC,WAAW;QAClCD,OAAOnC,YAAYoC,WAAW;QAC9BD,OAAOlC,OAAOmC,WAAW;QACzBD,OAAOjC,UAAUkC,WAAW;QAC5BD,OAAOhC,aAAaiC,WAAW;IACjC;IAEAF,GAAG,2CAA2C;QAC5ClC,WAAWc,YAAY,GAAGuB,KAAKC,EAAE,GAAGC,mBAAmB,CAACrC;QACxDE,QAAQoC,YAAY,GAAGC,UAAU,CAACC,eAAe,CAAC;YAChDC,KAAK;gBAAEC,MAAM;YAAG;YAChBC,SAAS;gBAAEC,eAAe,CAAC,MAAM,EAAE3C,aAAa;YAAC;QACnD;QACAgC,OAAO,MAAMpC,eAAegD,WAAW,CAAC3C,UAAU4C,IAAI,CAAC;IACzD;IAEAd,GAAG,sDAAsD;QACvDjC,MAAMkB,GAAG,GAAGkB,KAAKC,EAAE,GAAGC,mBAAmB,CAACrC;QAC1CE,QAAQoC,YAAY,GAAGC,UAAU,CAACC,eAAe,CAAC;YAChDC,KAAK;gBAAEC,MAAM;YAAG;YAChBC,SAAS;gBAAEC,eAAe,CAAC,MAAM,EAAE3C,aAAa;YAAC;QACnD;QACAgC,OAAO,MAAMpC,eAAegD,WAAW,CAAC3C,UAAU4C,IAAI,CAAC;IACzD;IAEAd,GAAG,+CAA+C;QAChD9B,QAAQoC,YAAY,GAAGC,UAAU,CAACC,eAAe,CAAC;YAChDC,KAAK;gBAAEC,MAAM;YAAG;YAChBC,SAAS;gBAAEC,eAAe,CAAC,MAAM,EAAE3C,aAAa;YAAC;QACnD;QACA,MAAMgC,OAAOpC,eAAegD,WAAW,CAAC3C,UAAU6C,OAAO,CAACC,OAAO;IACnE;IAEAhB,GAAG,4DAA4D;QAC7D,oCAAoC;QACpC9B,QAAQoC,YAAY,GAAGC,UAAU,CAACF,mBAAmB,CAAC;YACpDI,KAAK;gBAAEC,MAAM;YAAG;YAChBC,SAAS;gBAAEM,eAAe;YAAY;QACxC;QACA,MAAMhB,OAAOpC,eAAegD,WAAW,CAAC3C,UAAU6C,OAAO,CAACC,OAAO;QACjE9C,QAAQoC,YAAY,GAAGC,UAAU,CAACF,mBAAmB,CAAC;YACpDI,KAAK;gBAAEC,MAAM;YAAG;QAClB;QACA,MAAMT,OAAOpC,eAAegD,WAAW,CAAC3C,UAAU6C,OAAO,CAACC,OAAO;IACnE;IAEAhB,GAAG,CAAC,uEAAuE,EAAEkB,wBAAgB,CAAC,UAAU,CAAC,EAAE;QACzG,KAAK,MAAMC,OAAO;YAAC;YAAI,CAAC,CAAC,EAAED,wBAAgB,EAAE;YAAE,CAAC,CAAC,EAAEA,wBAAgB,CAAC,QAAQ,CAAC;SAAC,CAAE;YAC9EhD,QAAQoC,YAAY,GAAGC,UAAU,CAACF,mBAAmB,CAAC;gBACpDe,QAAQ;gBACRC,aAAaF;gBACbV,KAAK;oBAAEC,MAAM;gBAAG;YAClB;YACAT,OAAO,MAAMpC,eAAegD,WAAW,CAAC3C,UAAU4C,IAAI,CAAC;QACzD;IACF;IAEAd,GAAG,oDAAoD;QACrD9B,QAAQoC,YAAY,GAAGC,UAAU,CAACF,mBAAmB,CAAC;YACpDe,QAAQ;YACRC,aAAa;YACbZ,KAAK;gBAAEC,MAAM;YAAG;QAClB;QACA,MAAMT,OAAOpC,eAAegD,WAAW,CAAC3C,UAAU6C,OAAO,CAACC,OAAO;IACnE;AACF"}
1
+ {"version":3,"sources":["../../../../backend/src/authentication/guards/auth-basic.guard.spec.ts"],"sourcesContent":["/*\n * Copyright (C) 2012-2025 Johan Legrand <johan.legrand@sync-in.com>\n * This file is part of Sync-in | The open source file sync and share solution\n * See the LICENSE file for licensing details\n */\n\nimport { createMock, DeepMocked } from '@golevelup/ts-jest'\nimport { ExecutionContext } from '@nestjs/common'\nimport { Test, TestingModule } from '@nestjs/testing'\nimport { PinoLogger } from 'nestjs-pino'\nimport { UserModel } from '../../applications/users/models/user.model'\nimport { generateUserTest } from '../../applications/users/utils/test'\nimport { WEBDAV_BASE_PATH } from '../../applications/webdav/constants/routes'\nimport { Cache } from '../../infrastructure/cache/services/cache.service'\nimport { AuthMethod } from '../models/auth-method'\nimport { AuthBasicGuard } from './auth-basic.guard'\nimport { AuthBasicStrategy } from './auth-basic.strategy'\n\ndescribe(AuthBasicGuard.name, () => {\n let authBasicGuard: AuthBasicGuard\n let authBasicStrategy: AuthBasicStrategy\n let authMethod: AuthMethod\n let cache: Cache\n let userTest: UserModel\n let encodedAuth: string\n let context: DeepMocked<ExecutionContext>\n\n beforeAll(async () => {\n const module: TestingModule = await Test.createTestingModule({\n providers: [\n AuthBasicGuard,\n AuthBasicStrategy,\n {\n provide: AuthMethod,\n useValue: {\n validateUser: async () => null\n }\n },\n {\n provide: PinoLogger,\n useValue: {\n assign: () => undefined,\n error: jest.fn()\n }\n },\n {\n provide: Cache,\n useValue: {\n get: (_key: string) => undefined,\n set: async (_key: string, _value: string, _ttl: number) => undefined,\n genSlugKey: () => 'test'\n }\n }\n ]\n }).compile()\n\n authBasicGuard = module.get<AuthBasicGuard>(AuthBasicGuard)\n authBasicStrategy = module.get<AuthBasicStrategy>(AuthBasicStrategy)\n authMethod = module.get<AuthMethod>(AuthMethod)\n cache = module.get<Cache>(Cache)\n userTest = new UserModel(generateUserTest(), false)\n encodedAuth = Buffer.from(`${userTest.login}:${userTest.password}`).toString('base64')\n context = createMock<ExecutionContext>()\n })\n\n it('should be defined', () => {\n expect(authBasicGuard).toBeDefined()\n expect(authBasicStrategy).toBeDefined()\n expect(authMethod).toBeDefined()\n expect(cache).toBeDefined()\n expect(encodedAuth).toBeDefined()\n expect(userTest).toBeDefined()\n expect(userTest.password).toBeDefined()\n })\n\n it('should validate the user authentication', async () => {\n authMethod.validateUser = jest.fn().mockReturnValueOnce(userTest)\n context.switchToHttp().getRequest.mockReturnValue({\n raw: { user: '' },\n headers: { authorization: `Basic ${encodedAuth}` }\n })\n expect(await authBasicGuard.canActivate(context)).toBe(true)\n expect(userTest.password).toBeUndefined()\n })\n\n it('should validate the user authentication with cache', async () => {\n cache.get = jest.fn().mockReturnValueOnce(userTest)\n context.switchToHttp().getRequest.mockReturnValue({\n raw: { user: '' },\n headers: { authorization: `Basic ${encodedAuth}` }\n })\n expect(await authBasicGuard.canActivate(context)).toBe(true)\n })\n\n it('should not validate the user authentication when cache returns null (explicitly unauthorized)', async () => {\n cache.get = jest.fn().mockReturnValueOnce(null)\n context.switchToHttp().getRequest.mockReturnValue({\n raw: { user: '' },\n headers: { authorization: `Basic ${encodedAuth}` }\n })\n await expect(authBasicGuard.canActivate(context)).rejects.toThrow()\n })\n\n it('should not validate the user authentication when cache returns undefined and database return null', async () => {\n cache.get = jest.fn().mockReturnValueOnce(undefined)\n authMethod.validateUser = jest.fn().mockReturnValueOnce(null)\n jest.spyOn(cache, 'set').mockRejectedValueOnce(new Error('cache failed'))\n context.switchToHttp().getRequest.mockReturnValue({\n raw: { user: '' },\n headers: { authorization: `Basic ${encodedAuth}` }\n })\n const loggerSpy = jest\n .spyOn(authBasicStrategy['logger'], 'error') // <-- spy the SAME instance used in the class\n .mockImplementation(() => undefined)\n await expect(authBasicGuard.canActivate(context)).rejects.toThrow()\n expect(loggerSpy).toHaveBeenCalled()\n expect(loggerSpy.mock.calls[0][0]).toEqual(expect.stringContaining('cache failed'))\n })\n\n it('should not validate the user authentication', async () => {\n context.switchToHttp().getRequest.mockReturnValue({\n raw: { user: '' },\n headers: { authorization: `Basic ${encodedAuth}` }\n })\n await expect(authBasicGuard.canActivate(context)).rejects.toThrow()\n })\n\n it('should throw error due to malformed authorization header', async () => {\n // headers with capitals not working\n context.switchToHttp().getRequest.mockReturnValueOnce({\n raw: { user: '' },\n headers: { AUTHORIZATION: 'Basic foo' }\n })\n await expect(authBasicGuard.canActivate(context)).rejects.toThrow()\n context.switchToHttp().getRequest.mockReturnValueOnce({\n raw: { user: '' }\n })\n await expect(authBasicGuard.canActivate(context)).rejects.toThrow()\n })\n\n it(`should valid OPTIONS method without authentication header on \"/\" and \"/${WEBDAV_BASE_PATH}/*\" paths `, async () => {\n for (const url of ['', `/${WEBDAV_BASE_PATH}`, `/${WEBDAV_BASE_PATH}/foo/bar`]) {\n context.switchToHttp().getRequest.mockReturnValueOnce({\n method: 'OPTIONS',\n originalUrl: url,\n raw: { user: '' }\n })\n expect(await authBasicGuard.canActivate(context)).toBe(true)\n }\n })\n\n it('should not valid OPTIONS method with other paths', async () => {\n context.switchToHttp().getRequest.mockReturnValueOnce({\n method: 'OPTIONS',\n originalUrl: '/foo',\n raw: { user: '' }\n })\n await expect(authBasicGuard.canActivate(context)).rejects.toThrow()\n })\n})\n"],"names":["describe","AuthBasicGuard","name","authBasicGuard","authBasicStrategy","authMethod","cache","userTest","encodedAuth","context","beforeAll","module","Test","createTestingModule","providers","AuthBasicStrategy","provide","AuthMethod","useValue","validateUser","PinoLogger","assign","undefined","error","jest","fn","Cache","get","_key","set","_value","_ttl","genSlugKey","compile","UserModel","generateUserTest","Buffer","from","login","password","toString","createMock","it","expect","toBeDefined","mockReturnValueOnce","switchToHttp","getRequest","mockReturnValue","raw","user","headers","authorization","canActivate","toBe","toBeUndefined","rejects","toThrow","spyOn","mockRejectedValueOnce","Error","loggerSpy","mockImplementation","toHaveBeenCalled","mock","calls","toEqual","stringContaining","AUTHORIZATION","WEBDAV_BASE_PATH","url","method","originalUrl"],"mappings":"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"}
@@ -31,7 +31,6 @@ function _ts_metadata(k, v) {
31
31
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
32
32
  }
33
33
  let AuthBasicStrategy = class AuthBasicStrategy extends (0, _passport.PassportStrategy)(_passporthttp.BasicStrategy, 'basic') {
34
- // not declared properly: https://github.com/nestjs/passport/issues/929
35
34
  async validate(req, loginOrEmail, password) {
36
35
  this.logger.assign({
37
36
  user: loginOrEmail
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../backend/src/authentication/guards/auth-basic.strategy.ts"],"sourcesContent":["/*\n * Copyright (C) 2012-2025 Johan Legrand <johan.legrand@sync-in.com>\n * This file is part of Sync-in | The open source file sync and share solution\n * See the LICENSE file for licensing details\n */\n\nimport { Injectable } from '@nestjs/common'\nimport { PassportStrategy } from '@nestjs/passport'\nimport { instanceToPlain, plainToInstance } from 'class-transformer'\nimport { FastifyRequest } from 'fastify'\nimport { PinoLogger } from 'nestjs-pino'\nimport { BasicStrategy } from 'passport-http'\nimport { SERVER_NAME } from '../../app.constants'\nimport { UserModel } from '../../applications/users/models/user.model'\nimport { Cache } from '../../infrastructure/cache/services/cache.service'\nimport { AuthMethod } from '../models/auth-method'\n\n@Injectable()\nexport class AuthBasicStrategy extends PassportStrategy(BasicStrategy, 'basic') {\n static readonly CACHE_TTL = 900\n\n constructor(\n private readonly authMethod: AuthMethod,\n private readonly cache: Cache,\n private readonly logger: PinoLogger\n ) {\n super({ passReqToCallback: true, realm: SERVER_NAME })\n }\n\n // not declared properly: https://github.com/nestjs/passport/issues/929\n async validate(req: FastifyRequest, loginOrEmail: string, password: string): Promise<Omit<UserModel, 'password'> | null> {\n this.logger.assign({ user: loginOrEmail })\n const authBasicUser = `auth-webdav-${req.headers['authorization'].split(' ').at(-1).toLowerCase()}`\n const userFromCache: any = await this.cache.get(authBasicUser)\n if (userFromCache === null) {\n // not authorized\n return null\n }\n if (userFromCache !== undefined) {\n // cached\n return plainToInstance(UserModel, userFromCache)\n }\n const userFromDB: UserModel = await this.authMethod.validateUser(loginOrEmail, password, req.ip)\n if (userFromDB !== null) {\n userFromDB.removePassword()\n }\n const userToCache: Record<string, any> | null = userFromDB ? instanceToPlain(userFromDB, { excludePrefixes: ['_'] }) : null\n this.cache.set(authBasicUser, userToCache, AuthBasicStrategy.CACHE_TTL).catch((e: Error) => this.logger.error(`${this.validate.name} - ${e}`))\n return userFromDB\n }\n}\n"],"names":["AuthBasicStrategy","PassportStrategy","BasicStrategy","validate","req","loginOrEmail","password","logger","assign","user","authBasicUser","headers","split","at","toLowerCase","userFromCache","cache","get","undefined","plainToInstance","UserModel","userFromDB","authMethod","validateUser","ip","removePassword","userToCache","instanceToPlain","excludePrefixes","set","CACHE_TTL","catch","e","error","name","passReqToCallback","realm","SERVER_NAME"],"mappings":"AAAA;;;;CAIC;;;;+BAcYA;;;eAAAA;;;wBAZc;0BACM;kCACgB;4BAEtB;8BACG;8BACF;2BACF;8BACJ;4BACK;;;;;;;;;;AAGpB,IAAA,AAAMA,oBAAN,MAAMA,0BAA0BC,IAAAA,0BAAgB,EAACC,2BAAa,EAAE;IAWrE,wEAAwE;IACxE,MAAMC,SAASC,GAAmB,EAAEC,YAAoB,EAAEC,QAAgB,EAA+C;QACvH,IAAI,CAACC,MAAM,CAACC,MAAM,CAAC;YAAEC,MAAMJ;QAAa;QACxC,MAAMK,gBAAgB,CAAC,YAAY,EAAEN,IAAIO,OAAO,CAAC,gBAAgB,CAACC,KAAK,CAAC,KAAKC,EAAE,CAAC,CAAC,GAAGC,WAAW,IAAI;QACnG,MAAMC,gBAAqB,MAAM,IAAI,CAACC,KAAK,CAACC,GAAG,CAACP;QAChD,IAAIK,kBAAkB,MAAM;YAC1B,iBAAiB;YACjB,OAAO;QACT;QACA,IAAIA,kBAAkBG,WAAW;YAC/B,SAAS;YACT,OAAOC,IAAAA,iCAAe,EAACC,oBAAS,EAAEL;QACpC;QACA,MAAMM,aAAwB,MAAM,IAAI,CAACC,UAAU,CAACC,YAAY,CAAClB,cAAcC,UAAUF,IAAIoB,EAAE;QAC/F,IAAIH,eAAe,MAAM;YACvBA,WAAWI,cAAc;QAC3B;QACA,MAAMC,cAA0CL,aAAaM,IAAAA,iCAAe,EAACN,YAAY;YAAEO,iBAAiB;gBAAC;aAAI;QAAC,KAAK;QACvH,IAAI,CAACZ,KAAK,CAACa,GAAG,CAACnB,eAAegB,aAAa1B,kBAAkB8B,SAAS,EAAEC,KAAK,CAAC,CAACC,IAAa,IAAI,CAACzB,MAAM,CAAC0B,KAAK,CAAC,GAAG,IAAI,CAAC9B,QAAQ,CAAC+B,IAAI,CAAC,GAAG,EAAEF,GAAG;QAC5I,OAAOX;IACT;IA5BA,YACE,AAAiBC,UAAsB,EACvC,AAAiBN,KAAY,EAC7B,AAAiBT,MAAkB,CACnC;QACA,KAAK,CAAC;YAAE4B,mBAAmB;YAAMC,OAAOC,yBAAW;QAAC,SAJnCf,aAAAA,iBACAN,QAAAA,YACAT,SAAAA;IAGnB;AAuBF;AAhCaP,kBACK8B,YAAY"}
1
+ {"version":3,"sources":["../../../../backend/src/authentication/guards/auth-basic.strategy.ts"],"sourcesContent":["/*\n * Copyright (C) 2012-2025 Johan Legrand <johan.legrand@sync-in.com>\n * This file is part of Sync-in | The open source file sync and share solution\n * See the LICENSE file for licensing details\n */\n\nimport { Injectable } from '@nestjs/common'\nimport { AbstractStrategy, PassportStrategy } from '@nestjs/passport'\nimport { instanceToPlain, plainToInstance } from 'class-transformer'\nimport { FastifyRequest } from 'fastify'\nimport { PinoLogger } from 'nestjs-pino'\nimport { BasicStrategy } from 'passport-http'\nimport { SERVER_NAME } from '../../app.constants'\nimport { UserModel } from '../../applications/users/models/user.model'\nimport { Cache } from '../../infrastructure/cache/services/cache.service'\nimport { AuthMethod } from '../models/auth-method'\n\n@Injectable()\nexport class AuthBasicStrategy extends PassportStrategy(BasicStrategy, 'basic') implements AbstractStrategy {\n static readonly CACHE_TTL = 900\n\n constructor(\n private readonly authMethod: AuthMethod,\n private readonly cache: Cache,\n private readonly logger: PinoLogger\n ) {\n super({ passReqToCallback: true, realm: SERVER_NAME })\n }\n\n async validate(req: FastifyRequest, loginOrEmail: string, password: string): Promise<Omit<UserModel, 'password'> | null> {\n this.logger.assign({ user: loginOrEmail })\n const authBasicUser = `auth-webdav-${req.headers['authorization'].split(' ').at(-1).toLowerCase()}`\n const userFromCache: any = await this.cache.get(authBasicUser)\n if (userFromCache === null) {\n // not authorized\n return null\n }\n if (userFromCache !== undefined) {\n // cached\n return plainToInstance(UserModel, userFromCache)\n }\n const userFromDB: UserModel = await this.authMethod.validateUser(loginOrEmail, password, req.ip)\n if (userFromDB !== null) {\n userFromDB.removePassword()\n }\n const userToCache: Record<string, any> | null = userFromDB ? instanceToPlain(userFromDB, { excludePrefixes: ['_'] }) : null\n this.cache.set(authBasicUser, userToCache, AuthBasicStrategy.CACHE_TTL).catch((e: Error) => this.logger.error(`${this.validate.name} - ${e}`))\n return userFromDB\n }\n}\n"],"names":["AuthBasicStrategy","PassportStrategy","BasicStrategy","validate","req","loginOrEmail","password","logger","assign","user","authBasicUser","headers","split","at","toLowerCase","userFromCache","cache","get","undefined","plainToInstance","UserModel","userFromDB","authMethod","validateUser","ip","removePassword","userToCache","instanceToPlain","excludePrefixes","set","CACHE_TTL","catch","e","error","name","passReqToCallback","realm","SERVER_NAME"],"mappings":"AAAA;;;;CAIC;;;;+BAcYA;;;eAAAA;;;wBAZc;0BACwB;kCACF;4BAEtB;8BACG;8BACF;2BACF;8BACJ;4BACK;;;;;;;;;;AAGpB,IAAA,AAAMA,oBAAN,MAAMA,0BAA0BC,IAAAA,0BAAgB,EAACC,2BAAa,EAAE;IAWrE,MAAMC,SAASC,GAAmB,EAAEC,YAAoB,EAAEC,QAAgB,EAA+C;QACvH,IAAI,CAACC,MAAM,CAACC,MAAM,CAAC;YAAEC,MAAMJ;QAAa;QACxC,MAAMK,gBAAgB,CAAC,YAAY,EAAEN,IAAIO,OAAO,CAAC,gBAAgB,CAACC,KAAK,CAAC,KAAKC,EAAE,CAAC,CAAC,GAAGC,WAAW,IAAI;QACnG,MAAMC,gBAAqB,MAAM,IAAI,CAACC,KAAK,CAACC,GAAG,CAACP;QAChD,IAAIK,kBAAkB,MAAM;YAC1B,iBAAiB;YACjB,OAAO;QACT;QACA,IAAIA,kBAAkBG,WAAW;YAC/B,SAAS;YACT,OAAOC,IAAAA,iCAAe,EAACC,oBAAS,EAAEL;QACpC;QACA,MAAMM,aAAwB,MAAM,IAAI,CAACC,UAAU,CAACC,YAAY,CAAClB,cAAcC,UAAUF,IAAIoB,EAAE;QAC/F,IAAIH,eAAe,MAAM;YACvBA,WAAWI,cAAc;QAC3B;QACA,MAAMC,cAA0CL,aAAaM,IAAAA,iCAAe,EAACN,YAAY;YAAEO,iBAAiB;gBAAC;aAAI;QAAC,KAAK;QACvH,IAAI,CAACZ,KAAK,CAACa,GAAG,CAACnB,eAAegB,aAAa1B,kBAAkB8B,SAAS,EAAEC,KAAK,CAAC,CAACC,IAAa,IAAI,CAACzB,MAAM,CAAC0B,KAAK,CAAC,GAAG,IAAI,CAAC9B,QAAQ,CAAC+B,IAAI,CAAC,GAAG,EAAEF,GAAG;QAC5I,OAAOX;IACT;IA3BA,YACE,AAAiBC,UAAsB,EACvC,AAAiBN,KAAY,EAC7B,AAAiBT,MAAkB,CACnC;QACA,KAAK,CAAC;YAAE4B,mBAAmB;YAAMC,OAAOC,yBAAW;QAAC,SAJnCf,aAAAA,iBACAN,QAAAA,YACAT,SAAAA;IAGnB;AAsBF;AA/BaP,kBACK8B,YAAY"}
@@ -47,7 +47,7 @@ describe(_authlocalguard.AuthLocalGuard.name, ()=>{
47
47
  expect(userTest).toBeDefined();
48
48
  });
49
49
  it('should validate the user authentication', async ()=>{
50
- authMethod.validateUser = jest.fn().mockReturnValue(userTest);
50
+ authMethod.validateUser = jest.fn().mockReturnValueOnce(userTest);
51
51
  context.switchToHttp().getRequest.mockReturnValue({
52
52
  raw: {
53
53
  user: ''
@@ -57,10 +57,12 @@ describe(_authlocalguard.AuthLocalGuard.name, ()=>{
57
57
  password: userTest.password
58
58
  }
59
59
  });
60
- expect(await authLocalGuard.canActivate(context)).toBeDefined();
60
+ expect(await authLocalGuard.canActivate(context)).toBe(true);
61
+ expect(userTest.password).toBeUndefined();
61
62
  });
62
63
  it('should not validate the user authentication', async ()=>{
63
- authMethod.validateUser = jest.fn().mockReturnValue(null);
64
+ userTest.password = 'password';
65
+ authMethod.validateUser = jest.fn().mockReturnValueOnce(null);
64
66
  context.switchToHttp().getRequest.mockReturnValue({
65
67
  raw: {
66
68
  user: ''
@@ -70,10 +72,10 @@ describe(_authlocalguard.AuthLocalGuard.name, ()=>{
70
72
  password: userTest.password
71
73
  }
72
74
  });
73
- await expect(authLocalGuard.canActivate(context)).rejects.toThrow();
75
+ await expect(authLocalGuard.canActivate(context)).rejects.toThrow(/password/i);
74
76
  });
75
77
  it('should throw error due to malformed body', async ()=>{
76
- authMethod.validateUser = jest.fn().mockReturnValue(null);
78
+ authMethod.validateUser = jest.fn().mockReturnValueOnce(null);
77
79
  context.switchToHttp().getRequest.mockReturnValue({
78
80
  raw: {
79
81
  user: ''
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../backend/src/authentication/guards/auth-local.guard.spec.ts"],"sourcesContent":["/*\n * Copyright (C) 2012-2025 Johan Legrand <johan.legrand@sync-in.com>\n * This file is part of Sync-in | The open source file sync and share solution\n * See the LICENSE file for licensing details\n */\n\nimport { createMock, DeepMocked } from '@golevelup/ts-jest'\nimport { ExecutionContext } from '@nestjs/common'\nimport { Test, TestingModule } from '@nestjs/testing'\nimport { PinoLogger } from 'nestjs-pino'\nimport { UserModel } from '../../applications/users/models/user.model'\nimport { generateUserTest } from '../../applications/users/utils/test'\nimport { AuthMethod } from '../models/auth-method'\nimport { AuthLocalGuard } from './auth-local.guard'\nimport { AuthLocalStrategy } from './auth-local.strategy'\n\ndescribe(AuthLocalGuard.name, () => {\n let authLocalGuard: AuthLocalGuard\n let authMethod: AuthMethod\n let userTest: UserModel\n let context: DeepMocked<ExecutionContext>\n\n beforeAll(async () => {\n const module: TestingModule = await Test.createTestingModule({\n providers: [\n AuthLocalGuard,\n AuthLocalStrategy,\n { provide: AuthMethod, useValue: {} },\n {\n provide: PinoLogger,\n useValue: {\n assign: () => undefined\n }\n }\n ]\n }).compile()\n\n authLocalGuard = module.get<AuthLocalGuard>(AuthLocalGuard)\n authMethod = module.get<AuthMethod>(AuthMethod)\n userTest = new UserModel(generateUserTest(), false)\n context = createMock<ExecutionContext>()\n })\n\n it('should be defined', () => {\n expect(authLocalGuard).toBeDefined()\n expect(authMethod).toBeDefined()\n expect(userTest).toBeDefined()\n })\n\n it('should validate the user authentication', async () => {\n authMethod.validateUser = jest.fn().mockReturnValue(userTest)\n context.switchToHttp().getRequest.mockReturnValue({\n raw: { user: '' },\n body: {\n login: userTest.login,\n password: userTest.password\n }\n })\n expect(await authLocalGuard.canActivate(context)).toBeDefined()\n })\n\n it('should not validate the user authentication', async () => {\n authMethod.validateUser = jest.fn().mockReturnValue(null)\n context.switchToHttp().getRequest.mockReturnValue({\n raw: { user: '' },\n body: {\n login: userTest.login,\n password: userTest.password\n }\n })\n await expect(authLocalGuard.canActivate(context)).rejects.toThrow()\n })\n\n it('should throw error due to malformed body', async () => {\n authMethod.validateUser = jest.fn().mockReturnValue(null)\n context.switchToHttp().getRequest.mockReturnValue({\n raw: { user: '' },\n body: null\n })\n await expect(authLocalGuard.canActivate(context)).rejects.toThrow()\n })\n})\n"],"names":["describe","AuthLocalGuard","name","authLocalGuard","authMethod","userTest","context","beforeAll","module","Test","createTestingModule","providers","AuthLocalStrategy","provide","AuthMethod","useValue","PinoLogger","assign","undefined","compile","get","UserModel","generateUserTest","createMock","it","expect","toBeDefined","validateUser","jest","fn","mockReturnValue","switchToHttp","getRequest","raw","user","body","login","password","canActivate","rejects","toThrow"],"mappings":"AAAA;;;;CAIC;;;;wBAEsC;yBAEH;4BACT;2BACD;sBACO;4BACN;gCACI;mCACG;AAElCA,SAASC,8BAAc,CAACC,IAAI,EAAE;IAC5B,IAAIC;IACJ,IAAIC;IACJ,IAAIC;IACJ,IAAIC;IAEJC,UAAU;QACR,MAAMC,SAAwB,MAAMC,aAAI,CAACC,mBAAmB,CAAC;YAC3DC,WAAW;gBACTV,8BAAc;gBACdW,oCAAiB;gBACjB;oBAAEC,SAASC,sBAAU;oBAAEC,UAAU,CAAC;gBAAE;gBACpC;oBACEF,SAASG,sBAAU;oBACnBD,UAAU;wBACRE,QAAQ,IAAMC;oBAChB;gBACF;aACD;QACH,GAAGC,OAAO;QAEVhB,iBAAiBK,OAAOY,GAAG,CAAiBnB,8BAAc;QAC1DG,aAAaI,OAAOY,GAAG,CAAaN,sBAAU;QAC9CT,WAAW,IAAIgB,oBAAS,CAACC,IAAAA,sBAAgB,KAAI;QAC7ChB,UAAUiB,IAAAA,kBAAU;IACtB;IAEAC,GAAG,qBAAqB;QACtBC,OAAOtB,gBAAgBuB,WAAW;QAClCD,OAAOrB,YAAYsB,WAAW;QAC9BD,OAAOpB,UAAUqB,WAAW;IAC9B;IAEAF,GAAG,2CAA2C;QAC5CpB,WAAWuB,YAAY,GAAGC,KAAKC,EAAE,GAAGC,eAAe,CAACzB;QACpDC,QAAQyB,YAAY,GAAGC,UAAU,CAACF,eAAe,CAAC;YAChDG,KAAK;gBAAEC,MAAM;YAAG;YAChBC,MAAM;gBACJC,OAAO/B,SAAS+B,KAAK;gBACrBC,UAAUhC,SAASgC,QAAQ;YAC7B;QACF;QACAZ,OAAO,MAAMtB,eAAemC,WAAW,CAAChC,UAAUoB,WAAW;IAC/D;IAEAF,GAAG,+CAA+C;QAChDpB,WAAWuB,YAAY,GAAGC,KAAKC,EAAE,GAAGC,eAAe,CAAC;QACpDxB,QAAQyB,YAAY,GAAGC,UAAU,CAACF,eAAe,CAAC;YAChDG,KAAK;gBAAEC,MAAM;YAAG;YAChBC,MAAM;gBACJC,OAAO/B,SAAS+B,KAAK;gBACrBC,UAAUhC,SAASgC,QAAQ;YAC7B;QACF;QACA,MAAMZ,OAAOtB,eAAemC,WAAW,CAAChC,UAAUiC,OAAO,CAACC,OAAO;IACnE;IAEAhB,GAAG,4CAA4C;QAC7CpB,WAAWuB,YAAY,GAAGC,KAAKC,EAAE,GAAGC,eAAe,CAAC;QACpDxB,QAAQyB,YAAY,GAAGC,UAAU,CAACF,eAAe,CAAC;YAChDG,KAAK;gBAAEC,MAAM;YAAG;YAChBC,MAAM;QACR;QACA,MAAMV,OAAOtB,eAAemC,WAAW,CAAChC,UAAUiC,OAAO,CAACC,OAAO;IACnE;AACF"}
1
+ {"version":3,"sources":["../../../../backend/src/authentication/guards/auth-local.guard.spec.ts"],"sourcesContent":["/*\n * Copyright (C) 2012-2025 Johan Legrand <johan.legrand@sync-in.com>\n * This file is part of Sync-in | The open source file sync and share solution\n * See the LICENSE file for licensing details\n */\n\nimport { createMock, DeepMocked } from '@golevelup/ts-jest'\nimport { ExecutionContext } from '@nestjs/common'\nimport { Test, TestingModule } from '@nestjs/testing'\nimport { PinoLogger } from 'nestjs-pino'\nimport { UserModel } from '../../applications/users/models/user.model'\nimport { generateUserTest } from '../../applications/users/utils/test'\nimport { AuthMethod } from '../models/auth-method'\nimport { AuthLocalGuard } from './auth-local.guard'\nimport { AuthLocalStrategy } from './auth-local.strategy'\n\ndescribe(AuthLocalGuard.name, () => {\n let authLocalGuard: AuthLocalGuard\n let authMethod: AuthMethod\n let userTest: UserModel\n let context: DeepMocked<ExecutionContext>\n\n beforeAll(async () => {\n const module: TestingModule = await Test.createTestingModule({\n providers: [\n AuthLocalGuard,\n AuthLocalStrategy,\n { provide: AuthMethod, useValue: {} },\n {\n provide: PinoLogger,\n useValue: {\n assign: () => undefined\n }\n }\n ]\n }).compile()\n\n authLocalGuard = module.get<AuthLocalGuard>(AuthLocalGuard)\n authMethod = module.get<AuthMethod>(AuthMethod)\n userTest = new UserModel(generateUserTest(), false)\n context = createMock<ExecutionContext>()\n })\n\n it('should be defined', () => {\n expect(authLocalGuard).toBeDefined()\n expect(authMethod).toBeDefined()\n expect(userTest).toBeDefined()\n })\n\n it('should validate the user authentication', async () => {\n authMethod.validateUser = jest.fn().mockReturnValueOnce(userTest)\n context.switchToHttp().getRequest.mockReturnValue({\n raw: { user: '' },\n body: {\n login: userTest.login,\n password: userTest.password\n }\n })\n expect(await authLocalGuard.canActivate(context)).toBe(true)\n expect(userTest.password).toBeUndefined()\n })\n\n it('should not validate the user authentication', async () => {\n userTest.password = 'password'\n authMethod.validateUser = jest.fn().mockReturnValueOnce(null)\n context.switchToHttp().getRequest.mockReturnValue({\n raw: { user: '' },\n body: {\n login: userTest.login,\n password: userTest.password\n }\n })\n await expect(authLocalGuard.canActivate(context)).rejects.toThrow(/password/i)\n })\n\n it('should throw error due to malformed body', async () => {\n authMethod.validateUser = jest.fn().mockReturnValueOnce(null)\n context.switchToHttp().getRequest.mockReturnValue({\n raw: { user: '' },\n body: null\n })\n await expect(authLocalGuard.canActivate(context)).rejects.toThrow()\n })\n})\n"],"names":["describe","AuthLocalGuard","name","authLocalGuard","authMethod","userTest","context","beforeAll","module","Test","createTestingModule","providers","AuthLocalStrategy","provide","AuthMethod","useValue","PinoLogger","assign","undefined","compile","get","UserModel","generateUserTest","createMock","it","expect","toBeDefined","validateUser","jest","fn","mockReturnValueOnce","switchToHttp","getRequest","mockReturnValue","raw","user","body","login","password","canActivate","toBe","toBeUndefined","rejects","toThrow"],"mappings":"AAAA;;;;CAIC;;;;wBAEsC;yBAEH;4BACT;2BACD;sBACO;4BACN;gCACI;mCACG;AAElCA,SAASC,8BAAc,CAACC,IAAI,EAAE;IAC5B,IAAIC;IACJ,IAAIC;IACJ,IAAIC;IACJ,IAAIC;IAEJC,UAAU;QACR,MAAMC,SAAwB,MAAMC,aAAI,CAACC,mBAAmB,CAAC;YAC3DC,WAAW;gBACTV,8BAAc;gBACdW,oCAAiB;gBACjB;oBAAEC,SAASC,sBAAU;oBAAEC,UAAU,CAAC;gBAAE;gBACpC;oBACEF,SAASG,sBAAU;oBACnBD,UAAU;wBACRE,QAAQ,IAAMC;oBAChB;gBACF;aACD;QACH,GAAGC,OAAO;QAEVhB,iBAAiBK,OAAOY,GAAG,CAAiBnB,8BAAc;QAC1DG,aAAaI,OAAOY,GAAG,CAAaN,sBAAU;QAC9CT,WAAW,IAAIgB,oBAAS,CAACC,IAAAA,sBAAgB,KAAI;QAC7ChB,UAAUiB,IAAAA,kBAAU;IACtB;IAEAC,GAAG,qBAAqB;QACtBC,OAAOtB,gBAAgBuB,WAAW;QAClCD,OAAOrB,YAAYsB,WAAW;QAC9BD,OAAOpB,UAAUqB,WAAW;IAC9B;IAEAF,GAAG,2CAA2C;QAC5CpB,WAAWuB,YAAY,GAAGC,KAAKC,EAAE,GAAGC,mBAAmB,CAACzB;QACxDC,QAAQyB,YAAY,GAAGC,UAAU,CAACC,eAAe,CAAC;YAChDC,KAAK;gBAAEC,MAAM;YAAG;YAChBC,MAAM;gBACJC,OAAOhC,SAASgC,KAAK;gBACrBC,UAAUjC,SAASiC,QAAQ;YAC7B;QACF;QACAb,OAAO,MAAMtB,eAAeoC,WAAW,CAACjC,UAAUkC,IAAI,CAAC;QACvDf,OAAOpB,SAASiC,QAAQ,EAAEG,aAAa;IACzC;IAEAjB,GAAG,+CAA+C;QAChDnB,SAASiC,QAAQ,GAAG;QACpBlC,WAAWuB,YAAY,GAAGC,KAAKC,EAAE,GAAGC,mBAAmB,CAAC;QACxDxB,QAAQyB,YAAY,GAAGC,UAAU,CAACC,eAAe,CAAC;YAChDC,KAAK;gBAAEC,MAAM;YAAG;YAChBC,MAAM;gBACJC,OAAOhC,SAASgC,KAAK;gBACrBC,UAAUjC,SAASiC,QAAQ;YAC7B;QACF;QACA,MAAMb,OAAOtB,eAAeoC,WAAW,CAACjC,UAAUoC,OAAO,CAACC,OAAO,CAAC;IACpE;IAEAnB,GAAG,4CAA4C;QAC7CpB,WAAWuB,YAAY,GAAGC,KAAKC,EAAE,GAAGC,mBAAmB,CAAC;QACxDxB,QAAQyB,YAAY,GAAGC,UAAU,CAACC,eAAe,CAAC;YAChDC,KAAK;gBAAEC,MAAM;YAAG;YAChBC,MAAM;QACR;QACA,MAAMX,OAAOtB,eAAeoC,WAAW,CAACjC,UAAUoC,OAAO,CAACC,OAAO;IACnE;AACF"}
@@ -27,7 +27,6 @@ function _ts_metadata(k, v) {
27
27
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
28
28
  }
29
29
  let AuthLocalStrategy = class AuthLocalStrategy extends (0, _passport.PassportStrategy)(_passportlocal.Strategy, 'local') {
30
- // not declared properly: https://github.com/nestjs/passport/issues/929
31
30
  async validate(req, loginOrEmail, password) {
32
31
  this.logger.assign({
33
32
  user: loginOrEmail
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../backend/src/authentication/guards/auth-local.strategy.ts"],"sourcesContent":["/*\n * Copyright (C) 2012-2025 Johan Legrand <johan.legrand@sync-in.com>\n * This file is part of Sync-in | The open source file sync and share solution\n * See the LICENSE file for licensing details\n */\n\nimport { Injectable, UnauthorizedException } from '@nestjs/common'\nimport { PassportStrategy } from '@nestjs/passport'\nimport type { FastifyRequest } from 'fastify'\nimport { PinoLogger } from 'nestjs-pino'\nimport { Strategy } from 'passport-local'\nimport type { UserModel } from '../../applications/users/models/user.model'\nimport { AuthMethod } from '../models/auth-method'\n\n@Injectable()\nexport class AuthLocalStrategy extends PassportStrategy(Strategy, 'local') implements Strategy {\n constructor(\n private readonly authMethod: AuthMethod,\n private readonly logger: PinoLogger\n ) {\n super({ usernameField: 'login', passwordField: 'password', passReqToCallback: true })\n }\n\n // not declared properly: https://github.com/nestjs/passport/issues/929\n async validate(req: FastifyRequest, loginOrEmail: string, password: string): Promise<UserModel> {\n this.logger.assign({ user: loginOrEmail })\n const user: UserModel = await this.authMethod.validateUser(loginOrEmail, password, req.ip)\n if (user) {\n user.removePassword()\n return user\n }\n throw new UnauthorizedException('Wrong login or password')\n }\n}\n"],"names":["AuthLocalStrategy","PassportStrategy","Strategy","validate","req","loginOrEmail","password","logger","assign","user","authMethod","validateUser","ip","removePassword","UnauthorizedException","usernameField","passwordField","passReqToCallback"],"mappings":"AAAA;;;;CAIC;;;;+BAWYA;;;eAAAA;;;wBATqC;0BACjB;4BAEN;+BACF;4BAEE;;;;;;;;;;AAGpB,IAAA,AAAMA,oBAAN,MAAMA,0BAA0BC,IAAAA,0BAAgB,EAACC,uBAAQ,EAAE;IAQhE,wEAAwE;IACxE,MAAMC,SAASC,GAAmB,EAAEC,YAAoB,EAAEC,QAAgB,EAAsB;QAC9F,IAAI,CAACC,MAAM,CAACC,MAAM,CAAC;YAAEC,MAAMJ;QAAa;QACxC,MAAMI,OAAkB,MAAM,IAAI,CAACC,UAAU,CAACC,YAAY,CAACN,cAAcC,UAAUF,IAAIQ,EAAE;QACzF,IAAIH,MAAM;YACRA,KAAKI,cAAc;YACnB,OAAOJ;QACT;QACA,MAAM,IAAIK,6BAAqB,CAAC;IAClC;IAhBA,YACE,AAAiBJ,UAAsB,EACvC,AAAiBH,MAAkB,CACnC;QACA,KAAK,CAAC;YAAEQ,eAAe;YAASC,eAAe;YAAYC,mBAAmB;QAAK,SAHlEP,aAAAA,iBACAH,SAAAA;IAGnB;AAYF"}
1
+ {"version":3,"sources":["../../../../backend/src/authentication/guards/auth-local.strategy.ts"],"sourcesContent":["/*\n * Copyright (C) 2012-2025 Johan Legrand <johan.legrand@sync-in.com>\n * This file is part of Sync-in | The open source file sync and share solution\n * See the LICENSE file for licensing details\n */\n\nimport { Injectable, UnauthorizedException } from '@nestjs/common'\nimport { PassportStrategy, AbstractStrategy } from '@nestjs/passport'\nimport type { FastifyRequest } from 'fastify'\nimport { PinoLogger } from 'nestjs-pino'\nimport { Strategy } from 'passport-local'\nimport type { UserModel } from '../../applications/users/models/user.model'\nimport { AuthMethod } from '../models/auth-method'\n\n@Injectable()\nexport class AuthLocalStrategy extends PassportStrategy(Strategy, 'local') implements AbstractStrategy {\n constructor(\n private readonly authMethod: AuthMethod,\n private readonly logger: PinoLogger\n ) {\n super({ usernameField: 'login', passwordField: 'password', passReqToCallback: true })\n }\n\n async validate(req: FastifyRequest, loginOrEmail: string, password: string): Promise<UserModel> {\n this.logger.assign({ user: loginOrEmail })\n const user: UserModel = await this.authMethod.validateUser(loginOrEmail, password, req.ip)\n if (user) {\n user.removePassword()\n return user\n }\n throw new UnauthorizedException('Wrong login or password')\n }\n}\n"],"names":["AuthLocalStrategy","PassportStrategy","Strategy","validate","req","loginOrEmail","password","logger","assign","user","authMethod","validateUser","ip","removePassword","UnauthorizedException","usernameField","passwordField","passReqToCallback"],"mappings":"AAAA;;;;CAIC;;;;+BAWYA;;;eAAAA;;;wBATqC;0BACC;4BAExB;+BACF;4BAEE;;;;;;;;;;AAGpB,IAAA,AAAMA,oBAAN,MAAMA,0BAA0BC,IAAAA,0BAAgB,EAACC,uBAAQ,EAAE;IAQhE,MAAMC,SAASC,GAAmB,EAAEC,YAAoB,EAAEC,QAAgB,EAAsB;QAC9F,IAAI,CAACC,MAAM,CAACC,MAAM,CAAC;YAAEC,MAAMJ;QAAa;QACxC,MAAMI,OAAkB,MAAM,IAAI,CAACC,UAAU,CAACC,YAAY,CAACN,cAAcC,UAAUF,IAAIQ,EAAE;QACzF,IAAIH,MAAM;YACRA,KAAKI,cAAc;YACnB,OAAOJ;QACT;QACA,MAAM,IAAIK,6BAAqB,CAAC;IAClC;IAfA,YACE,AAAiBJ,UAAsB,EACvC,AAAiBH,MAAkB,CACnC;QACA,KAAK,CAAC;YAAEQ,eAAe;YAASC,eAAe;YAAYC,mBAAmB;QAAK,SAHlEP,aAAAA,iBACAH,SAAAA;IAGnB;AAWF"}
@@ -200,6 +200,36 @@ describe(_authtokenaccessguard.AuthTokenAccessGuard.name, ()=>{
200
200
  });
201
201
  await expect(authAccessGuard.canActivate(context)).rejects.toThrow(new RegExp(_auth.CSRF_ERROR.MISMATCH));
202
202
  });
203
+ it('should pass with method GET and a valid access token in cookies and a mismatch CSRF', async ()=>{
204
+ context.switchToHttp().getRequest.mockReturnValue({
205
+ method: 'GET',
206
+ raw: {
207
+ user: ''
208
+ },
209
+ headers: {
210
+ [authConfig.token.csrf.name]: csrfToken + '*'
211
+ },
212
+ cookies: {
213
+ [authConfig.token.access.name]: accessToken
214
+ }
215
+ });
216
+ await expect(authAccessGuard.canActivate(context)).resolves.not.toThrow();
217
+ });
218
+ it('should throw an error with method POST and a valid access token in cookies and a mismatch CSRF', async ()=>{
219
+ context.switchToHttp().getRequest.mockReturnValue({
220
+ method: 'POST',
221
+ raw: {
222
+ user: ''
223
+ },
224
+ headers: {
225
+ [authConfig.token.csrf.name]: csrfToken + '*'
226
+ },
227
+ cookies: {
228
+ [authConfig.token.access.name]: accessToken
229
+ }
230
+ });
231
+ await expect(authAccessGuard.canActivate(context)).rejects.toThrow(new RegExp(_auth.CSRF_ERROR.MISMATCH));
232
+ });
203
233
  it('should bypass access token when AuthTokenSkip decorator is applied to context', ()=>{
204
234
  context = (0, _tsjest.createMock)();
205
235
  (0, _authtokenskipdecorator.AuthTokenSkip)()(context.getHandler());