@sync-in/server 1.6.0 → 1.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (174) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/README.md +1 -1
  3. package/environment/environment.dist.yaml +1 -1
  4. package/package.json +10 -10
  5. package/server/authentication/auth.config.js +8 -4
  6. package/server/authentication/auth.config.js.map +1 -1
  7. package/server/authentication/constants/auth-ldap.js +44 -0
  8. package/server/authentication/constants/auth-ldap.js.map +1 -0
  9. package/server/authentication/services/auth-methods/auth-method-ldap.service.js +98 -86
  10. package/server/authentication/services/auth-methods/auth-method-ldap.service.js.map +1 -1
  11. package/server/authentication/services/auth-methods/auth-method-ldap.service.spec.js +50 -47
  12. package/server/authentication/services/auth-methods/auth-method-ldap.service.spec.js.map +1 -1
  13. package/static/assets/pdfjs/build/pdf.mjs +2522 -914
  14. package/static/assets/pdfjs/build/pdf.mjs.map +1 -1
  15. package/static/assets/pdfjs/build/pdf.sandbox.mjs +2 -2
  16. package/static/assets/pdfjs/build/pdf.worker.mjs +1024 -566
  17. package/static/assets/pdfjs/build/pdf.worker.mjs.map +1 -1
  18. package/static/assets/pdfjs/version +1 -1
  19. package/static/assets/pdfjs/web/debugger.mjs +116 -37
  20. package/static/assets/pdfjs/web/images/comment-popup-editButton.svg +5 -0
  21. package/static/assets/pdfjs/web/locale/ach/viewer.ftl +0 -12
  22. package/static/assets/pdfjs/web/locale/af/viewer.ftl +0 -12
  23. package/static/assets/pdfjs/web/locale/an/viewer.ftl +0 -16
  24. package/static/assets/pdfjs/web/locale/ar/viewer.ftl +0 -32
  25. package/static/assets/pdfjs/web/locale/ast/viewer.ftl +0 -19
  26. package/static/assets/pdfjs/web/locale/az/viewer.ftl +0 -16
  27. package/static/assets/pdfjs/web/locale/be/viewer.ftl +0 -32
  28. package/static/assets/pdfjs/web/locale/bg/viewer.ftl +0 -32
  29. package/static/assets/pdfjs/web/locale/bn/viewer.ftl +0 -16
  30. package/static/assets/pdfjs/web/locale/bo/viewer.ftl +0 -12
  31. package/static/assets/pdfjs/web/locale/br/viewer.ftl +0 -22
  32. package/static/assets/pdfjs/web/locale/brx/viewer.ftl +0 -16
  33. package/static/assets/pdfjs/web/locale/bs/viewer.ftl +0 -32
  34. package/static/assets/pdfjs/web/locale/ca/viewer.ftl +12 -23
  35. package/static/assets/pdfjs/web/locale/cak/viewer.ftl +0 -23
  36. package/static/assets/pdfjs/web/locale/ckb/viewer.ftl +0 -16
  37. package/static/assets/pdfjs/web/locale/cs/viewer.ftl +0 -32
  38. package/static/assets/pdfjs/web/locale/cy/viewer.ftl +0 -32
  39. package/static/assets/pdfjs/web/locale/da/viewer.ftl +3 -35
  40. package/static/assets/pdfjs/web/locale/de/viewer.ftl +0 -32
  41. package/static/assets/pdfjs/web/locale/dsb/viewer.ftl +0 -32
  42. package/static/assets/pdfjs/web/locale/el/viewer.ftl +0 -32
  43. package/static/assets/pdfjs/web/locale/en-CA/viewer.ftl +0 -32
  44. package/static/assets/pdfjs/web/locale/en-GB/viewer.ftl +0 -32
  45. package/static/assets/pdfjs/web/locale/en-US/viewer.ftl +25 -13
  46. package/static/assets/pdfjs/web/locale/eo/viewer.ftl +0 -32
  47. package/static/assets/pdfjs/web/locale/es-AR/viewer.ftl +0 -32
  48. package/static/assets/pdfjs/web/locale/es-CL/viewer.ftl +0 -32
  49. package/static/assets/pdfjs/web/locale/es-ES/viewer.ftl +5 -32
  50. package/static/assets/pdfjs/web/locale/es-MX/viewer.ftl +0 -32
  51. package/static/assets/pdfjs/web/locale/et/viewer.ftl +0 -16
  52. package/static/assets/pdfjs/web/locale/eu/viewer.ftl +38 -32
  53. package/static/assets/pdfjs/web/locale/fa/viewer.ftl +0 -19
  54. package/static/assets/pdfjs/web/locale/ff/viewer.ftl +0 -12
  55. package/static/assets/pdfjs/web/locale/fi/viewer.ftl +0 -32
  56. package/static/assets/pdfjs/web/locale/fr/viewer.ftl +0 -32
  57. package/static/assets/pdfjs/web/locale/fur/viewer.ftl +0 -32
  58. package/static/assets/pdfjs/web/locale/fy-NL/viewer.ftl +0 -32
  59. package/static/assets/pdfjs/web/locale/ga-IE/viewer.ftl +0 -12
  60. package/static/assets/pdfjs/web/locale/gd/viewer.ftl +0 -23
  61. package/static/assets/pdfjs/web/locale/gl/viewer.ftl +0 -32
  62. package/static/assets/pdfjs/web/locale/gn/viewer.ftl +0 -32
  63. package/static/assets/pdfjs/web/locale/gu-IN/viewer.ftl +0 -12
  64. package/static/assets/pdfjs/web/locale/he/viewer.ftl +0 -32
  65. package/static/assets/pdfjs/web/locale/hi-IN/viewer.ftl +0 -16
  66. package/static/assets/pdfjs/web/locale/hr/viewer.ftl +0 -32
  67. package/static/assets/pdfjs/web/locale/hsb/viewer.ftl +0 -32
  68. package/static/assets/pdfjs/web/locale/hu/viewer.ftl +0 -32
  69. package/static/assets/pdfjs/web/locale/hy-AM/viewer.ftl +372 -16
  70. package/static/assets/pdfjs/web/locale/hye/viewer.ftl +0 -16
  71. package/static/assets/pdfjs/web/locale/ia/viewer.ftl +0 -32
  72. package/static/assets/pdfjs/web/locale/id/viewer.ftl +38 -32
  73. package/static/assets/pdfjs/web/locale/is/viewer.ftl +27 -32
  74. package/static/assets/pdfjs/web/locale/it/viewer.ftl +0 -33
  75. package/static/assets/pdfjs/web/locale/ja/viewer.ftl +31 -33
  76. package/static/assets/pdfjs/web/locale/ka/viewer.ftl +0 -32
  77. package/static/assets/pdfjs/web/locale/kab/viewer.ftl +0 -32
  78. package/static/assets/pdfjs/web/locale/kk/viewer.ftl +31 -32
  79. package/static/assets/pdfjs/web/locale/km/viewer.ftl +0 -12
  80. package/static/assets/pdfjs/web/locale/kn/viewer.ftl +0 -12
  81. package/static/assets/pdfjs/web/locale/ko/viewer.ftl +0 -32
  82. package/static/assets/pdfjs/web/locale/lij/viewer.ftl +0 -12
  83. package/static/assets/pdfjs/web/locale/lo/viewer.ftl +0 -23
  84. package/static/assets/pdfjs/web/locale/lt/viewer.ftl +0 -16
  85. package/static/assets/pdfjs/web/locale/ltg/viewer.ftl +0 -12
  86. package/static/assets/pdfjs/web/locale/lv/viewer.ftl +0 -12
  87. package/static/assets/pdfjs/web/locale/meh/viewer.ftl +0 -14
  88. package/static/assets/pdfjs/web/locale/mk/viewer.ftl +0 -19
  89. package/static/assets/pdfjs/web/locale/ml/viewer.ftl +0 -31
  90. package/static/assets/pdfjs/web/locale/mr/viewer.ftl +0 -16
  91. package/static/assets/pdfjs/web/locale/ms/viewer.ftl +0 -12
  92. package/static/assets/pdfjs/web/locale/my/viewer.ftl +0 -12
  93. package/static/assets/pdfjs/web/locale/nb-NO/viewer.ftl +0 -32
  94. package/static/assets/pdfjs/web/locale/ne-NP/viewer.ftl +0 -12
  95. package/static/assets/pdfjs/web/locale/nl/viewer.ftl +0 -32
  96. package/static/assets/pdfjs/web/locale/nn-NO/viewer.ftl +0 -32
  97. package/static/assets/pdfjs/web/locale/oc/viewer.ftl +0 -24
  98. package/static/assets/pdfjs/web/locale/pa-IN/viewer.ftl +0 -32
  99. package/static/assets/pdfjs/web/locale/pl/viewer.ftl +0 -32
  100. package/static/assets/pdfjs/web/locale/pt-BR/viewer.ftl +0 -32
  101. package/static/assets/pdfjs/web/locale/pt-PT/viewer.ftl +0 -32
  102. package/static/assets/pdfjs/web/locale/rm/viewer.ftl +0 -32
  103. package/static/assets/pdfjs/web/locale/ro/viewer.ftl +5 -37
  104. package/static/assets/pdfjs/web/locale/ru/viewer.ftl +0 -32
  105. package/static/assets/pdfjs/web/locale/sat/viewer.ftl +0 -23
  106. package/static/assets/pdfjs/web/locale/sc/viewer.ftl +8 -27
  107. package/static/assets/pdfjs/web/locale/sco/viewer.ftl +0 -16
  108. package/static/assets/pdfjs/web/locale/si/viewer.ftl +0 -22
  109. package/static/assets/pdfjs/web/locale/sk/viewer.ftl +0 -32
  110. package/static/assets/pdfjs/web/locale/skr/viewer.ftl +0 -32
  111. package/static/assets/pdfjs/web/locale/sl/viewer.ftl +30 -32
  112. package/static/assets/pdfjs/web/locale/son/viewer.ftl +0 -12
  113. package/static/assets/pdfjs/web/locale/sq/viewer.ftl +0 -32
  114. package/static/assets/pdfjs/web/locale/sr/viewer.ftl +0 -32
  115. package/static/assets/pdfjs/web/locale/sv-SE/viewer.ftl +0 -32
  116. package/static/assets/pdfjs/web/locale/szl/viewer.ftl +0 -16
  117. package/static/assets/pdfjs/web/locale/ta/viewer.ftl +0 -12
  118. package/static/assets/pdfjs/web/locale/te/viewer.ftl +0 -16
  119. package/static/assets/pdfjs/web/locale/tg/viewer.ftl +0 -32
  120. package/static/assets/pdfjs/web/locale/th/viewer.ftl +38 -32
  121. package/static/assets/pdfjs/web/locale/tl/viewer.ftl +0 -16
  122. package/static/assets/pdfjs/web/locale/tr/viewer.ftl +0 -32
  123. package/static/assets/pdfjs/web/locale/trs/viewer.ftl +0 -12
  124. package/static/assets/pdfjs/web/locale/uk/viewer.ftl +0 -32
  125. package/static/assets/pdfjs/web/locale/ur/viewer.ftl +0 -16
  126. package/static/assets/pdfjs/web/locale/uz/viewer.ftl +0 -12
  127. package/static/assets/pdfjs/web/locale/vi/viewer.ftl +0 -32
  128. package/static/assets/pdfjs/web/locale/xh/viewer.ftl +0 -12
  129. package/static/assets/pdfjs/web/locale/zh-CN/viewer.ftl +0 -32
  130. package/static/assets/pdfjs/web/locale/zh-TW/viewer.ftl +0 -32
  131. package/static/assets/pdfjs/web/viewer.css +586 -437
  132. package/static/assets/pdfjs/web/viewer.html +12 -23
  133. package/static/assets/pdfjs/web/viewer.mjs +955 -514
  134. package/static/assets/pdfjs/web/viewer.mjs.map +1 -1
  135. package/static/assets/pdfjs/web/wasm/openjpeg.wasm +0 -0
  136. package/static/assets/pdfjs/web/wasm/openjpeg_nowasm_fallback.js +10 -22
  137. package/static/{chunk-YVJDYSDE.js → chunk-27YQB3TE.js} +1 -1
  138. package/static/{chunk-CN27VAGB.js → chunk-2I4CUFUA.js} +1 -1
  139. package/static/{chunk-2TZUZMCM.js → chunk-2MTM6SWN.js} +3 -3
  140. package/static/{chunk-5M4YJZUB.js → chunk-34MKICK5.js} +1 -1
  141. package/static/chunk-5O3DIUU3.js +1 -0
  142. package/static/{chunk-IIFHIIC6.js → chunk-6NMVZIIT.js} +1 -1
  143. package/static/{chunk-G2TKYYWK.js → chunk-7DN7ZAPU.js} +1 -1
  144. package/static/{chunk-DNMO47SY.js → chunk-7FUM3JGM.js} +1 -1
  145. package/static/{chunk-HME7LAEY.js → chunk-7ITZXYYJ.js} +1 -1
  146. package/static/{chunk-XPIYOZBX.js → chunk-7P27WBGC.js} +1 -1
  147. package/static/chunk-ATP3BFHV.js +562 -0
  148. package/static/{chunk-XCBLEI2E.js → chunk-AWQ2YTVC.js} +1 -1
  149. package/static/{chunk-NN3VQOS7.js → chunk-DSOE3FEP.js} +1 -1
  150. package/static/{chunk-ABGR5AYC.js → chunk-EFKMBLRE.js} +1 -1
  151. package/static/{chunk-ET6QDNNM.js → chunk-FUFKVHPU.js} +1 -1
  152. package/static/{chunk-5ZGQYTS2.js → chunk-HCDLWTMW.js} +1 -1
  153. package/static/{chunk-G3FOG2QB.js → chunk-IPAC4VAF.js} +1 -1
  154. package/static/{chunk-QFOMEU3T.js → chunk-IQOALFYU.js} +1 -1
  155. package/static/{chunk-6BFNMDUD.js → chunk-JASU3CIH.js} +1 -1
  156. package/static/{chunk-O3ANXCPE.js → chunk-JQ5FTO2M.js} +1 -1
  157. package/static/{chunk-IEUANP3Q.js → chunk-JUNZFADM.js} +1 -1
  158. package/static/{chunk-YD74UCFG.js → chunk-LJUKI4SQ.js} +1 -1
  159. package/static/{chunk-WINILGQN.js → chunk-LUWQFIWR.js} +1 -1
  160. package/static/{chunk-RKNTQYMU.js → chunk-ORMRCEGT.js} +1 -1
  161. package/static/{chunk-YDFVKH2D.js → chunk-Q7D6RN4N.js} +1 -1
  162. package/static/{chunk-M57NVD4V.js → chunk-QJX6ITLW.js} +1 -1
  163. package/static/{chunk-KPZ7FEMO.js → chunk-QQ6UQQBR.js} +1 -1
  164. package/static/{chunk-2XJ5Z2GZ.js → chunk-S2HDY3OL.js} +1 -1
  165. package/static/{chunk-X7MFVDBY.js → chunk-S75P2FFI.js} +1 -1
  166. package/static/{chunk-XLWCV4HI.js → chunk-T3EYFSVZ.js} +1 -1
  167. package/static/{chunk-GCUWGVYT.js → chunk-U34OZUZ7.js} +1 -1
  168. package/static/{chunk-NW3CTYUW.js → chunk-Y7EH7G5K.js} +1 -1
  169. package/static/{chunk-EI4PVI2W.js → chunk-ZQQPUYLU.js} +1 -1
  170. package/static/index.html +1 -1
  171. package/static/main-7SQDDVMD.js +9 -0
  172. package/static/chunk-6IRL673W.js +0 -559
  173. package/static/chunk-UQ4TRQCE.js +0 -1
  174. package/static/main-QNBKYA6L.js +0 -9
package/CHANGELOG.md CHANGED
@@ -1,4 +1,12 @@
1
1
 
2
+ ## [1.6.1](https://github.com/Sync-in/server/compare/v1.6.0...v1.6.1) (2025-10-09)
3
+
4
+
5
+ ### Bug Fixes
6
+
7
+ * **backend:auth:** improve AD/LDAP authentication handling and normalization ([db1a9e3](https://github.com/Sync-in/server/commit/db1a9e3d4a02c6be5ef594b4a383e05d0bc50fc4))
8
+ * **frontend:links:** fallback to default MIME URL when origin MIME URL is not found ([5724f3a](https://github.com/Sync-in/server/commit/5724f3a730fc8d8b51268071b0d3370bc62f6901))
9
+
2
10
  ## [1.6.0](https://github.com/Sync-in/server/compare/v1.5.2...v1.6.0) (2025-09-26)
3
11
 
4
12
  🔥🚀 Support for Multi-Factor Authentication (MFA) & App Passwords
package/README.md CHANGED
@@ -102,7 +102,7 @@ Before submitting your pull request, please confirm the following:
102
102
  This project is licensed under the **GNU Affero General Public License (AGPL-3.0-or-later)**.
103
103
  See [LICENSE](LICENSE) for the full text.
104
104
 
105
- Sync-in® is a registered trademark, see our [Trademark Policy](https://sync-in.com/docs/about/trademark).
105
+ Sync-in® is a registered trademark, see our [Trademark Policy](https://sync-in.com/trademark).
106
106
 
107
107
  ---
108
108
 
@@ -120,7 +120,7 @@ auth:
120
120
  # filter, e.g: (acl=admin)
121
121
  filter:
122
122
  attributes:
123
- # login attribute (e.g. `uid` | `sAMAccountName` | `userPrincipalName`)
123
+ # login attribute: `uid` | `sAMAccountName` | `userPrincipalName`
124
124
  # default: `uid`
125
125
  login: uid
126
126
  # email attribute: `mail` or `email`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sync-in/server",
3
- "version": "1.6.0",
3
+ "version": "1.6.1",
4
4
  "description": "The secure, open-source platform for file storage, sharing, collaboration, and sync",
5
5
  "author": {
6
6
  "name": "Johan Legrand",
@@ -73,7 +73,7 @@
73
73
  },
74
74
  "dependencies": {
75
75
  "@fastify/cookie": "11.0.2",
76
- "@fastify/helmet": "13.0.1",
76
+ "@fastify/helmet": "13.0.2",
77
77
  "@fastify/multipart": "9.2.1",
78
78
  "@fastify/send": "4.1.0",
79
79
  "@fastify/static": "8.2.0",
@@ -97,28 +97,28 @@
97
97
  "class-transformer": "0.5.1",
98
98
  "class-validator": "0.14.2",
99
99
  "deepmerge": "4.3.1",
100
- "drizzle-kit": "0.31.4",
101
- "drizzle-orm": "0.44.5",
102
- "fast-xml-parser": "5.2.5",
100
+ "drizzle-kit": "0.31.5",
101
+ "drizzle-orm": "0.44.6",
102
+ "fast-xml-parser": "5.3.0",
103
103
  "fs-extra": "11.3.2",
104
104
  "html-to-text": "9.0.5",
105
105
  "js-yaml": "4.1.0",
106
106
  "ldapts": "8.0.9",
107
107
  "mime-types": "3.0.1",
108
- "mysql2": "3.15.0",
109
- "nestjs-pino": "4.4.0",
110
- "nodemailer": "7.0.6",
108
+ "mysql2": "3.15.2",
109
+ "nestjs-pino": "4.4.1",
110
+ "nodemailer": "7.0.9",
111
111
  "passport-http": "0.3.0",
112
112
  "passport-jwt": "4.0.1",
113
113
  "passport-local": "1.0.0",
114
114
  "passport": "0.7.0",
115
- "pdfjs-dist": "5.4.149",
115
+ "pdfjs-dist": "5.4.296",
116
116
  "pino-pretty": "13.1.1",
117
117
  "qrcode-generator": "2.0.4",
118
118
  "redis": "4.7.1",
119
119
  "sax": "1.4.1",
120
120
  "socket.io": "4.8.1",
121
- "tar": "7.4.3",
121
+ "tar": "7.5.1",
122
122
  "time2fa": "1.4.2",
123
123
  "yauzl": "3.2.0"
124
124
  }
@@ -48,6 +48,7 @@ const _classtransformer = require("class-transformer");
48
48
  const _classvalidator = require("class-validator");
49
49
  const _appconstants = require("../app.constants");
50
50
  const _auth = require("./constants/auth");
51
+ const _authldap = require("./constants/auth-ldap");
51
52
  function _ts_decorate(decorators, target, key, desc) {
52
53
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
53
54
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
@@ -180,19 +181,22 @@ _ts_decorate([
180
181
  ], AuthTokenConfig.prototype, "ws", void 0);
181
182
  let AuthMethodLdapAttributesConfig = class AuthMethodLdapAttributesConfig {
182
183
  constructor(){
183
- this.login = 'uid';
184
- this.email = 'mail';
184
+ this.login = _authldap.LDAP_LOGIN_ATTR.UID;
185
+ this.email = _authldap.LDAP_COMMON_ATTR.MAIL;
185
186
  }
186
187
  };
187
188
  _ts_decorate([
188
189
  (0, _classvalidator.IsOptional)(),
189
190
  (0, _classvalidator.IsString)(),
190
- (0, _classtransformer.Transform)(({ value })=>value || 'uid')
191
+ (0, _classtransformer.Transform)(({ value })=>value || _authldap.LDAP_LOGIN_ATTR.UID),
192
+ (0, _classvalidator.IsEnum)(_authldap.LDAP_LOGIN_ATTR),
193
+ _ts_metadata("design:type", typeof _authldap.LDAP_LOGIN_ATTR === "undefined" ? Object : _authldap.LDAP_LOGIN_ATTR)
191
194
  ], AuthMethodLdapAttributesConfig.prototype, "login", void 0);
192
195
  _ts_decorate([
193
196
  (0, _classvalidator.IsOptional)(),
194
197
  (0, _classvalidator.IsString)(),
195
- (0, _classtransformer.Transform)(({ value })=>value || 'mail')
198
+ (0, _classtransformer.Transform)(({ value })=>value || _authldap.LDAP_COMMON_ATTR.MAIL),
199
+ _ts_metadata("design:type", String)
196
200
  ], AuthMethodLdapAttributesConfig.prototype, "email", void 0);
197
201
  let AuthMethodLdapConfig = class AuthMethodLdapConfig {
198
202
  constructor(){
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../backend/src/authentication/auth.config.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, Transform, Type } from 'class-transformer'\nimport {\n ArrayNotEmpty,\n IsArray,\n IsBoolean,\n IsDefined,\n IsIn,\n IsNotEmpty,\n IsNotEmptyObject,\n IsObject,\n IsOptional,\n IsString,\n ValidateIf,\n ValidateNested\n} from 'class-validator'\nimport { SERVER_NAME } from '../app.constants'\nimport { ACCESS_KEY, CSRF_KEY, REFRESH_KEY, WS_KEY } from './constants/auth'\n\nexport class AuthMfaTotpConfig {\n @IsBoolean()\n enabled = true\n\n @IsString()\n issuer = SERVER_NAME\n}\n\nexport class AuthMfaConfig {\n @IsDefined()\n @IsNotEmptyObject()\n @IsObject()\n @ValidateNested()\n @Type(() => AuthMfaTotpConfig)\n totp: AuthMfaTotpConfig = new AuthMfaTotpConfig()\n}\n\nexport class AuthTokenAccessConfig {\n @Exclude({ toClassOnly: true })\n // force default name\n name = ACCESS_KEY\n\n @IsString()\n @IsNotEmpty()\n secret: string\n\n @IsString()\n @IsNotEmpty()\n expiration = '30m'\n}\n\nexport class AuthTokenRefreshConfig {\n @Exclude({ toClassOnly: true })\n // force default name\n name = REFRESH_KEY\n\n @IsString()\n @IsNotEmpty()\n secret: string\n\n @IsString()\n @IsNotEmpty()\n expiration = '4h'\n}\n\nexport class AuthTokenCsrfConfig extends AuthTokenRefreshConfig {\n @IsString()\n @IsNotEmpty()\n override name: string = CSRF_KEY\n}\n\nexport class AuthTokenWSConfig extends AuthTokenRefreshConfig {\n @IsString()\n @IsNotEmpty()\n override name: string = WS_KEY\n}\n\nexport class AuthTokenConfig {\n @IsDefined()\n @IsNotEmptyObject()\n @IsObject()\n @ValidateNested()\n @Type(() => AuthTokenAccessConfig)\n access: AuthTokenAccessConfig\n\n @IsDefined()\n @IsNotEmptyObject()\n @IsObject()\n @ValidateNested()\n @Type(() => AuthTokenRefreshConfig)\n refresh: AuthTokenRefreshConfig\n\n @IsDefined()\n @IsNotEmptyObject()\n @IsObject()\n @ValidateNested()\n @Type(() => AuthTokenCsrfConfig)\n csrf: AuthTokenCsrfConfig\n\n @IsDefined()\n @IsNotEmptyObject()\n @IsObject()\n @ValidateNested()\n @Type(() => AuthTokenWSConfig)\n ws: AuthTokenWSConfig\n}\n\nexport class AuthMethodLdapAttributesConfig {\n @IsOptional()\n @IsString()\n @Transform(({ value }) => value || 'uid')\n login? = 'uid'\n\n @IsOptional()\n @IsString()\n @Transform(({ value }) => value || 'mail')\n email? = 'mail'\n}\n\nexport class AuthMethodLdapConfig {\n @Transform(({ value }) => (Array.isArray(value) ? value.filter((v: string) => Boolean(v)) : value))\n @ArrayNotEmpty()\n @IsArray()\n @IsString({ each: true })\n servers: string[]\n\n @IsString()\n @IsNotEmpty()\n baseDN: string\n\n @IsOptional()\n @IsString()\n filter?: string\n\n @IsDefined()\n @IsNotEmptyObject()\n @IsObject()\n @ValidateNested()\n @Type(() => AuthMethodLdapAttributesConfig)\n attributes: AuthMethodLdapAttributesConfig = new AuthMethodLdapAttributesConfig()\n}\n\nexport class AuthConfig {\n @IsString()\n @IsIn(['mysql', 'ldap'])\n method: 'mysql' | 'ldap' = 'mysql'\n\n @IsOptional()\n @IsString()\n encryptionKey: string\n\n @IsDefined()\n @IsNotEmptyObject()\n @IsObject()\n @ValidateNested()\n @Type(() => AuthMfaConfig)\n mfa: AuthMfaConfig = new AuthMfaConfig()\n\n @IsString()\n @IsIn(['lax', 'strict'])\n cookieSameSite: 'lax' | 'strict' = 'strict'\n\n @IsDefined()\n @IsNotEmptyObject()\n @IsObject()\n @ValidateNested()\n @Type(() => AuthTokenConfig)\n token: AuthTokenConfig\n\n @ValidateIf((o: AuthConfig) => o.method === 'ldap')\n @IsDefined()\n @IsObject()\n @ValidateNested()\n @Type(() => AuthMethodLdapConfig)\n ldap: AuthMethodLdapConfig\n}\n"],"names":["AuthConfig","AuthMethodLdapAttributesConfig","AuthMethodLdapConfig","AuthMfaConfig","AuthMfaTotpConfig","AuthTokenAccessConfig","AuthTokenConfig","AuthTokenCsrfConfig","AuthTokenRefreshConfig","AuthTokenWSConfig","enabled","issuer","SERVER_NAME","totp","name","ACCESS_KEY","expiration","toClassOnly","REFRESH_KEY","CSRF_KEY","WS_KEY","login","email","value","attributes","Array","isArray","filter","v","Boolean","each","method","mfa","cookieSameSite","o"],"mappings":"AAAA;;;;CAIC;;;;;;;;;;;QA8IYA;eAAAA;;QAnCAC;eAAAA;;QAYAC;eAAAA;;QA3FAC;eAAAA;;QARAC;eAAAA;;QAiBAC;eAAAA;;QAwCAC;eAAAA;;QAZAC;eAAAA;;QAdAC;eAAAA;;QAoBAC;eAAAA;;;kCArE4B;gCAclC;8BACqB;sBAC8B;;;;;;;;;;AAEnD,IAAA,AAAML,oBAAN,MAAMA;;aAEXM,UAAU;aAGVC,SAASC,yBAAW;;AACtB;;;;;;;AAEO,IAAA,AAAMT,gBAAN,MAAMA;;aAMXU,OAA0B,IAAIT;;AAChC;;;;;;oCAFcA;;;AAIP,IAAA,AAAMC,wBAAN,MAAMA;;aAEX,qBAAqB;QACrBS,OAAOC,gBAAU;aAQjBC,aAAa;;AACf;;;QAXaC,aAAa;;;;;;;;;;;;AAanB,IAAA,AAAMT,yBAAN,MAAMA;;aAEX,qBAAqB;QACrBM,OAAOI,iBAAW;aAQlBF,aAAa;;AACf;;;QAXaC,aAAa;;;;;;;;;;;;AAanB,IAAA,AAAMV,sBAAN,MAAMA,4BAA4BC;;QAAlC,qBAGIM,OAAeK,cAAQ;;AAClC;;;;;;AAEO,IAAA,AAAMV,oBAAN,MAAMA,0BAA0BD;;QAAhC,qBAGIM,OAAeM,YAAM;;AAChC;;;;;;AAEO,IAAA,AAAMd,kBAAN,MAAMA;AA4Bb;;;;;;oCAvBcD;;;;;;;;oCAOAG;;;;;;;;oCAOAD;;;;;;;;oCAOAE;;;AAIP,IAAA,AAAMR,iCAAN,MAAMA;;aAIXoB,QAAS;aAKTC,QAAS;;AACX;;;;sCAPc,EAAEC,KAAK,EAAE,GAAKA,SAAS;;;;;sCAKvB,EAAEA,KAAK,EAAE,GAAKA,SAAS;;AAI9B,IAAA,AAAMrB,uBAAN,MAAMA;;aAoBXsB,aAA6C,IAAIvB;;AACnD;;sCApBc,EAAEsB,KAAK,EAAE,GAAME,MAAMC,OAAO,CAACH,SAASA,MAAMI,MAAM,CAAC,CAACC,IAAcC,QAAQD,MAAML;;;;QAGhFO,MAAM;;;;;;;;;;;;;;;;;;;oCAeN7B;;;AAIP,IAAA,AAAMD,aAAN,MAAMA;;aAGX+B,SAA2B;aAW3BC,MAAqB,IAAI7B;aAIzB8B,iBAAmC;;AAerC;;;;QA/BS;QAAS;;;;;;;;;;;;;;oCAWJ9B;;;;;;QAIL;QAAO;;;;;;;;;oCAOFG;;;;qCAGC4B,IAAkBA,EAAEH,MAAM,KAAK;;;;oCAIhC7B"}
1
+ {"version":3,"sources":["../../../backend/src/authentication/auth.config.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, Transform, Type } from 'class-transformer'\nimport {\n ArrayNotEmpty,\n IsArray,\n IsBoolean,\n IsDefined,\n IsEnum,\n IsIn,\n IsNotEmpty,\n IsNotEmptyObject,\n IsObject,\n IsOptional,\n IsString,\n ValidateIf,\n ValidateNested\n} from 'class-validator'\nimport { SERVER_NAME } from '../app.constants'\nimport { ACCESS_KEY, CSRF_KEY, REFRESH_KEY, WS_KEY } from './constants/auth'\nimport { LDAP_COMMON_ATTR, LDAP_LOGIN_ATTR } from './constants/auth-ldap'\n\nexport class AuthMfaTotpConfig {\n @IsBoolean()\n enabled = true\n\n @IsString()\n issuer = SERVER_NAME\n}\n\nexport class AuthMfaConfig {\n @IsDefined()\n @IsNotEmptyObject()\n @IsObject()\n @ValidateNested()\n @Type(() => AuthMfaTotpConfig)\n totp: AuthMfaTotpConfig = new AuthMfaTotpConfig()\n}\n\nexport class AuthTokenAccessConfig {\n @Exclude({ toClassOnly: true })\n // force default name\n name = ACCESS_KEY\n\n @IsString()\n @IsNotEmpty()\n secret: string\n\n @IsString()\n @IsNotEmpty()\n expiration = '30m'\n}\n\nexport class AuthTokenRefreshConfig {\n @Exclude({ toClassOnly: true })\n // force default name\n name = REFRESH_KEY\n\n @IsString()\n @IsNotEmpty()\n secret: string\n\n @IsString()\n @IsNotEmpty()\n expiration = '4h'\n}\n\nexport class AuthTokenCsrfConfig extends AuthTokenRefreshConfig {\n @IsString()\n @IsNotEmpty()\n override name: string = CSRF_KEY\n}\n\nexport class AuthTokenWSConfig extends AuthTokenRefreshConfig {\n @IsString()\n @IsNotEmpty()\n override name: string = WS_KEY\n}\n\nexport class AuthTokenConfig {\n @IsDefined()\n @IsNotEmptyObject()\n @IsObject()\n @ValidateNested()\n @Type(() => AuthTokenAccessConfig)\n access: AuthTokenAccessConfig\n\n @IsDefined()\n @IsNotEmptyObject()\n @IsObject()\n @ValidateNested()\n @Type(() => AuthTokenRefreshConfig)\n refresh: AuthTokenRefreshConfig\n\n @IsDefined()\n @IsNotEmptyObject()\n @IsObject()\n @ValidateNested()\n @Type(() => AuthTokenCsrfConfig)\n csrf: AuthTokenCsrfConfig\n\n @IsDefined()\n @IsNotEmptyObject()\n @IsObject()\n @ValidateNested()\n @Type(() => AuthTokenWSConfig)\n ws: AuthTokenWSConfig\n}\n\nexport class AuthMethodLdapAttributesConfig {\n @IsOptional()\n @IsString()\n @Transform(({ value }) => value || LDAP_LOGIN_ATTR.UID)\n @IsEnum(LDAP_LOGIN_ATTR)\n login: LDAP_LOGIN_ATTR = LDAP_LOGIN_ATTR.UID\n\n @IsOptional()\n @IsString()\n @Transform(({ value }) => value || LDAP_COMMON_ATTR.MAIL)\n email: string = LDAP_COMMON_ATTR.MAIL\n}\n\nexport class AuthMethodLdapConfig {\n @Transform(({ value }) => (Array.isArray(value) ? value.filter((v: string) => Boolean(v)) : value))\n @ArrayNotEmpty()\n @IsArray()\n @IsString({ each: true })\n servers: string[]\n\n @IsString()\n @IsNotEmpty()\n baseDN: string\n\n @IsOptional()\n @IsString()\n filter?: string\n\n @IsDefined()\n @IsNotEmptyObject()\n @IsObject()\n @ValidateNested()\n @Type(() => AuthMethodLdapAttributesConfig)\n attributes: AuthMethodLdapAttributesConfig = new AuthMethodLdapAttributesConfig()\n}\n\nexport class AuthConfig {\n @IsString()\n @IsIn(['mysql', 'ldap'])\n method: 'mysql' | 'ldap' = 'mysql'\n\n @IsOptional()\n @IsString()\n encryptionKey: string\n\n @IsDefined()\n @IsNotEmptyObject()\n @IsObject()\n @ValidateNested()\n @Type(() => AuthMfaConfig)\n mfa: AuthMfaConfig = new AuthMfaConfig()\n\n @IsString()\n @IsIn(['lax', 'strict'])\n cookieSameSite: 'lax' | 'strict' = 'strict'\n\n @IsDefined()\n @IsNotEmptyObject()\n @IsObject()\n @ValidateNested()\n @Type(() => AuthTokenConfig)\n token: AuthTokenConfig\n\n @ValidateIf((o: AuthConfig) => o.method === 'ldap')\n @IsDefined()\n @IsObject()\n @ValidateNested()\n @Type(() => AuthMethodLdapConfig)\n ldap: AuthMethodLdapConfig\n}\n"],"names":["AuthConfig","AuthMethodLdapAttributesConfig","AuthMethodLdapConfig","AuthMfaConfig","AuthMfaTotpConfig","AuthTokenAccessConfig","AuthTokenConfig","AuthTokenCsrfConfig","AuthTokenRefreshConfig","AuthTokenWSConfig","enabled","issuer","SERVER_NAME","totp","name","ACCESS_KEY","expiration","toClassOnly","REFRESH_KEY","CSRF_KEY","WS_KEY","login","LDAP_LOGIN_ATTR","UID","email","LDAP_COMMON_ATTR","MAIL","value","attributes","Array","isArray","filter","v","Boolean","each","method","mfa","cookieSameSite","o"],"mappings":"AAAA;;;;CAIC;;;;;;;;;;;QAiJYA;eAAAA;;QApCAC;eAAAA;;QAaAC;eAAAA;;QA5FAC;eAAAA;;QARAC;eAAAA;;QAiBAC;eAAAA;;QAwCAC;eAAAA;;QAZAC;eAAAA;;QAdAC;eAAAA;;QAoBAC;eAAAA;;;kCAvE4B;gCAelC;8BACqB;sBAC8B;0BACR;;;;;;;;;;AAE3C,IAAA,AAAML,oBAAN,MAAMA;;aAEXM,UAAU;aAGVC,SAASC,yBAAW;;AACtB;;;;;;;AAEO,IAAA,AAAMT,gBAAN,MAAMA;;aAMXU,OAA0B,IAAIT;;AAChC;;;;;;oCAFcA;;;AAIP,IAAA,AAAMC,wBAAN,MAAMA;;aAEX,qBAAqB;QACrBS,OAAOC,gBAAU;aAQjBC,aAAa;;AACf;;;QAXaC,aAAa;;;;;;;;;;;;AAanB,IAAA,AAAMT,yBAAN,MAAMA;;aAEX,qBAAqB;QACrBM,OAAOI,iBAAW;aAQlBF,aAAa;;AACf;;;QAXaC,aAAa;;;;;;;;;;;;AAanB,IAAA,AAAMV,sBAAN,MAAMA,4BAA4BC;;QAAlC,qBAGIM,OAAeK,cAAQ;;AAClC;;;;;;AAEO,IAAA,AAAMV,oBAAN,MAAMA,0BAA0BD;;QAAhC,qBAGIM,OAAeM,YAAM;;AAChC;;;;;;AAEO,IAAA,AAAMd,kBAAN,MAAMA;AA4Bb;;;;;;oCAvBcD;;;;;;;;oCAOAG;;;;;;;;oCAOAD;;;;;;;;oCAOAE;;;AAIP,IAAA,AAAMR,iCAAN,MAAMA;;aAKXoB,QAAyBC,yBAAe,CAACC,GAAG;aAK5CC,QAAgBC,0BAAgB,CAACC,IAAI;;AACvC;;;;sCARc,EAAEC,KAAK,EAAE,GAAKA,SAASL,yBAAe,CAACC,GAAG;;;;;;;sCAM1C,EAAEI,KAAK,EAAE,GAAKA,SAASF,0BAAgB,CAACC,IAAI;;;AAInD,IAAA,AAAMxB,uBAAN,MAAMA;;aAoBX0B,aAA6C,IAAI3B;;AACnD;;sCApBc,EAAE0B,KAAK,EAAE,GAAME,MAAMC,OAAO,CAACH,SAASA,MAAMI,MAAM,CAAC,CAACC,IAAcC,QAAQD,MAAML;;;;QAGhFO,MAAM;;;;;;;;;;;;;;;;;;;oCAeNjC;;;AAIP,IAAA,AAAMD,aAAN,MAAMA;;aAGXmC,SAA2B;aAW3BC,MAAqB,IAAIjC;aAIzBkC,iBAAmC;;AAerC;;;;QA/BS;QAAS;;;;;;;;;;;;;;oCAWJlC;;;;;;QAIL;QAAO;;;;;;;;;oCAOFG;;;;qCAGCgC,IAAkBA,EAAEH,MAAM,KAAK;;;;oCAIhCjC"}
@@ -0,0 +1,44 @@
1
+ /*
2
+ * Copyright (C) 2012-2025 Johan Legrand <johan.legrand@sync-in.com>
3
+ * This file is part of Sync-in | The open source file sync and share solution
4
+ * See the LICENSE file for licensing details
5
+ */ "use strict";
6
+ Object.defineProperty(exports, "__esModule", {
7
+ value: true
8
+ });
9
+ function _export(target, all) {
10
+ for(var name in all)Object.defineProperty(target, name, {
11
+ enumerable: true,
12
+ get: Object.getOwnPropertyDescriptor(all, name).get
13
+ });
14
+ }
15
+ _export(exports, {
16
+ get ALL_LDAP_ATTRIBUTES () {
17
+ return ALL_LDAP_ATTRIBUTES;
18
+ },
19
+ get LDAP_COMMON_ATTR () {
20
+ return LDAP_COMMON_ATTR;
21
+ },
22
+ get LDAP_LOGIN_ATTR () {
23
+ return LDAP_LOGIN_ATTR;
24
+ }
25
+ });
26
+ var LDAP_LOGIN_ATTR = /*#__PURE__*/ function(LDAP_LOGIN_ATTR) {
27
+ LDAP_LOGIN_ATTR["UID"] = "uid";
28
+ LDAP_LOGIN_ATTR["SAM"] = "sAMAccountName";
29
+ LDAP_LOGIN_ATTR["UPN"] = "userPrincipalName";
30
+ return LDAP_LOGIN_ATTR;
31
+ }({});
32
+ const LDAP_COMMON_ATTR = {
33
+ MAIL: 'mail',
34
+ GIVEN_NAME: 'givenName',
35
+ SN: 'sn',
36
+ CN: 'cn',
37
+ DISPLAY_NAME: 'displayName'
38
+ };
39
+ const ALL_LDAP_ATTRIBUTES = [
40
+ ...Object.values(LDAP_LOGIN_ATTR),
41
+ ...Object.values(LDAP_COMMON_ATTR)
42
+ ];
43
+
44
+ //# sourceMappingURL=auth-ldap.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../backend/src/authentication/constants/auth-ldap.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\nexport enum LDAP_LOGIN_ATTR {\n UID = 'uid',\n SAM = 'sAMAccountName',\n UPN = 'userPrincipalName'\n}\n\nexport const LDAP_COMMON_ATTR = {\n MAIL: 'mail',\n GIVEN_NAME: 'givenName',\n SN: 'sn',\n CN: 'cn',\n DISPLAY_NAME: 'displayName'\n} as const\n\nexport const ALL_LDAP_ATTRIBUTES = [...Object.values(LDAP_LOGIN_ATTR), ...Object.values(LDAP_COMMON_ATTR)]\n"],"names":["ALL_LDAP_ATTRIBUTES","LDAP_COMMON_ATTR","LDAP_LOGIN_ATTR","MAIL","GIVEN_NAME","SN","CN","DISPLAY_NAME","Object","values"],"mappings":"AAAA;;;;CAIC;;;;;;;;;;;QAgBYA;eAAAA;;QARAC;eAAAA;;QANDC;eAAAA;;;AAAL,IAAA,AAAKA,yCAAAA;;;;WAAAA;;AAML,MAAMD,mBAAmB;IAC9BE,MAAM;IACNC,YAAY;IACZC,IAAI;IACJC,IAAI;IACJC,cAAc;AAChB;AAEO,MAAMP,sBAAsB;OAAIQ,OAAOC,MAAM,CAACP;OAAqBM,OAAOC,MAAM,CAACR;CAAkB"}
@@ -20,6 +20,7 @@ const _adminusersmanagerservice = require("../../../applications/users/services/
20
20
  const _usersmanagerservice = require("../../../applications/users/services/users-manager.service");
21
21
  const _functions = require("../../../common/functions");
22
22
  const _configenvironment = require("../../../configuration/config.environment");
23
+ const _authldap = require("../../constants/auth-ldap");
23
24
  function _ts_decorate(decorators, target, key, desc) {
24
25
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
25
26
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
@@ -29,31 +30,9 @@ function _ts_decorate(decorators, target, key, desc) {
29
30
  function _ts_metadata(k, v) {
30
31
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
31
32
  }
32
- const LDAP_ATTRIBUTES = {
33
- AD: {
34
- SAM_ACCOUNT: 'sAMAccountName',
35
- USER_NAME: 'userPrincipalName'
36
- },
37
- LDAP: {
38
- UID: 'uid'
39
- },
40
- COMMON: {
41
- MAIL: 'mail',
42
- GIVEN_NAME: 'givenName',
43
- SN: 'sn',
44
- CN: 'cn',
45
- DISPLAY_NAME: 'displayName'
46
- }
47
- };
48
- const ALL_ATTRIBUTES = [
49
- ...Object.values(LDAP_ATTRIBUTES.COMMON),
50
- ...Object.values(LDAP_ATTRIBUTES.LDAP),
51
- ...Object.values(LDAP_ATTRIBUTES.AD)
52
- ];
53
33
  let AuthMethodLdapService = class AuthMethodLdapService {
54
34
  async validateUser(login, password, ip, scope) {
55
- login = this.getUserLogin(login);
56
- let user = await this.usersManager.findUser(login, false);
35
+ let user = await this.usersManager.findUser(this.normalizeLogin(login), false);
57
36
  if (user) {
58
37
  if (user.isGuest) {
59
38
  // allow guests to be authenticated from db and check if the current user is defined as active
@@ -80,9 +59,9 @@ let AuthMethodLdapService = class AuthMethodLdapService {
80
59
  }
81
60
  }
82
61
  return null;
83
- } else if (!entry[_configenvironment.configuration.auth.ldap.attributes.login] || !entry[_configenvironment.configuration.auth.ldap.attributes.email]) {
62
+ } else if (!entry[this.loginAttribute] || !entry[this.emailAttribute]) {
84
63
  this.logger.error(`${this.validateUser.name} - required ldap fields are missing :
85
- [${_configenvironment.configuration.auth.ldap.attributes.login}, ${_configenvironment.configuration.auth.ldap.attributes.email}] =>
64
+ [${this.loginAttribute}, ${this.emailAttribute}] =>
86
65
  (${JSON.stringify(entry)})`);
87
66
  return null;
88
67
  }
@@ -91,30 +70,31 @@ let AuthMethodLdapService = class AuthMethodLdapService {
91
70
  this.usersManager.updateAccesses(user, ip, true).catch((e)=>this.logger.error(`${this.validateUser.name} : ${e}`));
92
71
  return user;
93
72
  }
94
- async checkAuth(uid, password) {
95
- const servers = _configenvironment.configuration.auth.ldap.servers;
96
- const loginAttr = _configenvironment.configuration.auth.ldap.attributes.login;
97
- const baseDN = _configenvironment.configuration.auth.ldap.baseDN;
98
- const bindUserDN = Object.values(LDAP_ATTRIBUTES.AD).indexOf(loginAttr) > -1 ? loginAttr : `${loginAttr}=${uid},${baseDN}`;
73
+ async checkAuth(login, password) {
74
+ const loginAttr = this.loginAttribute;
75
+ const isAD = loginAttr === _authldap.LDAP_LOGIN_ATTR.SAM || loginAttr === _authldap.LDAP_LOGIN_ATTR.UPN;
76
+ // AD: bind directly with the user input (UPN or DOMAIN\user)
77
+ // Generic LDAP: build DN from login attribute + baseDN
78
+ const bindUserDN = isAD ? login : `${loginAttr}=${login},${this.baseDN}`;
99
79
  let client;
100
80
  let error;
101
- for (const s of servers){
81
+ for (const s of this.servers){
102
82
  client = new _ldapts.Client({
103
83
  ...this.clientOptions,
104
84
  url: s
105
85
  });
106
86
  try {
107
87
  await client.bind(bindUserDN, password);
108
- return await this.checkAccess(client, uid);
88
+ return await this.checkAccess(client, login);
109
89
  } catch (e) {
110
90
  if (e.errors?.length) {
111
91
  for (const err of e.errors){
112
- this.logger.warn(`${this.checkAuth.name} - ${uid} : ${err}`);
92
+ this.logger.warn(`${this.checkAuth.name} - ${login} : ${err}`);
113
93
  error = err;
114
94
  }
115
95
  } else {
116
96
  error = e;
117
- this.logger.warn(`${this.checkAuth.name} - ${uid} : ${e}`);
97
+ this.logger.warn(`${this.checkAuth.name} - ${login} : ${e}`);
118
98
  }
119
99
  if (error instanceof _ldapts.InvalidCredentialsError) {
120
100
  return false;
@@ -128,69 +108,77 @@ let AuthMethodLdapService = class AuthMethodLdapService {
128
108
  }
129
109
  return false;
130
110
  }
131
- async checkAccess(client, uid) {
132
- const searchFilter = `(&(${_configenvironment.configuration.auth.ldap.attributes.login}=${uid})${_configenvironment.configuration.auth.ldap.filter || ''})`;
111
+ async checkAccess(client, login) {
112
+ const searchFilter = this.buildUserFilter(login, this.filter);
133
113
  try {
134
- const { searchEntries } = await client.search(_configenvironment.configuration.auth.ldap.baseDN, {
114
+ const { searchEntries } = await client.search(this.baseDN, {
135
115
  scope: 'sub',
136
116
  filter: searchFilter,
137
- attributes: ALL_ATTRIBUTES
117
+ attributes: _authldap.ALL_LDAP_ATTRIBUTES
138
118
  });
139
- for (const entry of searchEntries){
140
- if (entry[_configenvironment.configuration.auth.ldap.attributes.login] === uid) {
141
- return this.convertToLdapUserEntry(entry);
142
- }
119
+ if (searchEntries.length === 0) {
120
+ this.logger.debug(`${this.checkAccess.name} - search filter : ${searchFilter}`);
121
+ this.logger.warn(`${this.checkAccess.name} - no LDAP entry found for : ${login}`);
122
+ return false;
143
123
  }
144
- this.logger.warn(`${this.checkAuth.name} - unable to find user id : ${uid}`);
145
- return false;
124
+ if (searchEntries.length > 1) {
125
+ this.logger.warn(`${this.checkAccess.name} - multiple LDAP entries found for : ${login}, using first one`);
126
+ }
127
+ // Always return the first valid entry
128
+ return this.convertToLdapUserEntry(searchEntries[0]);
146
129
  } catch (e) {
147
- this.logger.error(`${this.checkAccess.name} - ${uid} : ${e}`);
130
+ this.logger.debug(`${this.checkAccess.name} - search filter : ${searchFilter}`);
131
+ this.logger.error(`${this.checkAccess.name} - ${login} : ${e}`);
148
132
  return false;
149
133
  }
150
134
  }
151
135
  async updateOrCreateUser(identity, user) {
152
136
  if (user === null) {
153
- return this.adminUsersManager.createUserOrGuest(identity, _user.USER_ROLE.USER);
154
- } else {
155
- if (identity.login !== user.login) {
156
- this.logger.error(`${this.updateOrCreateUser.name} - user id mismatch : ${identity.login} !== ${user.login}`);
157
- throw new _common.HttpException('Account matching error', _common.HttpStatus.FORBIDDEN);
137
+ const createdUser = await this.adminUsersManager.createUserOrGuest(identity, _user.USER_ROLE.USER);
138
+ const freshUser = await this.usersManager.fromUserId(createdUser.id);
139
+ if (!freshUser) {
140
+ this.logger.error(`${this.updateOrCreateUser.name} - user was not found : ${createdUser.login} (${createdUser.id})`);
141
+ throw new _common.HttpException('User not found', _common.HttpStatus.NOT_FOUND);
158
142
  }
159
- // check if user information has changed
160
- const identityHasChanged = Object.fromEntries((await Promise.all(Object.keys(identity).map(async (key)=>{
161
- if (key === 'password') {
162
- const isSame = await (0, _functions.comparePassword)(identity[key], user.password);
163
- return isSame ? null : [
164
- key,
165
- identity[key]
166
- ];
167
- }
168
- return identity[key] !== user[key] ? [
143
+ return freshUser;
144
+ }
145
+ if (identity.login !== user.login) {
146
+ this.logger.error(`${this.updateOrCreateUser.name} - user login mismatch : ${identity.login} !== ${user.login}`);
147
+ throw new _common.HttpException('Account matching error', _common.HttpStatus.FORBIDDEN);
148
+ }
149
+ // check if user information has changed
150
+ const identityHasChanged = Object.fromEntries((await Promise.all(Object.keys(identity).map(async (key)=>{
151
+ if (key === 'password') {
152
+ const isSame = await (0, _functions.comparePassword)(identity[key], user.password);
153
+ return isSame ? null : [
169
154
  key,
170
155
  identity[key]
171
- ] : null;
172
- }))).filter(Boolean));
173
- if (Object.keys(identityHasChanged).length > 0) {
174
- try {
175
- await this.adminUsersManager.updateUserOrGuest(user.id, identityHasChanged);
176
- if (identityHasChanged?.password) {
177
- delete identityHasChanged.password;
178
- }
179
- Object.assign(user, identityHasChanged);
180
- if ('lastName' in identityHasChanged || 'firstName' in identityHasChanged) {
181
- // force fullName update
182
- user.setFullName(true);
183
- }
184
- } catch (e) {
185
- this.logger.warn(`${this.updateOrCreateUser.name} - unable to update user *${user.login}* : ${e}`);
156
+ ];
157
+ }
158
+ return identity[key] !== user[key] ? [
159
+ key,
160
+ identity[key]
161
+ ] : null;
162
+ }))).filter(Boolean));
163
+ if (Object.keys(identityHasChanged).length > 0) {
164
+ try {
165
+ await this.adminUsersManager.updateUserOrGuest(user.id, identityHasChanged);
166
+ if (identityHasChanged?.password) {
167
+ delete identityHasChanged.password;
186
168
  }
169
+ Object.assign(user, identityHasChanged);
170
+ if ('lastName' in identityHasChanged || 'firstName' in identityHasChanged) {
171
+ // force fullName update in current user model
172
+ user.setFullName(true);
173
+ }
174
+ } catch (e) {
175
+ this.logger.warn(`${this.updateOrCreateUser.name} - unable to update user *${user.login}* : ${e}`);
187
176
  }
188
- await user.makePaths();
189
- return user;
190
177
  }
178
+ return user;
191
179
  }
192
180
  convertToLdapUserEntry(entry) {
193
- for (const attr of ALL_ATTRIBUTES){
181
+ for (const attr of _authldap.ALL_LDAP_ATTRIBUTES){
194
182
  if (Array.isArray(entry[attr])) {
195
183
  entry[attr] = entry[attr].length > 0 ? entry[attr][0] : null;
196
184
  }
@@ -199,8 +187,8 @@ let AuthMethodLdapService = class AuthMethodLdapService {
199
187
  }
200
188
  createIdentity(entry, password) {
201
189
  return {
202
- login: this.getUserLogin(entry[_configenvironment.configuration.auth.ldap.attributes.login]),
203
- email: entry[_configenvironment.configuration.auth.ldap.attributes.email],
190
+ login: this.normalizeLogin(entry[this.loginAttribute]),
191
+ email: entry[this.emailAttribute],
204
192
  password: password,
205
193
  ...this.getFirstNameAndLastName(entry)
206
194
  };
@@ -227,18 +215,42 @@ let AuthMethodLdapService = class AuthMethodLdapService {
227
215
  lastName: ''
228
216
  };
229
217
  }
230
- getUserLogin(login) {
231
- if (_configenvironment.configuration.auth.ldap.attributes.login === LDAP_ATTRIBUTES.AD.USER_NAME) {
232
- return login.split('@')[0];
233
- } else if (_configenvironment.configuration.auth.ldap.attributes.login === LDAP_ATTRIBUTES.AD.SAM_ACCOUNT) {
234
- return login.split('\\')[0];
218
+ normalizeLogin(login, toLowerCase = true) {
219
+ const normalized = (login.includes('\\') ? login.split('\\').slice(-1)[0] : login).trim();
220
+ return toLowerCase ? normalized.toLowerCase() : normalized;
221
+ }
222
+ buildUserFilter(login, extraFilter) {
223
+ // Build a safe LDAP filter to search for a user.
224
+ // Important: - Values passed to EqualityFilter are auto-escaped by ldapts
225
+ // - extraFilter is appended as-is (assumed trusted configuration)
226
+ // Output: (&(|(userPrincipalName=john.doe)(sAMAccountName=john.doe)(uid=john.doe))(*extraFilter*))
227
+ // OR clause: accept UPN, sAMAccountName, or uid
228
+ const normalizedLogin = this.normalizeLogin(login, false);
229
+ const eq = new _ldapts.EqualityFilter({
230
+ attribute: this.loginAttribute,
231
+ value: normalizedLogin
232
+ });
233
+ // Convert to LDAP filter string
234
+ let filterString = new _ldapts.AndFilter({
235
+ filters: [
236
+ eq
237
+ ]
238
+ }).toString();
239
+ // Optionally append an extra filter from config (trusted source)
240
+ if (extraFilter && extraFilter.trim()) {
241
+ filterString = `(&${filterString}${extraFilter})`;
235
242
  }
236
- return login;
243
+ return filterString;
237
244
  }
238
245
  constructor(usersManager, adminUsersManager){
239
246
  this.usersManager = usersManager;
240
247
  this.adminUsersManager = adminUsersManager;
241
248
  this.logger = new _common.Logger(AuthMethodLdapService.name);
249
+ this.loginAttribute = _configenvironment.configuration.auth.ldap.attributes.login;
250
+ this.emailAttribute = _configenvironment.configuration.auth.ldap.attributes.email;
251
+ this.servers = _configenvironment.configuration.auth.ldap.servers;
252
+ this.baseDN = _configenvironment.configuration.auth.ldap.baseDN;
253
+ this.filter = _configenvironment.configuration.auth.ldap.filter;
242
254
  this.clientOptions = {
243
255
  timeout: 6000,
244
256
  connectTimeout: 6000,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../backend/src/authentication/services/auth-methods/auth-method-ldap.service.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 { HttpException, HttpStatus, Injectable, Logger } from '@nestjs/common'\nimport { Client, ClientOptions, Entry, InvalidCredentialsError } from 'ldapts'\nimport { CONNECT_ERROR_CODE } from '../../../app.constants'\nimport { USER_ROLE } from '../../../applications/users/constants/user'\nimport type { CreateUserDto, UpdateUserDto } from '../../../applications/users/dto/create-or-update-user.dto'\nimport { UserModel } from '../../../applications/users/models/user.model'\nimport { AdminUsersManager } from '../../../applications/users/services/admin-users-manager.service'\nimport { UsersManager } from '../../../applications/users/services/users-manager.service'\nimport { comparePassword, splitFullName } from '../../../common/functions'\nimport { configuration } from '../../../configuration/config.environment'\nimport type { AUTH_SCOPE } from '../../constants/scope'\nimport { AuthMethod } from '../../models/auth-method'\n\nconst LDAP_ATTRIBUTES = {\n AD: {\n SAM_ACCOUNT: 'sAMAccountName',\n USER_NAME: 'userPrincipalName'\n },\n LDAP: {\n UID: 'uid'\n },\n COMMON: {\n MAIL: 'mail',\n GIVEN_NAME: 'givenName',\n SN: 'sn',\n CN: 'cn',\n DISPLAY_NAME: 'displayName'\n }\n} as const\n\nconst ALL_ATTRIBUTES = [...Object.values(LDAP_ATTRIBUTES.COMMON), ...Object.values(LDAP_ATTRIBUTES.LDAP), ...Object.values(LDAP_ATTRIBUTES.AD)]\n\ntype KnownAttr =\n | (typeof LDAP_ATTRIBUTES.AD)[keyof typeof LDAP_ATTRIBUTES.AD]\n | (typeof LDAP_ATTRIBUTES.LDAP)[keyof typeof LDAP_ATTRIBUTES.LDAP]\n | (typeof LDAP_ATTRIBUTES.COMMON)[keyof typeof LDAP_ATTRIBUTES.COMMON]\n\ntype LdapUserEntry = Entry & Record<KnownAttr | string, string>\n\n@Injectable()\nexport class AuthMethodLdapService implements AuthMethod {\n private readonly logger = new Logger(AuthMethodLdapService.name)\n private clientOptions: ClientOptions = { timeout: 6000, connectTimeout: 6000, url: '' }\n\n constructor(\n private readonly usersManager: UsersManager,\n private readonly adminUsersManager: AdminUsersManager\n ) {}\n\n async validateUser(login: string, password: string, ip?: string, scope?: AUTH_SCOPE): Promise<UserModel> {\n login = this.getUserLogin(login)\n let user: UserModel = await this.usersManager.findUser(login, false)\n if (user) {\n if (user.isGuest) {\n // allow guests to be authenticated from db and check if the current user is defined as active\n return this.usersManager.logUser(user, password, ip)\n }\n if (!user.isActive) {\n this.logger.error(`${this.validateUser.name} - user *${user.login}* is locked`)\n throw new HttpException('Account locked', HttpStatus.FORBIDDEN)\n }\n }\n const entry: false | LdapUserEntry = await this.checkAuth(login, password)\n if (entry === false) {\n // LDAP auth failed\n if (user) {\n let authSuccess = false\n if (scope) {\n // try user app password\n authSuccess = await this.usersManager.validateAppPassword(user, password, ip, scope)\n }\n this.usersManager.updateAccesses(user, ip, authSuccess).catch((e: Error) => this.logger.error(`${this.validateUser.name} : ${e}`))\n if (authSuccess) {\n // logged with app password\n return user\n }\n }\n return null\n } else if (!entry[configuration.auth.ldap.attributes.login] || !entry[configuration.auth.ldap.attributes.email]) {\n this.logger.error(`${this.validateUser.name} - required ldap fields are missing : \n [${configuration.auth.ldap.attributes.login}, ${configuration.auth.ldap.attributes.email}] => \n (${JSON.stringify(entry)})`)\n return null\n }\n const identity = this.createIdentity(entry, password)\n user = await this.updateOrCreateUser(identity, user)\n this.usersManager.updateAccesses(user, ip, true).catch((e: Error) => this.logger.error(`${this.validateUser.name} : ${e}`))\n return user\n }\n\n private async checkAuth(uid: string, password: string): Promise<LdapUserEntry | false> {\n const servers = configuration.auth.ldap.servers\n const loginAttr = configuration.auth.ldap.attributes.login\n const baseDN = configuration.auth.ldap.baseDN\n const bindUserDN = (Object.values(LDAP_ATTRIBUTES.AD) as string[]).indexOf(loginAttr) > -1 ? loginAttr : `${loginAttr}=${uid},${baseDN}`\n let client: Client\n let error: any\n for (const s of servers) {\n client = new Client({ ...this.clientOptions, url: s })\n try {\n await client.bind(bindUserDN, password)\n return await this.checkAccess(client, uid)\n } catch (e) {\n if (e.errors?.length) {\n for (const err of e.errors) {\n this.logger.warn(`${this.checkAuth.name} - ${uid} : ${err}`)\n error = err\n }\n } else {\n error = e\n this.logger.warn(`${this.checkAuth.name} - ${uid} : ${e}`)\n }\n if (error instanceof InvalidCredentialsError) {\n return false\n }\n } finally {\n await client.unbind()\n }\n }\n if (error && CONNECT_ERROR_CODE.has(error.code)) {\n throw new HttpException('Authentication service error', HttpStatus.INTERNAL_SERVER_ERROR)\n }\n return false\n }\n\n private async checkAccess(client: Client, uid: string): Promise<LdapUserEntry | false> {\n const searchFilter = `(&(${configuration.auth.ldap.attributes.login}=${uid})${configuration.auth.ldap.filter || ''})`\n try {\n const { searchEntries } = await client.search(configuration.auth.ldap.baseDN, {\n scope: 'sub',\n filter: searchFilter,\n attributes: ALL_ATTRIBUTES\n })\n for (const entry of searchEntries) {\n if (entry[configuration.auth.ldap.attributes.login] === uid) {\n return this.convertToLdapUserEntry(entry)\n }\n }\n this.logger.warn(`${this.checkAuth.name} - unable to find user id : ${uid}`)\n return false\n } catch (e) {\n this.logger.error(`${this.checkAccess.name} - ${uid} : ${e}`)\n return false\n }\n }\n\n private async updateOrCreateUser(identity: CreateUserDto, user: UserModel): Promise<UserModel> {\n if (user === null) {\n return this.adminUsersManager.createUserOrGuest(identity, USER_ROLE.USER)\n } else {\n if (identity.login !== user.login) {\n this.logger.error(`${this.updateOrCreateUser.name} - user id mismatch : ${identity.login} !== ${user.login}`)\n throw new HttpException('Account matching error', HttpStatus.FORBIDDEN)\n }\n // check if user information has changed\n const identityHasChanged: UpdateUserDto = Object.fromEntries(\n (\n await Promise.all(\n Object.keys(identity).map(async (key: string) => {\n if (key === 'password') {\n const isSame = await comparePassword(identity[key], user.password)\n return isSame ? null : [key, identity[key]]\n }\n return identity[key] !== user[key] ? [key, identity[key]] : null\n })\n )\n ).filter(Boolean)\n )\n if (Object.keys(identityHasChanged).length > 0) {\n try {\n await this.adminUsersManager.updateUserOrGuest(user.id, identityHasChanged)\n if (identityHasChanged?.password) {\n delete identityHasChanged.password\n }\n Object.assign(user, identityHasChanged)\n if ('lastName' in identityHasChanged || 'firstName' in identityHasChanged) {\n // force fullName update\n user.setFullName(true)\n }\n } catch (e) {\n this.logger.warn(`${this.updateOrCreateUser.name} - unable to update user *${user.login}* : ${e}`)\n }\n }\n await user.makePaths()\n return user\n }\n }\n\n private convertToLdapUserEntry(entry: Entry): LdapUserEntry {\n for (const attr of ALL_ATTRIBUTES) {\n if (Array.isArray(entry[attr])) {\n entry[attr] = entry[attr].length > 0 ? entry[attr][0] : null\n }\n }\n return entry as LdapUserEntry\n }\n\n private createIdentity(entry: LdapUserEntry, password: string): CreateUserDto {\n return {\n login: this.getUserLogin(entry[configuration.auth.ldap.attributes.login]),\n email: entry[configuration.auth.ldap.attributes.email],\n password: password,\n ...this.getFirstNameAndLastName(entry)\n } satisfies CreateUserDto\n }\n\n private getFirstNameAndLastName(entry: LdapUserEntry): { firstName: string; lastName: string } {\n // 1) Prefer structured attributes\n if (entry.sn && entry.givenName) {\n return { firstName: entry.givenName, lastName: entry.sn }\n }\n // 2) Fallback to displayName if available\n if (entry.displayName && entry.displayName.trim()) {\n return splitFullName(entry.displayName)\n }\n // 3) Fallback to cn\n if (entry.cn && entry.cn.trim()) {\n return splitFullName(entry.cn)\n }\n // 4) Nothing usable\n return { firstName: '', lastName: '' }\n }\n\n private getUserLogin(login: string): string {\n if (configuration.auth.ldap.attributes.login === LDAP_ATTRIBUTES.AD.USER_NAME) {\n return login.split('@')[0]\n } else if (configuration.auth.ldap.attributes.login === LDAP_ATTRIBUTES.AD.SAM_ACCOUNT) {\n return login.split('\\\\')[0]\n }\n return login\n }\n}\n"],"names":["AuthMethodLdapService","LDAP_ATTRIBUTES","AD","SAM_ACCOUNT","USER_NAME","LDAP","UID","COMMON","MAIL","GIVEN_NAME","SN","CN","DISPLAY_NAME","ALL_ATTRIBUTES","Object","values","validateUser","login","password","ip","scope","getUserLogin","user","usersManager","findUser","isGuest","logUser","isActive","logger","error","name","HttpException","HttpStatus","FORBIDDEN","entry","checkAuth","authSuccess","validateAppPassword","updateAccesses","catch","e","configuration","auth","ldap","attributes","email","JSON","stringify","identity","createIdentity","updateOrCreateUser","uid","servers","loginAttr","baseDN","bindUserDN","indexOf","client","s","Client","clientOptions","url","bind","checkAccess","errors","length","err","warn","InvalidCredentialsError","unbind","CONNECT_ERROR_CODE","has","code","INTERNAL_SERVER_ERROR","searchFilter","filter","searchEntries","search","convertToLdapUserEntry","adminUsersManager","createUserOrGuest","USER_ROLE","USER","identityHasChanged","fromEntries","Promise","all","keys","map","key","isSame","comparePassword","Boolean","updateUserOrGuest","id","assign","setFullName","makePaths","attr","Array","isArray","getFirstNameAndLastName","sn","givenName","firstName","lastName","displayName","trim","splitFullName","cn","split","Logger","timeout","connectTimeout"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../backend/src/authentication/services/auth-methods/auth-method-ldap.service.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 { HttpException, HttpStatus, Injectable, Logger } from '@nestjs/common'\nimport { AndFilter, Client, ClientOptions, Entry, EqualityFilter, InvalidCredentialsError } from 'ldapts'\nimport { CONNECT_ERROR_CODE } from '../../../app.constants'\nimport { USER_ROLE } from '../../../applications/users/constants/user'\nimport type { CreateUserDto, UpdateUserDto } from '../../../applications/users/dto/create-or-update-user.dto'\nimport { UserModel } from '../../../applications/users/models/user.model'\nimport { AdminUsersManager } from '../../../applications/users/services/admin-users-manager.service'\nimport { UsersManager } from '../../../applications/users/services/users-manager.service'\nimport { comparePassword, splitFullName } from '../../../common/functions'\nimport { configuration } from '../../../configuration/config.environment'\nimport { ALL_LDAP_ATTRIBUTES, LDAP_COMMON_ATTR, LDAP_LOGIN_ATTR } from '../../constants/auth-ldap'\nimport type { AUTH_SCOPE } from '../../constants/scope'\nimport { AuthMethod } from '../../models/auth-method'\n\ntype LdapUserEntry = Entry & Record<LDAP_LOGIN_ATTR | (typeof LDAP_COMMON_ATTR)[keyof typeof LDAP_COMMON_ATTR], string>\n\n@Injectable()\nexport class AuthMethodLdapService implements AuthMethod {\n private readonly logger = new Logger(AuthMethodLdapService.name)\n private readonly loginAttribute: LDAP_LOGIN_ATTR = configuration.auth.ldap.attributes.login\n private readonly emailAttribute: string = configuration.auth.ldap.attributes.email\n private readonly servers: string[] = configuration.auth.ldap.servers\n private readonly baseDN: string = configuration.auth.ldap.baseDN\n private readonly filter: string = configuration.auth.ldap.filter\n private clientOptions: ClientOptions = { timeout: 6000, connectTimeout: 6000, url: '' }\n\n constructor(\n private readonly usersManager: UsersManager,\n private readonly adminUsersManager: AdminUsersManager\n ) {}\n\n async validateUser(login: string, password: string, ip?: string, scope?: AUTH_SCOPE): Promise<UserModel> {\n let user: UserModel = await this.usersManager.findUser(this.normalizeLogin(login), false)\n if (user) {\n if (user.isGuest) {\n // allow guests to be authenticated from db and check if the current user is defined as active\n return this.usersManager.logUser(user, password, ip)\n }\n if (!user.isActive) {\n this.logger.error(`${this.validateUser.name} - user *${user.login}* is locked`)\n throw new HttpException('Account locked', HttpStatus.FORBIDDEN)\n }\n }\n const entry: false | LdapUserEntry = await this.checkAuth(login, password)\n if (entry === false) {\n // LDAP auth failed\n if (user) {\n let authSuccess = false\n if (scope) {\n // try user app password\n authSuccess = await this.usersManager.validateAppPassword(user, password, ip, scope)\n }\n this.usersManager.updateAccesses(user, ip, authSuccess).catch((e: Error) => this.logger.error(`${this.validateUser.name} : ${e}`))\n if (authSuccess) {\n // logged with app password\n return user\n }\n }\n return null\n } else if (!entry[this.loginAttribute] || !entry[this.emailAttribute]) {\n this.logger.error(`${this.validateUser.name} - required ldap fields are missing : \n [${this.loginAttribute}, ${this.emailAttribute}] => \n (${JSON.stringify(entry)})`)\n return null\n }\n const identity = this.createIdentity(entry, password)\n user = await this.updateOrCreateUser(identity, user)\n this.usersManager.updateAccesses(user, ip, true).catch((e: Error) => this.logger.error(`${this.validateUser.name} : ${e}`))\n return user\n }\n\n private async checkAuth(login: string, password: string): Promise<LdapUserEntry | false> {\n const loginAttr = this.loginAttribute\n const isAD = loginAttr === LDAP_LOGIN_ATTR.SAM || loginAttr === LDAP_LOGIN_ATTR.UPN\n // AD: bind directly with the user input (UPN or DOMAIN\\user)\n // Generic LDAP: build DN from login attribute + baseDN\n const bindUserDN = isAD ? login : `${loginAttr}=${login},${this.baseDN}`\n let client: Client\n let error: any\n for (const s of this.servers) {\n client = new Client({ ...this.clientOptions, url: s })\n try {\n await client.bind(bindUserDN, password)\n return await this.checkAccess(client, login)\n } catch (e) {\n if (e.errors?.length) {\n for (const err of e.errors) {\n this.logger.warn(`${this.checkAuth.name} - ${login} : ${err}`)\n error = err\n }\n } else {\n error = e\n this.logger.warn(`${this.checkAuth.name} - ${login} : ${e}`)\n }\n if (error instanceof InvalidCredentialsError) {\n return false\n }\n } finally {\n await client.unbind()\n }\n }\n if (error && CONNECT_ERROR_CODE.has(error.code)) {\n throw new HttpException('Authentication service error', HttpStatus.INTERNAL_SERVER_ERROR)\n }\n return false\n }\n\n private async checkAccess(client: Client, login: string): Promise<LdapUserEntry | false> {\n const searchFilter = this.buildUserFilter(login, this.filter)\n try {\n const { searchEntries } = await client.search(this.baseDN, {\n scope: 'sub',\n filter: searchFilter,\n attributes: ALL_LDAP_ATTRIBUTES\n })\n\n if (searchEntries.length === 0) {\n this.logger.debug(`${this.checkAccess.name} - search filter : ${searchFilter}`)\n this.logger.warn(`${this.checkAccess.name} - no LDAP entry found for : ${login}`)\n return false\n }\n\n if (searchEntries.length > 1) {\n this.logger.warn(`${this.checkAccess.name} - multiple LDAP entries found for : ${login}, using first one`)\n }\n\n // Always return the first valid entry\n return this.convertToLdapUserEntry(searchEntries[0])\n } catch (e) {\n this.logger.debug(`${this.checkAccess.name} - search filter : ${searchFilter}`)\n this.logger.error(`${this.checkAccess.name} - ${login} : ${e}`)\n return false\n }\n }\n\n private async updateOrCreateUser(identity: CreateUserDto, user: UserModel): Promise<UserModel> {\n if (user === null) {\n const createdUser = await this.adminUsersManager.createUserOrGuest(identity, USER_ROLE.USER)\n const freshUser = await this.usersManager.fromUserId(createdUser.id)\n if (!freshUser) {\n this.logger.error(`${this.updateOrCreateUser.name} - user was not found : ${createdUser.login} (${createdUser.id})`)\n throw new HttpException('User not found', HttpStatus.NOT_FOUND)\n }\n return freshUser\n }\n if (identity.login !== user.login) {\n this.logger.error(`${this.updateOrCreateUser.name} - user login mismatch : ${identity.login} !== ${user.login}`)\n throw new HttpException('Account matching error', HttpStatus.FORBIDDEN)\n }\n // check if user information has changed\n const identityHasChanged: UpdateUserDto = Object.fromEntries(\n (\n await Promise.all(\n Object.keys(identity).map(async (key: string) => {\n if (key === 'password') {\n const isSame = await comparePassword(identity[key], user.password)\n return isSame ? null : [key, identity[key]]\n }\n return identity[key] !== user[key] ? [key, identity[key]] : null\n })\n )\n ).filter(Boolean)\n )\n if (Object.keys(identityHasChanged).length > 0) {\n try {\n await this.adminUsersManager.updateUserOrGuest(user.id, identityHasChanged)\n if (identityHasChanged?.password) {\n delete identityHasChanged.password\n }\n Object.assign(user, identityHasChanged)\n if ('lastName' in identityHasChanged || 'firstName' in identityHasChanged) {\n // force fullName update in current user model\n user.setFullName(true)\n }\n } catch (e) {\n this.logger.warn(`${this.updateOrCreateUser.name} - unable to update user *${user.login}* : ${e}`)\n }\n }\n return user\n }\n\n private convertToLdapUserEntry(entry: Entry): LdapUserEntry {\n for (const attr of ALL_LDAP_ATTRIBUTES) {\n if (Array.isArray(entry[attr])) {\n entry[attr] = entry[attr].length > 0 ? entry[attr][0] : null\n }\n }\n return entry as LdapUserEntry\n }\n\n private createIdentity(entry: LdapUserEntry, password: string): CreateUserDto {\n return {\n login: this.normalizeLogin(entry[this.loginAttribute]),\n email: entry[this.emailAttribute] as string,\n password: password,\n ...this.getFirstNameAndLastName(entry)\n } satisfies CreateUserDto\n }\n\n private getFirstNameAndLastName(entry: LdapUserEntry): { firstName: string; lastName: string } {\n // 1) Prefer structured attributes\n if (entry.sn && entry.givenName) {\n return { firstName: entry.givenName, lastName: entry.sn }\n }\n // 2) Fallback to displayName if available\n if (entry.displayName && entry.displayName.trim()) {\n return splitFullName(entry.displayName)\n }\n // 3) Fallback to cn\n if (entry.cn && entry.cn.trim()) {\n return splitFullName(entry.cn)\n }\n // 4) Nothing usable\n return { firstName: '', lastName: '' }\n }\n\n private normalizeLogin(login: string, toLowerCase = true): string {\n const normalized = (login.includes('\\\\') ? login.split('\\\\').slice(-1)[0] : login).trim()\n return toLowerCase ? normalized.toLowerCase() : normalized\n }\n\n private buildUserFilter(login: string, extraFilter?: string): string {\n // Build a safe LDAP filter to search for a user.\n // Important: - Values passed to EqualityFilter are auto-escaped by ldapts\n // - extraFilter is appended as-is (assumed trusted configuration)\n // Output: (&(|(userPrincipalName=john.doe)(sAMAccountName=john.doe)(uid=john.doe))(*extraFilter*))\n\n // OR clause: accept UPN, sAMAccountName, or uid\n const normalizedLogin = this.normalizeLogin(login, false)\n\n const eq = new EqualityFilter({ attribute: this.loginAttribute, value: normalizedLogin })\n // Convert to LDAP filter string\n let filterString = new AndFilter({ filters: [eq] }).toString()\n\n // Optionally append an extra filter from config (trusted source)\n if (extraFilter && extraFilter.trim()) {\n filterString = `(&${filterString}${extraFilter})`\n }\n return filterString\n }\n}\n"],"names":["AuthMethodLdapService","validateUser","login","password","ip","scope","user","usersManager","findUser","normalizeLogin","isGuest","logUser","isActive","logger","error","name","HttpException","HttpStatus","FORBIDDEN","entry","checkAuth","authSuccess","validateAppPassword","updateAccesses","catch","e","loginAttribute","emailAttribute","JSON","stringify","identity","createIdentity","updateOrCreateUser","loginAttr","isAD","LDAP_LOGIN_ATTR","SAM","UPN","bindUserDN","baseDN","client","s","servers","Client","clientOptions","url","bind","checkAccess","errors","length","err","warn","InvalidCredentialsError","unbind","CONNECT_ERROR_CODE","has","code","INTERNAL_SERVER_ERROR","searchFilter","buildUserFilter","filter","searchEntries","search","attributes","ALL_LDAP_ATTRIBUTES","debug","convertToLdapUserEntry","createdUser","adminUsersManager","createUserOrGuest","USER_ROLE","USER","freshUser","fromUserId","id","NOT_FOUND","identityHasChanged","Object","fromEntries","Promise","all","keys","map","key","isSame","comparePassword","Boolean","updateUserOrGuest","assign","setFullName","attr","Array","isArray","email","getFirstNameAndLastName","sn","givenName","firstName","lastName","displayName","trim","splitFullName","cn","toLowerCase","normalized","includes","split","slice","extraFilter","normalizedLogin","eq","EqualityFilter","attribute","value","filterString","AndFilter","filters","toString","Logger","configuration","auth","ldap","timeout","connectTimeout"],"mappings":"AAAA;;;;CAIC;;;;+BAmBYA;;;eAAAA;;;wBAjBiD;wBACmC;8BAC9D;sBACT;0CAGQ;qCACL;2BACkB;mCACjB;0BACyC;;;;;;;;;;AAOhE,IAAA,AAAMA,wBAAN,MAAMA;IAcX,MAAMC,aAAaC,KAAa,EAAEC,QAAgB,EAAEC,EAAW,EAAEC,KAAkB,EAAsB;QACvG,IAAIC,OAAkB,MAAM,IAAI,CAACC,YAAY,CAACC,QAAQ,CAAC,IAAI,CAACC,cAAc,CAACP,QAAQ;QACnF,IAAII,MAAM;YACR,IAAIA,KAAKI,OAAO,EAAE;gBAChB,8FAA8F;gBAC9F,OAAO,IAAI,CAACH,YAAY,CAACI,OAAO,CAACL,MAAMH,UAAUC;YACnD;YACA,IAAI,CAACE,KAAKM,QAAQ,EAAE;gBAClB,IAAI,CAACC,MAAM,CAACC,KAAK,CAAC,GAAG,IAAI,CAACb,YAAY,CAACc,IAAI,CAAC,SAAS,EAAET,KAAKJ,KAAK,CAAC,WAAW,CAAC;gBAC9E,MAAM,IAAIc,qBAAa,CAAC,kBAAkBC,kBAAU,CAACC,SAAS;YAChE;QACF;QACA,MAAMC,QAA+B,MAAM,IAAI,CAACC,SAAS,CAAClB,OAAOC;QACjE,IAAIgB,UAAU,OAAO;YACnB,mBAAmB;YACnB,IAAIb,MAAM;gBACR,IAAIe,cAAc;gBAClB,IAAIhB,OAAO;oBACT,wBAAwB;oBACxBgB,cAAc,MAAM,IAAI,CAACd,YAAY,CAACe,mBAAmB,CAAChB,MAAMH,UAAUC,IAAIC;gBAChF;gBACA,IAAI,CAACE,YAAY,CAACgB,cAAc,CAACjB,MAAMF,IAAIiB,aAAaG,KAAK,CAAC,CAACC,IAAa,IAAI,CAACZ,MAAM,CAACC,KAAK,CAAC,GAAG,IAAI,CAACb,YAAY,CAACc,IAAI,CAAC,GAAG,EAAEU,GAAG;gBAChI,IAAIJ,aAAa;oBACf,2BAA2B;oBAC3B,OAAOf;gBACT;YACF;YACA,OAAO;QACT,OAAO,IAAI,CAACa,KAAK,CAAC,IAAI,CAACO,cAAc,CAAC,IAAI,CAACP,KAAK,CAAC,IAAI,CAACQ,cAAc,CAAC,EAAE;YACrE,IAAI,CAACd,MAAM,CAACC,KAAK,CAAC,GAAG,IAAI,CAACb,YAAY,CAACc,IAAI,CAAC;OAC3C,EAAE,IAAI,CAACW,cAAc,CAAC,EAAE,EAAE,IAAI,CAACC,cAAc,CAAC;OAC9C,EAAEC,KAAKC,SAAS,CAACV,OAAO,CAAC,CAAC;YAC3B,OAAO;QACT;QACA,MAAMW,WAAW,IAAI,CAACC,cAAc,CAACZ,OAAOhB;QAC5CG,OAAO,MAAM,IAAI,CAAC0B,kBAAkB,CAACF,UAAUxB;QAC/C,IAAI,CAACC,YAAY,CAACgB,cAAc,CAACjB,MAAMF,IAAI,MAAMoB,KAAK,CAAC,CAACC,IAAa,IAAI,CAACZ,MAAM,CAACC,KAAK,CAAC,GAAG,IAAI,CAACb,YAAY,CAACc,IAAI,CAAC,GAAG,EAAEU,GAAG;QACzH,OAAOnB;IACT;IAEA,MAAcc,UAAUlB,KAAa,EAAEC,QAAgB,EAAkC;QACvF,MAAM8B,YAAY,IAAI,CAACP,cAAc;QACrC,MAAMQ,OAAOD,cAAcE,yBAAe,CAACC,GAAG,IAAIH,cAAcE,yBAAe,CAACE,GAAG;QACnF,6DAA6D;QAC7D,uDAAuD;QACvD,MAAMC,aAAaJ,OAAOhC,QAAQ,GAAG+B,UAAU,CAAC,EAAE/B,MAAM,CAAC,EAAE,IAAI,CAACqC,MAAM,EAAE;QACxE,IAAIC;QACJ,IAAI1B;QACJ,KAAK,MAAM2B,KAAK,IAAI,CAACC,OAAO,CAAE;YAC5BF,SAAS,IAAIG,cAAM,CAAC;gBAAE,GAAG,IAAI,CAACC,aAAa;gBAAEC,KAAKJ;YAAE;YACpD,IAAI;gBACF,MAAMD,OAAOM,IAAI,CAACR,YAAYnC;gBAC9B,OAAO,MAAM,IAAI,CAAC4C,WAAW,CAACP,QAAQtC;YACxC,EAAE,OAAOuB,GAAG;gBACV,IAAIA,EAAEuB,MAAM,EAAEC,QAAQ;oBACpB,KAAK,MAAMC,OAAOzB,EAAEuB,MAAM,CAAE;wBAC1B,IAAI,CAACnC,MAAM,CAACsC,IAAI,CAAC,GAAG,IAAI,CAAC/B,SAAS,CAACL,IAAI,CAAC,GAAG,EAAEb,MAAM,GAAG,EAAEgD,KAAK;wBAC7DpC,QAAQoC;oBACV;gBACF,OAAO;oBACLpC,QAAQW;oBACR,IAAI,CAACZ,MAAM,CAACsC,IAAI,CAAC,GAAG,IAAI,CAAC/B,SAAS,CAACL,IAAI,CAAC,GAAG,EAAEb,MAAM,GAAG,EAAEuB,GAAG;gBAC7D;gBACA,IAAIX,iBAAiBsC,+BAAuB,EAAE;oBAC5C,OAAO;gBACT;YACF,SAAU;gBACR,MAAMZ,OAAOa,MAAM;YACrB;QACF;QACA,IAAIvC,SAASwC,gCAAkB,CAACC,GAAG,CAACzC,MAAM0C,IAAI,GAAG;YAC/C,MAAM,IAAIxC,qBAAa,CAAC,gCAAgCC,kBAAU,CAACwC,qBAAqB;QAC1F;QACA,OAAO;IACT;IAEA,MAAcV,YAAYP,MAAc,EAAEtC,KAAa,EAAkC;QACvF,MAAMwD,eAAe,IAAI,CAACC,eAAe,CAACzD,OAAO,IAAI,CAAC0D,MAAM;QAC5D,IAAI;YACF,MAAM,EAAEC,aAAa,EAAE,GAAG,MAAMrB,OAAOsB,MAAM,CAAC,IAAI,CAACvB,MAAM,EAAE;gBACzDlC,OAAO;gBACPuD,QAAQF;gBACRK,YAAYC,6BAAmB;YACjC;YAEA,IAAIH,cAAcZ,MAAM,KAAK,GAAG;gBAC9B,IAAI,CAACpC,MAAM,CAACoD,KAAK,CAAC,GAAG,IAAI,CAAClB,WAAW,CAAChC,IAAI,CAAC,mBAAmB,EAAE2C,cAAc;gBAC9E,IAAI,CAAC7C,MAAM,CAACsC,IAAI,CAAC,GAAG,IAAI,CAACJ,WAAW,CAAChC,IAAI,CAAC,6BAA6B,EAAEb,OAAO;gBAChF,OAAO;YACT;YAEA,IAAI2D,cAAcZ,MAAM,GAAG,GAAG;gBAC5B,IAAI,CAACpC,MAAM,CAACsC,IAAI,CAAC,GAAG,IAAI,CAACJ,WAAW,CAAChC,IAAI,CAAC,qCAAqC,EAAEb,MAAM,iBAAiB,CAAC;YAC3G;YAEA,sCAAsC;YACtC,OAAO,IAAI,CAACgE,sBAAsB,CAACL,aAAa,CAAC,EAAE;QACrD,EAAE,OAAOpC,GAAG;YACV,IAAI,CAACZ,MAAM,CAACoD,KAAK,CAAC,GAAG,IAAI,CAAClB,WAAW,CAAChC,IAAI,CAAC,mBAAmB,EAAE2C,cAAc;YAC9E,IAAI,CAAC7C,MAAM,CAACC,KAAK,CAAC,GAAG,IAAI,CAACiC,WAAW,CAAChC,IAAI,CAAC,GAAG,EAAEb,MAAM,GAAG,EAAEuB,GAAG;YAC9D,OAAO;QACT;IACF;IAEA,MAAcO,mBAAmBF,QAAuB,EAAExB,IAAe,EAAsB;QAC7F,IAAIA,SAAS,MAAM;YACjB,MAAM6D,cAAc,MAAM,IAAI,CAACC,iBAAiB,CAACC,iBAAiB,CAACvC,UAAUwC,eAAS,CAACC,IAAI;YAC3F,MAAMC,YAAY,MAAM,IAAI,CAACjE,YAAY,CAACkE,UAAU,CAACN,YAAYO,EAAE;YACnE,IAAI,CAACF,WAAW;gBACd,IAAI,CAAC3D,MAAM,CAACC,KAAK,CAAC,GAAG,IAAI,CAACkB,kBAAkB,CAACjB,IAAI,CAAC,wBAAwB,EAAEoD,YAAYjE,KAAK,CAAC,EAAE,EAAEiE,YAAYO,EAAE,CAAC,CAAC,CAAC;gBACnH,MAAM,IAAI1D,qBAAa,CAAC,kBAAkBC,kBAAU,CAAC0D,SAAS;YAChE;YACA,OAAOH;QACT;QACA,IAAI1C,SAAS5B,KAAK,KAAKI,KAAKJ,KAAK,EAAE;YACjC,IAAI,CAACW,MAAM,CAACC,KAAK,CAAC,GAAG,IAAI,CAACkB,kBAAkB,CAACjB,IAAI,CAAC,yBAAyB,EAAEe,SAAS5B,KAAK,CAAC,KAAK,EAAEI,KAAKJ,KAAK,EAAE;YAC/G,MAAM,IAAIc,qBAAa,CAAC,0BAA0BC,kBAAU,CAACC,SAAS;QACxE;QACA,wCAAwC;QACxC,MAAM0D,qBAAoCC,OAAOC,WAAW,CAC1D,AACE,CAAA,MAAMC,QAAQC,GAAG,CACfH,OAAOI,IAAI,CAACnD,UAAUoD,GAAG,CAAC,OAAOC;YAC/B,IAAIA,QAAQ,YAAY;gBACtB,MAAMC,SAAS,MAAMC,IAAAA,0BAAe,EAACvD,QAAQ,CAACqD,IAAI,EAAE7E,KAAKH,QAAQ;gBACjE,OAAOiF,SAAS,OAAO;oBAACD;oBAAKrD,QAAQ,CAACqD,IAAI;iBAAC;YAC7C;YACA,OAAOrD,QAAQ,CAACqD,IAAI,KAAK7E,IAAI,CAAC6E,IAAI,GAAG;gBAACA;gBAAKrD,QAAQ,CAACqD,IAAI;aAAC,GAAG;QAC9D,GACF,EACAvB,MAAM,CAAC0B;QAEX,IAAIT,OAAOI,IAAI,CAACL,oBAAoB3B,MAAM,GAAG,GAAG;YAC9C,IAAI;gBACF,MAAM,IAAI,CAACmB,iBAAiB,CAACmB,iBAAiB,CAACjF,KAAKoE,EAAE,EAAEE;gBACxD,IAAIA,oBAAoBzE,UAAU;oBAChC,OAAOyE,mBAAmBzE,QAAQ;gBACpC;gBACA0E,OAAOW,MAAM,CAAClF,MAAMsE;gBACpB,IAAI,cAAcA,sBAAsB,eAAeA,oBAAoB;oBACzE,8CAA8C;oBAC9CtE,KAAKmF,WAAW,CAAC;gBACnB;YACF,EAAE,OAAOhE,GAAG;gBACV,IAAI,CAACZ,MAAM,CAACsC,IAAI,CAAC,GAAG,IAAI,CAACnB,kBAAkB,CAACjB,IAAI,CAAC,0BAA0B,EAAET,KAAKJ,KAAK,CAAC,IAAI,EAAEuB,GAAG;YACnG;QACF;QACA,OAAOnB;IACT;IAEQ4D,uBAAuB/C,KAAY,EAAiB;QAC1D,KAAK,MAAMuE,QAAQ1B,6BAAmB,CAAE;YACtC,IAAI2B,MAAMC,OAAO,CAACzE,KAAK,CAACuE,KAAK,GAAG;gBAC9BvE,KAAK,CAACuE,KAAK,GAAGvE,KAAK,CAACuE,KAAK,CAACzC,MAAM,GAAG,IAAI9B,KAAK,CAACuE,KAAK,CAAC,EAAE,GAAG;YAC1D;QACF;QACA,OAAOvE;IACT;IAEQY,eAAeZ,KAAoB,EAAEhB,QAAgB,EAAiB;QAC5E,OAAO;YACLD,OAAO,IAAI,CAACO,cAAc,CAACU,KAAK,CAAC,IAAI,CAACO,cAAc,CAAC;YACrDmE,OAAO1E,KAAK,CAAC,IAAI,CAACQ,cAAc,CAAC;YACjCxB,UAAUA;YACV,GAAG,IAAI,CAAC2F,uBAAuB,CAAC3E,MAAM;QACxC;IACF;IAEQ2E,wBAAwB3E,KAAoB,EAA2C;QAC7F,kCAAkC;QAClC,IAAIA,MAAM4E,EAAE,IAAI5E,MAAM6E,SAAS,EAAE;YAC/B,OAAO;gBAAEC,WAAW9E,MAAM6E,SAAS;gBAAEE,UAAU/E,MAAM4E,EAAE;YAAC;QAC1D;QACA,0CAA0C;QAC1C,IAAI5E,MAAMgF,WAAW,IAAIhF,MAAMgF,WAAW,CAACC,IAAI,IAAI;YACjD,OAAOC,IAAAA,wBAAa,EAAClF,MAAMgF,WAAW;QACxC;QACA,oBAAoB;QACpB,IAAIhF,MAAMmF,EAAE,IAAInF,MAAMmF,EAAE,CAACF,IAAI,IAAI;YAC/B,OAAOC,IAAAA,wBAAa,EAAClF,MAAMmF,EAAE;QAC/B;QACA,oBAAoB;QACpB,OAAO;YAAEL,WAAW;YAAIC,UAAU;QAAG;IACvC;IAEQzF,eAAeP,KAAa,EAAEqG,cAAc,IAAI,EAAU;QAChE,MAAMC,aAAa,AAACtG,CAAAA,MAAMuG,QAAQ,CAAC,QAAQvG,MAAMwG,KAAK,CAAC,MAAMC,KAAK,CAAC,CAAC,EAAE,CAAC,EAAE,GAAGzG,KAAI,EAAGkG,IAAI;QACvF,OAAOG,cAAcC,WAAWD,WAAW,KAAKC;IAClD;IAEQ7C,gBAAgBzD,KAAa,EAAE0G,WAAoB,EAAU;QACnE,iDAAiD;QACjD,0EAA0E;QAC1E,6EAA6E;QAC7E,mGAAmG;QAEnG,gDAAgD;QAChD,MAAMC,kBAAkB,IAAI,CAACpG,cAAc,CAACP,OAAO;QAEnD,MAAM4G,KAAK,IAAIC,sBAAc,CAAC;YAAEC,WAAW,IAAI,CAACtF,cAAc;YAAEuF,OAAOJ;QAAgB;QACvF,gCAAgC;QAChC,IAAIK,eAAe,IAAIC,iBAAS,CAAC;YAAEC,SAAS;gBAACN;aAAG;QAAC,GAAGO,QAAQ;QAE5D,iEAAiE;QACjE,IAAIT,eAAeA,YAAYR,IAAI,IAAI;YACrCc,eAAe,CAAC,EAAE,EAAEA,eAAeN,YAAY,CAAC,CAAC;QACnD;QACA,OAAOM;IACT;IArNA,YACE,AAAiB3G,YAA0B,EAC3C,AAAiB6D,iBAAoC,CACrD;aAFiB7D,eAAAA;aACA6D,oBAAAA;aAVFvD,SAAS,IAAIyG,cAAM,CAACtH,sBAAsBe,IAAI;aAC9CW,iBAAkC6F,gCAAa,CAACC,IAAI,CAACC,IAAI,CAAC1D,UAAU,CAAC7D,KAAK;aAC1EyB,iBAAyB4F,gCAAa,CAACC,IAAI,CAACC,IAAI,CAAC1D,UAAU,CAAC8B,KAAK;aACjEnD,UAAoB6E,gCAAa,CAACC,IAAI,CAACC,IAAI,CAAC/E,OAAO;aACnDH,SAAiBgF,gCAAa,CAACC,IAAI,CAACC,IAAI,CAAClF,MAAM;aAC/CqB,SAAiB2D,gCAAa,CAACC,IAAI,CAACC,IAAI,CAAC7D,MAAM;aACxDhB,gBAA+B;YAAE8E,SAAS;YAAMC,gBAAgB;YAAM9E,KAAK;QAAG;IAKnF;AAmNL"}