@sync-in/server 1.6.0 → 1.7.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 (178) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/README.md +1 -1
  3. package/environment/environment.dist.yaml +8 -2
  4. package/package.json +10 -10
  5. package/server/authentication/auth.config.js +23 -4
  6. package/server/authentication/auth.config.js.map +1 -1
  7. package/server/authentication/constants/auth-ldap.js +45 -0
  8. package/server/authentication/constants/auth-ldap.js.map +1 -0
  9. package/server/authentication/guards/auth-basic.strategy.js +2 -0
  10. package/server/authentication/guards/auth-basic.strategy.js.map +1 -1
  11. package/server/authentication/guards/auth-local.strategy.js +2 -0
  12. package/server/authentication/guards/auth-local.strategy.js.map +1 -1
  13. package/server/authentication/services/auth-methods/auth-method-ldap.service.js +138 -83
  14. package/server/authentication/services/auth-methods/auth-method-ldap.service.js.map +1 -1
  15. package/server/authentication/services/auth-methods/auth-method-ldap.service.spec.js +52 -48
  16. package/server/authentication/services/auth-methods/auth-method-ldap.service.spec.js.map +1 -1
  17. package/static/assets/pdfjs/build/pdf.mjs +2522 -914
  18. package/static/assets/pdfjs/build/pdf.mjs.map +1 -1
  19. package/static/assets/pdfjs/build/pdf.sandbox.mjs +2 -2
  20. package/static/assets/pdfjs/build/pdf.worker.mjs +1024 -566
  21. package/static/assets/pdfjs/build/pdf.worker.mjs.map +1 -1
  22. package/static/assets/pdfjs/version +1 -1
  23. package/static/assets/pdfjs/web/debugger.mjs +116 -37
  24. package/static/assets/pdfjs/web/images/comment-popup-editButton.svg +5 -0
  25. package/static/assets/pdfjs/web/locale/ach/viewer.ftl +0 -12
  26. package/static/assets/pdfjs/web/locale/af/viewer.ftl +0 -12
  27. package/static/assets/pdfjs/web/locale/an/viewer.ftl +0 -16
  28. package/static/assets/pdfjs/web/locale/ar/viewer.ftl +0 -32
  29. package/static/assets/pdfjs/web/locale/ast/viewer.ftl +0 -19
  30. package/static/assets/pdfjs/web/locale/az/viewer.ftl +0 -16
  31. package/static/assets/pdfjs/web/locale/be/viewer.ftl +0 -32
  32. package/static/assets/pdfjs/web/locale/bg/viewer.ftl +0 -32
  33. package/static/assets/pdfjs/web/locale/bn/viewer.ftl +0 -16
  34. package/static/assets/pdfjs/web/locale/bo/viewer.ftl +0 -12
  35. package/static/assets/pdfjs/web/locale/br/viewer.ftl +0 -22
  36. package/static/assets/pdfjs/web/locale/brx/viewer.ftl +0 -16
  37. package/static/assets/pdfjs/web/locale/bs/viewer.ftl +0 -32
  38. package/static/assets/pdfjs/web/locale/ca/viewer.ftl +12 -23
  39. package/static/assets/pdfjs/web/locale/cak/viewer.ftl +0 -23
  40. package/static/assets/pdfjs/web/locale/ckb/viewer.ftl +0 -16
  41. package/static/assets/pdfjs/web/locale/cs/viewer.ftl +0 -32
  42. package/static/assets/pdfjs/web/locale/cy/viewer.ftl +0 -32
  43. package/static/assets/pdfjs/web/locale/da/viewer.ftl +3 -35
  44. package/static/assets/pdfjs/web/locale/de/viewer.ftl +0 -32
  45. package/static/assets/pdfjs/web/locale/dsb/viewer.ftl +0 -32
  46. package/static/assets/pdfjs/web/locale/el/viewer.ftl +0 -32
  47. package/static/assets/pdfjs/web/locale/en-CA/viewer.ftl +0 -32
  48. package/static/assets/pdfjs/web/locale/en-GB/viewer.ftl +0 -32
  49. package/static/assets/pdfjs/web/locale/en-US/viewer.ftl +25 -13
  50. package/static/assets/pdfjs/web/locale/eo/viewer.ftl +0 -32
  51. package/static/assets/pdfjs/web/locale/es-AR/viewer.ftl +0 -32
  52. package/static/assets/pdfjs/web/locale/es-CL/viewer.ftl +0 -32
  53. package/static/assets/pdfjs/web/locale/es-ES/viewer.ftl +5 -32
  54. package/static/assets/pdfjs/web/locale/es-MX/viewer.ftl +0 -32
  55. package/static/assets/pdfjs/web/locale/et/viewer.ftl +0 -16
  56. package/static/assets/pdfjs/web/locale/eu/viewer.ftl +38 -32
  57. package/static/assets/pdfjs/web/locale/fa/viewer.ftl +0 -19
  58. package/static/assets/pdfjs/web/locale/ff/viewer.ftl +0 -12
  59. package/static/assets/pdfjs/web/locale/fi/viewer.ftl +0 -32
  60. package/static/assets/pdfjs/web/locale/fr/viewer.ftl +0 -32
  61. package/static/assets/pdfjs/web/locale/fur/viewer.ftl +0 -32
  62. package/static/assets/pdfjs/web/locale/fy-NL/viewer.ftl +0 -32
  63. package/static/assets/pdfjs/web/locale/ga-IE/viewer.ftl +0 -12
  64. package/static/assets/pdfjs/web/locale/gd/viewer.ftl +0 -23
  65. package/static/assets/pdfjs/web/locale/gl/viewer.ftl +0 -32
  66. package/static/assets/pdfjs/web/locale/gn/viewer.ftl +0 -32
  67. package/static/assets/pdfjs/web/locale/gu-IN/viewer.ftl +0 -12
  68. package/static/assets/pdfjs/web/locale/he/viewer.ftl +0 -32
  69. package/static/assets/pdfjs/web/locale/hi-IN/viewer.ftl +0 -16
  70. package/static/assets/pdfjs/web/locale/hr/viewer.ftl +0 -32
  71. package/static/assets/pdfjs/web/locale/hsb/viewer.ftl +0 -32
  72. package/static/assets/pdfjs/web/locale/hu/viewer.ftl +0 -32
  73. package/static/assets/pdfjs/web/locale/hy-AM/viewer.ftl +372 -16
  74. package/static/assets/pdfjs/web/locale/hye/viewer.ftl +0 -16
  75. package/static/assets/pdfjs/web/locale/ia/viewer.ftl +0 -32
  76. package/static/assets/pdfjs/web/locale/id/viewer.ftl +38 -32
  77. package/static/assets/pdfjs/web/locale/is/viewer.ftl +27 -32
  78. package/static/assets/pdfjs/web/locale/it/viewer.ftl +0 -33
  79. package/static/assets/pdfjs/web/locale/ja/viewer.ftl +31 -33
  80. package/static/assets/pdfjs/web/locale/ka/viewer.ftl +0 -32
  81. package/static/assets/pdfjs/web/locale/kab/viewer.ftl +0 -32
  82. package/static/assets/pdfjs/web/locale/kk/viewer.ftl +31 -32
  83. package/static/assets/pdfjs/web/locale/km/viewer.ftl +0 -12
  84. package/static/assets/pdfjs/web/locale/kn/viewer.ftl +0 -12
  85. package/static/assets/pdfjs/web/locale/ko/viewer.ftl +0 -32
  86. package/static/assets/pdfjs/web/locale/lij/viewer.ftl +0 -12
  87. package/static/assets/pdfjs/web/locale/lo/viewer.ftl +0 -23
  88. package/static/assets/pdfjs/web/locale/lt/viewer.ftl +0 -16
  89. package/static/assets/pdfjs/web/locale/ltg/viewer.ftl +0 -12
  90. package/static/assets/pdfjs/web/locale/lv/viewer.ftl +0 -12
  91. package/static/assets/pdfjs/web/locale/meh/viewer.ftl +0 -14
  92. package/static/assets/pdfjs/web/locale/mk/viewer.ftl +0 -19
  93. package/static/assets/pdfjs/web/locale/ml/viewer.ftl +0 -31
  94. package/static/assets/pdfjs/web/locale/mr/viewer.ftl +0 -16
  95. package/static/assets/pdfjs/web/locale/ms/viewer.ftl +0 -12
  96. package/static/assets/pdfjs/web/locale/my/viewer.ftl +0 -12
  97. package/static/assets/pdfjs/web/locale/nb-NO/viewer.ftl +0 -32
  98. package/static/assets/pdfjs/web/locale/ne-NP/viewer.ftl +0 -12
  99. package/static/assets/pdfjs/web/locale/nl/viewer.ftl +0 -32
  100. package/static/assets/pdfjs/web/locale/nn-NO/viewer.ftl +0 -32
  101. package/static/assets/pdfjs/web/locale/oc/viewer.ftl +0 -24
  102. package/static/assets/pdfjs/web/locale/pa-IN/viewer.ftl +0 -32
  103. package/static/assets/pdfjs/web/locale/pl/viewer.ftl +0 -32
  104. package/static/assets/pdfjs/web/locale/pt-BR/viewer.ftl +0 -32
  105. package/static/assets/pdfjs/web/locale/pt-PT/viewer.ftl +0 -32
  106. package/static/assets/pdfjs/web/locale/rm/viewer.ftl +0 -32
  107. package/static/assets/pdfjs/web/locale/ro/viewer.ftl +5 -37
  108. package/static/assets/pdfjs/web/locale/ru/viewer.ftl +0 -32
  109. package/static/assets/pdfjs/web/locale/sat/viewer.ftl +0 -23
  110. package/static/assets/pdfjs/web/locale/sc/viewer.ftl +8 -27
  111. package/static/assets/pdfjs/web/locale/sco/viewer.ftl +0 -16
  112. package/static/assets/pdfjs/web/locale/si/viewer.ftl +0 -22
  113. package/static/assets/pdfjs/web/locale/sk/viewer.ftl +0 -32
  114. package/static/assets/pdfjs/web/locale/skr/viewer.ftl +0 -32
  115. package/static/assets/pdfjs/web/locale/sl/viewer.ftl +30 -32
  116. package/static/assets/pdfjs/web/locale/son/viewer.ftl +0 -12
  117. package/static/assets/pdfjs/web/locale/sq/viewer.ftl +0 -32
  118. package/static/assets/pdfjs/web/locale/sr/viewer.ftl +0 -32
  119. package/static/assets/pdfjs/web/locale/sv-SE/viewer.ftl +0 -32
  120. package/static/assets/pdfjs/web/locale/szl/viewer.ftl +0 -16
  121. package/static/assets/pdfjs/web/locale/ta/viewer.ftl +0 -12
  122. package/static/assets/pdfjs/web/locale/te/viewer.ftl +0 -16
  123. package/static/assets/pdfjs/web/locale/tg/viewer.ftl +0 -32
  124. package/static/assets/pdfjs/web/locale/th/viewer.ftl +38 -32
  125. package/static/assets/pdfjs/web/locale/tl/viewer.ftl +0 -16
  126. package/static/assets/pdfjs/web/locale/tr/viewer.ftl +0 -32
  127. package/static/assets/pdfjs/web/locale/trs/viewer.ftl +0 -12
  128. package/static/assets/pdfjs/web/locale/uk/viewer.ftl +0 -32
  129. package/static/assets/pdfjs/web/locale/ur/viewer.ftl +0 -16
  130. package/static/assets/pdfjs/web/locale/uz/viewer.ftl +0 -12
  131. package/static/assets/pdfjs/web/locale/vi/viewer.ftl +0 -32
  132. package/static/assets/pdfjs/web/locale/xh/viewer.ftl +0 -12
  133. package/static/assets/pdfjs/web/locale/zh-CN/viewer.ftl +0 -32
  134. package/static/assets/pdfjs/web/locale/zh-TW/viewer.ftl +0 -32
  135. package/static/assets/pdfjs/web/viewer.css +586 -437
  136. package/static/assets/pdfjs/web/viewer.html +12 -23
  137. package/static/assets/pdfjs/web/viewer.mjs +955 -514
  138. package/static/assets/pdfjs/web/viewer.mjs.map +1 -1
  139. package/static/assets/pdfjs/web/wasm/openjpeg.wasm +0 -0
  140. package/static/assets/pdfjs/web/wasm/openjpeg_nowasm_fallback.js +10 -22
  141. package/static/{chunk-NW3CTYUW.js → chunk-3GFGJYMK.js} +1 -1
  142. package/static/{chunk-DNMO47SY.js → chunk-4YGJGZZZ.js} +1 -1
  143. package/static/{chunk-5M4YJZUB.js → chunk-5K7HEX3C.js} +1 -1
  144. package/static/{chunk-GCUWGVYT.js → chunk-5KLMS6A4.js} +1 -1
  145. package/static/{chunk-HME7LAEY.js → chunk-7ITZXYYJ.js} +1 -1
  146. package/static/chunk-ATP3BFHV.js +562 -0
  147. package/static/{chunk-KPZ7FEMO.js → chunk-BB4G55KE.js} +1 -1
  148. package/static/{chunk-X7MFVDBY.js → chunk-DJYJ66UF.js} +1 -1
  149. package/static/{chunk-XCBLEI2E.js → chunk-EVIE5F2U.js} +1 -1
  150. package/static/{chunk-IEUANP3Q.js → chunk-EWKSX76T.js} +1 -1
  151. package/static/{chunk-CN27VAGB.js → chunk-FHLACA7V.js} +1 -1
  152. package/static/{chunk-ABGR5AYC.js → chunk-GCATNU55.js} +1 -1
  153. package/static/chunk-GYODPCIE.js +1 -0
  154. package/static/{chunk-YVJDYSDE.js → chunk-HZTFYLM5.js} +1 -1
  155. package/static/{chunk-G3FOG2QB.js → chunk-IPAC4VAF.js} +1 -1
  156. package/static/{chunk-QFOMEU3T.js → chunk-IQOALFYU.js} +1 -1
  157. package/static/{chunk-O3ANXCPE.js → chunk-JSUKJT6Z.js} +1 -1
  158. package/static/{chunk-NN3VQOS7.js → chunk-LTGFCQR7.js} +1 -1
  159. package/static/{chunk-6BFNMDUD.js → chunk-LV3PYKWO.js} +1 -1
  160. package/static/{chunk-5ZGQYTS2.js → chunk-N2WFNW6M.js} +1 -1
  161. package/static/{chunk-RKNTQYMU.js → chunk-ORMRCEGT.js} +1 -1
  162. package/static/{chunk-M57NVD4V.js → chunk-OUTBJSMW.js} +1 -1
  163. package/static/{chunk-WINILGQN.js → chunk-PTGDOWV3.js} +1 -1
  164. package/static/{chunk-EI4PVI2W.js → chunk-QNJFQVYI.js} +1 -1
  165. package/static/{chunk-YD74UCFG.js → chunk-RS2PX32L.js} +1 -1
  166. package/static/{chunk-2XJ5Z2GZ.js → chunk-RSSWH3S2.js} +1 -1
  167. package/static/{chunk-G2TKYYWK.js → chunk-SIPE37PA.js} +1 -1
  168. package/static/{chunk-ET6QDNNM.js → chunk-TKTCBDOG.js} +1 -1
  169. package/static/{chunk-YDFVKH2D.js → chunk-V6K2N46L.js} +1 -1
  170. package/static/{chunk-2TZUZMCM.js → chunk-XLCCZSQL.js} +3 -3
  171. package/static/{chunk-IIFHIIC6.js → chunk-YPEH66GG.js} +1 -1
  172. package/static/{chunk-XLWCV4HI.js → chunk-YPOIUQ57.js} +1 -1
  173. package/static/{chunk-XPIYOZBX.js → chunk-ZKCFO2OA.js} +1 -1
  174. package/static/index.html +1 -1
  175. package/static/main-MZ7HWZXO.js +9 -0
  176. package/static/chunk-6IRL673W.js +0 -559
  177. package/static/chunk-UQ4TRQCE.js +0 -1
  178. package/static/main-QNBKYA6L.js +0 -9
@@ -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, OrFilter } 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 ldapConfig = configuration.auth.ldap\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.dbLogin(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.ldapConfig.attributes.login] || !entry[this.ldapConfig.attributes.email]) {\n this.logger.error(`${this.validateUser.name} - required ldap fields are missing : \n [${this.ldapConfig.attributes.login}, ${this.ldapConfig.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(login: string, password: string): Promise<LdapUserEntry | false> {\n const ldapLogin = this.buildLdapLogin(login)\n const isAD = this.ldapConfig.attributes.login === LDAP_LOGIN_ATTR.SAM || this.ldapConfig.attributes.login === 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 ? ldapLogin : `${this.ldapConfig.attributes.login}=${ldapLogin},${this.ldapConfig.baseDN}`\n let client: Client\n let error: any\n for (const s of this.ldapConfig.servers) {\n client = new Client({ ...this.clientOptions, url: s })\n try {\n await client.bind(bindUserDN, password)\n return await this.checkAccess(ldapLogin, client)\n } catch (e) {\n if (e.errors?.length) {\n for (const err of e.errors) {\n this.logger.warn(`${this.checkAuth.name} - ${ldapLogin} : ${err}`)\n error = err\n }\n } else {\n error = e\n this.logger.warn(`${this.checkAuth.name} - ${ldapLogin} : ${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(login: string, client: Client): Promise<LdapUserEntry | false> {\n const searchFilter = this.buildUserFilter(login, this.ldapConfig.filter)\n try {\n const { searchEntries } = await client.search(this.ldapConfig.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 // create\n const createdUser = await this.adminUsersManager.createUserOrGuest(identity, identity.role)\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 // update: 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 if (identityHasChanged?.role != null) {\n if (user.role === USER_ROLE.ADMINISTRATOR && !this.ldapConfig.adminGroup) {\n // Prevent removing admin role when adminGroup was removed or not defined\n delete identityHasChanged.role\n }\n }\n // Update user properties\n await this.adminUsersManager.updateUserOrGuest(user.id, identityHasChanged)\n // Extra stuff\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 (attr === LDAP_COMMON_ATTR.MEMBER_OF && entry[attr]) {\n entry[attr] = (Array.isArray(entry[attr]) ? entry[attr] : entry[attr] ? [entry[attr]] : [])\n .filter((v: any) => typeof v === 'string')\n .map((v) => v.match(/cn\\s*=\\s*([^,]+)/i)?.[1]?.trim())\n .filter(Boolean)\n continue\n }\n if (Array.isArray(entry[attr])) {\n // Keep only the first value for all other attributes (e.g., email)\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 const isAdmin =\n typeof this.ldapConfig.adminGroup === 'string' &&\n this.ldapConfig.adminGroup &&\n entry[LDAP_COMMON_ATTR.MEMBER_OF]?.includes(this.ldapConfig.adminGroup)\n return {\n login: this.dbLogin(entry[this.ldapConfig.attributes.login]),\n email: entry[this.ldapConfig.attributes.email] as string,\n password: password,\n role: isAdmin ? USER_ROLE.ADMINISTRATOR : USER_ROLE.USER,\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 dbLogin(login: string): string {\n if (login.includes('@')) {\n return login.split('@')[0]\n } else if (login.includes('\\\\')) {\n return login.split('\\\\').slice(-1)[0]\n }\n return login\n }\n\n private buildLdapLogin(login: string): string {\n if (this.ldapConfig.attributes.login === LDAP_LOGIN_ATTR.UPN) {\n if (this.ldapConfig.upnSuffix && !login.includes('@')) {\n return `${login}@${this.ldapConfig.upnSuffix}`\n }\n } else if (this.ldapConfig.attributes.login === LDAP_LOGIN_ATTR.SAM) {\n if (this.ldapConfig.netbiosName && !login.includes('\\\\')) {\n return `${this.ldapConfig.netbiosName}\\\\${login}`\n }\n }\n return login\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@sync-in.com)(sAMAccountName=john.doe)(uid=john.doe))(*extraFilter*))\n\n // Handle the case where the sAMAccountName is provided in domain-qualified format (e.g., SYNC_IN\\\\user)\n // Note: sAMAccountName is always stored without the domain in Active Directory.\n const uid = this.dbLogin(login)\n\n const or = new OrFilter({\n filters: [\n new EqualityFilter({ attribute: LDAP_LOGIN_ATTR.UPN, value: login }),\n new EqualityFilter({ attribute: LDAP_LOGIN_ATTR.SAM, value: uid }),\n new EqualityFilter({ attribute: LDAP_LOGIN_ATTR.UID, value: uid })\n ]\n })\n\n // Convert to LDAP filter string\n let filterString = new AndFilter({ filters: [or] }).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","dbLogin","isGuest","logUser","isActive","logger","error","name","HttpException","HttpStatus","FORBIDDEN","entry","checkAuth","authSuccess","validateAppPassword","updateAccesses","catch","e","ldapConfig","attributes","email","JSON","stringify","identity","createIdentity","updateOrCreateUser","ldapLogin","buildLdapLogin","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","ALL_LDAP_ATTRIBUTES","debug","convertToLdapUserEntry","createdUser","adminUsersManager","createUserOrGuest","role","freshUser","fromUserId","id","NOT_FOUND","identityHasChanged","Object","fromEntries","Promise","all","keys","map","key","isSame","comparePassword","Boolean","USER_ROLE","ADMINISTRATOR","adminGroup","updateUserOrGuest","assign","setFullName","attr","LDAP_COMMON_ATTR","MEMBER_OF","Array","isArray","v","match","trim","isAdmin","includes","USER","getFirstNameAndLastName","sn","givenName","firstName","lastName","displayName","splitFullName","cn","split","slice","upnSuffix","netbiosName","extraFilter","uid","or","OrFilter","filters","EqualityFilter","attribute","value","UID","filterString","AndFilter","toString","Logger","configuration","auth","ldap","timeout","connectTimeout"],"mappings":"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"}
@@ -13,6 +13,7 @@ const _adminusersmanagerservice = require("../../../applications/users/services/
13
13
  const _usersmanagerservice = require("../../../applications/users/services/users-manager.service");
14
14
  const _functions = /*#__PURE__*/ _interop_require_wildcard(require("../../../common/functions"));
15
15
  const _configenvironment = require("../../../configuration/config.environment");
16
+ const _authldap = require("../../constants/auth-ldap");
16
17
  const _authmethodldapservice = require("./auth-method-ldap.service");
17
18
  function _getRequireWildcardCache(nodeInterop) {
18
19
  if (typeof WeakMap !== "function") return null;
@@ -57,17 +58,17 @@ function _interop_require_wildcard(obj, nodeInterop) {
57
58
  }
58
59
  // Mock ldapts Client to simulate LDAP behaviors
59
60
  jest.mock('ldapts', ()=>{
60
- let InvalidCredentialsError = class InvalidCredentialsError extends Error {
61
- };
61
+ const actual = jest.requireActual('ldapts');
62
62
  const mockClientInstance = {
63
63
  bind: jest.fn(),
64
64
  search: jest.fn(),
65
65
  unbind: jest.fn()
66
66
  };
67
67
  const Client = jest.fn().mockImplementation(()=>mockClientInstance);
68
+ // Conserver tous les autres exports réels (dont EqualityFilter, AndFilter, InvalidCredentialsError, etc.)
68
69
  return {
69
- Client,
70
- InvalidCredentialsError
70
+ ...actual,
71
+ Client
71
72
  };
72
73
  });
73
74
  // --- Test helpers (DRY) ---
@@ -97,6 +98,7 @@ const buildUser = (overrides = {})=>({
97
98
  isGuest: false,
98
99
  isActive: true,
99
100
  makePaths: jest.fn().mockResolvedValue(undefined),
101
+ setFullName: jest.fn(),
100
102
  ...overrides
101
103
  });
102
104
  // --------------------------
@@ -117,6 +119,17 @@ describe(_authmethodldapservice.AuthMethodLdapService.name, ()=>{
117
119
  };
118
120
  const spyLoggerError = ()=>jest.spyOn(authMethodLdapService['logger'], 'error').mockImplementation(()=>undefined);
119
121
  beforeAll(async ()=>{
122
+ _configenvironment.configuration.auth.ldap = {
123
+ servers: [
124
+ 'ldap://localhost:389'
125
+ ],
126
+ attributes: {
127
+ login: _authldap.LDAP_LOGIN_ATTR.UID,
128
+ email: 'mail'
129
+ },
130
+ baseDN: 'ou=people,dc=example,dc=org',
131
+ filter: ''
132
+ };
120
133
  const module = await _testing.Test.createTestingModule({
121
134
  providers: [
122
135
  _authmethodldapservice.AuthMethodLdapService,
@@ -125,7 +138,9 @@ describe(_authmethodldapservice.AuthMethodLdapService.name, ()=>{
125
138
  useValue: {
126
139
  findUser: jest.fn(),
127
140
  logUser: jest.fn(),
128
- updateAccesses: jest.fn().mockResolvedValue(undefined)
141
+ updateAccesses: jest.fn().mockResolvedValue(undefined),
142
+ validateAppPassword: jest.fn(),
143
+ fromUserId: jest.fn()
129
144
  }
130
145
  },
131
146
  {
@@ -143,17 +158,6 @@ describe(_authmethodldapservice.AuthMethodLdapService.name, ()=>{
143
158
  authMethodLdapService = module.get(_authmethodldapservice.AuthMethodLdapService);
144
159
  adminUsersManager = module.get(_adminusersmanagerservice.AdminUsersManager);
145
160
  usersManager = module.get(_usersmanagerservice.UsersManager);
146
- _configenvironment.configuration.auth.ldap = {
147
- servers: [
148
- 'ldap://localhost:389'
149
- ],
150
- attributes: {
151
- login: 'uid',
152
- email: 'mail'
153
- },
154
- baseDN: 'ou=people,dc=example,dc=org',
155
- filter: ''
156
- };
157
161
  });
158
162
  it('should be defined', ()=>{
159
163
  expect(authMethodLdapService).toBeDefined();
@@ -180,7 +184,7 @@ describe(_authmethodldapservice.AuthMethodLdapService.name, ()=>{
180
184
  expect(usersManager.logUser).toHaveBeenCalledWith(guestUser, 'pass', '127.0.0.1');
181
185
  expect(_ldapts.Client).not.toHaveBeenCalled(); // client should not be constructed
182
186
  });
183
- it('should throw FORBIDDEN for locked account and LDAP login mismatch', async ()=>{
187
+ it('should throw FORBIDDEN for locked account and resolve null for LDAP login mismatch', async ()=>{
184
188
  // Phase 1: locked account
185
189
  usersManager.findUser.mockResolvedValue({
186
190
  login: 'john',
@@ -190,7 +194,7 @@ describe(_authmethodldapservice.AuthMethodLdapService.name, ()=>{
190
194
  const loggerErrorSpy1 = jest.spyOn(authMethodLdapService['logger'], 'error').mockImplementation(()=>undefined);
191
195
  await expect(authMethodLdapService.validateUser('john', 'pwd')).rejects.toThrow(/account locked/i);
192
196
  expect(loggerErrorSpy1).toHaveBeenCalled();
193
- // Phase 2: mismatch between requested login and LDAP returned login
197
+ // Phase 2: mismatch between requested login and LDAP returned login -> service renvoie null
194
198
  const existingUser = buildUser({
195
199
  id: 8
196
200
  });
@@ -203,7 +207,7 @@ describe(_authmethodldapservice.AuthMethodLdapService.name, ()=>{
203
207
  mail: 'jane@example.org'
204
208
  }
205
209
  ]);
206
- await expect(authMethodLdapService.validateUser('john', 'pwd')).resolves.toEqual(null);
210
+ await expect(authMethodLdapService.validateUser('john', 'pwd')).rejects.toThrow(/account matching error/i);
207
211
  });
208
212
  it('should handle invalid LDAP credentials for both existing and unknown users', async ()=>{
209
213
  // Phase 1: existing user -> updateAccesses invoked with success=false and logger.error intercepted
@@ -247,14 +251,15 @@ describe(_authmethodldapservice.AuthMethodLdapService.name, ()=>{
247
251
  expect(adminUsersManager.createUserOrGuest).not.toHaveBeenCalled();
248
252
  expect(loggerErrorSpy).toHaveBeenCalled();
249
253
  // Phase 2: create a new user (success, single email)
254
+ // Stub directement checkAuth pour retourner une entrée LDAP valide
255
+ const checkAuthSpy = jest.spyOn(authMethodLdapService, 'checkAuth');
256
+ checkAuthSpy.mockResolvedValueOnce({
257
+ uid: 'john',
258
+ cn: 'John Doe',
259
+ mail: 'john@example.org'
260
+ });
261
+ adminUsersManager.createUserOrGuest.mockClear();
250
262
  usersManager.findUser.mockResolvedValue(null);
251
- setupLdapSuccess([
252
- {
253
- uid: 'john',
254
- cn: 'John Doe',
255
- mail: 'john@example.org'
256
- }
257
- ]);
258
263
  const createdUser = {
259
264
  id: 2,
260
265
  login: 'john',
@@ -263,6 +268,8 @@ describe(_authmethodldapservice.AuthMethodLdapService.name, ()=>{
263
268
  makePaths: jest.fn()
264
269
  };
265
270
  adminUsersManager.createUserOrGuest.mockResolvedValue(createdUser);
271
+ // If the service reloads the user via fromUserId after creation
272
+ usersManager.fromUserId.mockResolvedValue(createdUser);
266
273
  // Cover the success-flow catch branch
267
274
  const loggerErrorSpy2 = spyLoggerError();
268
275
  usersManager.updateAccesses.mockRejectedValueOnce(new Error('updateAccesses success flow boom'));
@@ -272,13 +279,15 @@ describe(_authmethodldapservice.AuthMethodLdapService.name, ()=>{
272
279
  email: 'john@example.org',
273
280
  password: 'pwd',
274
281
  firstName: 'John',
275
- lastName: 'Doe'
282
+ lastName: 'Doe',
283
+ role: 1
276
284
  }, expect.anything() // USER_ROLE.USER
277
285
  );
278
286
  expect(resB).toBe(createdUser);
279
287
  expect(usersManager.updateAccesses).toHaveBeenCalledWith(createdUser, '192.168.1.10', true);
280
288
  expect(loggerErrorSpy2).toHaveBeenCalled();
281
289
  // Phase 3: multiple emails -> keep the first
290
+ adminUsersManager.createUserOrGuest.mockClear();
282
291
  usersManager.findUser.mockResolvedValue(null);
283
292
  setupLdapSuccess([
284
293
  {
@@ -296,6 +305,7 @@ describe(_authmethodldapservice.AuthMethodLdapService.name, ()=>{
296
305
  makePaths: jest.fn()
297
306
  };
298
307
  adminUsersManager.createUserOrGuest.mockResolvedValue(createdUser2);
308
+ usersManager.fromUserId.mockResolvedValue(createdUser2);
299
309
  const resC = await authMethodLdapService.validateUser('multi', 'pwd');
300
310
  expect(adminUsersManager.createUserOrGuest).toHaveBeenCalledWith(expect.objectContaining({
301
311
  email: 'first@example.org'
@@ -403,6 +413,20 @@ describe(_authmethodldapservice.AuthMethodLdapService.name, ()=>{
403
413
  expect(resB).toBeNull();
404
414
  expect(usersManager.updateAccesses).toHaveBeenCalledWith(existingUser2, '1.1.1.1', false);
405
415
  });
416
+ it('should allow app password when LDAP fails and scope is provided', async ()=>{
417
+ const existingUser = buildUser({
418
+ id: 42
419
+ });
420
+ usersManager.findUser.mockResolvedValue(existingUser);
421
+ // LDAP invalid credentials
422
+ mockBindRejectInvalid(ldapClient, _ldapts.InvalidCredentialsError, 'invalid credentials');
423
+ // App password success
424
+ usersManager.validateAppPassword.mockResolvedValue(true);
425
+ const res = await authMethodLdapService.validateUser('john', 'app-password', '10.0.0.2', 'webdav');
426
+ expect(res).toBe(existingUser);
427
+ expect(usersManager.validateAppPassword).toHaveBeenCalledWith(existingUser, 'app-password', '10.0.0.2', 'webdav');
428
+ expect(usersManager.updateAccesses).toHaveBeenCalledWith(existingUser, '10.0.0.2', true);
429
+ });
406
430
  it('should throw 500 when LDAP connection error occurs during bind', async ()=>{
407
431
  // Arrange: no existing user to reach checkAuth flow
408
432
  usersManager.findUser.mockResolvedValue(null);
@@ -430,7 +454,7 @@ describe(_authmethodldapservice.AuthMethodLdapService.name, ()=>{
430
454
  expect(res).toBeNull();
431
455
  expect(usersManager.updateAccesses).not.toHaveBeenCalled();
432
456
  });
433
- it('should log update failure and still call makePaths when updating existing user', async ()=>{
457
+ it('should log update failure when updating existing user', async ()=>{
434
458
  // Arrange: existing user with changed identity
435
459
  const existingUser = buildUser({
436
460
  id: 11,
@@ -438,7 +462,6 @@ describe(_authmethodldapservice.AuthMethodLdapService.name, ()=>{
438
462
  });
439
463
  usersManager.findUser.mockResolvedValue(existingUser);
440
464
  // Ensure LDAP loginAttribute matches uid for this test (a previous test sets it to 'cn')
441
- _configenvironment.configuration.auth.ldap.attributes.login = 'uid';
442
465
  setupLdapSuccess([
443
466
  {
444
467
  uid: 'john',
@@ -455,8 +478,6 @@ describe(_authmethodldapservice.AuthMethodLdapService.name, ()=>{
455
478
  });
456
479
  const res = await authMethodLdapService.validateUser('john', 'pwd');
457
480
  expect(adminUsersManager.updateUserOrGuest).toHaveBeenCalled();
458
- // makePaths still invoked
459
- expect(existingUser.makePaths).toHaveBeenCalled();
460
481
  // Local fields unchanged since update failed
461
482
  expect(existingUser.email).toBe('old@ex.org');
462
483
  expect(res).toBe(existingUser);
@@ -470,22 +491,7 @@ describe(_authmethodldapservice.AuthMethodLdapService.name, ()=>{
470
491
  isActive: true
471
492
  };
472
493
  usersManager.findUser.mockResolvedValue(userA);
473
- _configenvironment.configuration.auth.ldap.attributes.login = 'uid';
474
494
  ldapClient.bind.mockResolvedValue(undefined);
475
- // Non-matching entry: uid !== requested uid
476
- ldapClient.search.mockResolvedValue({
477
- searchEntries: [
478
- {
479
- uid: 'jane',
480
- cn: 'Jane Doe',
481
- mail: 'jane@example.org'
482
- }
483
- ]
484
- });
485
- ldapClient.unbind.mockResolvedValue(undefined);
486
- const resA = await authMethodLdapService.validateUser('john', 'pwd', '3.3.3.3');
487
- expect(resA).toBeNull();
488
- expect(usersManager.updateAccesses).toHaveBeenCalledWith(userA, '3.3.3.3', false);
489
495
  // Phase B: Matching entry + password considered changed -> updateUserOrGuest called, password not reassigned locally
490
496
  jest.clearAllMocks();
491
497
  const userB = buildUser({
@@ -493,7 +499,6 @@ describe(_authmethodldapservice.AuthMethodLdapService.name, ()=>{
493
499
  email: 'old@ex.org'
494
500
  });
495
501
  usersManager.findUser.mockResolvedValue(userB);
496
- _configenvironment.configuration.auth.ldap.attributes.login = 'uid';
497
502
  setupLdapSuccess([
498
503
  {
499
504
  uid: 'john',
@@ -522,7 +527,6 @@ describe(_authmethodldapservice.AuthMethodLdapService.name, ()=>{
522
527
  firstName: 'John',
523
528
  lastName: 'Doe'
524
529
  });
525
- expect(userB.makePaths).toHaveBeenCalled();
526
530
  expect(resB).toBe(userB);
527
531
  });
528
532
  });
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../backend/src/authentication/services/auth-methods/auth-method-ldap.service.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 { Test, TestingModule } from '@nestjs/testing'\nimport { Mocked } from 'jest-mock'\nimport { Client, InvalidCredentialsError } from 'ldapts'\nimport { CONNECT_ERROR_CODE } from '../../../app.constants'\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 * as commonFunctions from '../../../common/functions'\nimport { configuration } from '../../../configuration/config.environment'\nimport { AuthMethodLdapService } from './auth-method-ldap.service'\n\n// Mock ldapts Client to simulate LDAP behaviors\njest.mock('ldapts', () => {\n class InvalidCredentialsError extends Error {}\n const mockClientInstance = {\n bind: jest.fn(),\n search: jest.fn(),\n unbind: jest.fn()\n }\n const Client = jest.fn().mockImplementation(() => mockClientInstance)\n return { Client, InvalidCredentialsError }\n})\n\n// --- Test helpers (DRY) ---\n// Reusable LDAP mocks\nconst mockBindResolve = (ldapClient: any) => {\n ldapClient.bind.mockResolvedValue(undefined)\n ldapClient.unbind.mockResolvedValue(undefined)\n}\nconst mockBindRejectInvalid = (ldapClient: any, InvalidCredentialsErrorCtor: any, message = 'invalid') => {\n ldapClient.bind.mockRejectedValue(new InvalidCredentialsErrorCtor(message))\n ldapClient.unbind.mockResolvedValue(undefined)\n}\nconst mockSearchEntries = (ldapClient: any, entries: any[]) => {\n ldapClient.search.mockResolvedValue({ searchEntries: entries })\n}\nconst mockSearchReject = (ldapClient: any, err: Error) => {\n ldapClient.search.mockRejectedValue(err)\n}\n// User factory\nconst buildUser = (overrides: Partial<UserModel> = {}) =>\n ({\n id: 0,\n login: 'john',\n email: 'old@example.org',\n password: 'hashed',\n isGuest: false,\n isActive: true,\n makePaths: jest.fn().mockResolvedValue(undefined),\n ...overrides\n }) as any\n\n// --------------------------\n\ndescribe(AuthMethodLdapService.name, () => {\n let authMethodLdapService: AuthMethodLdapService\n let usersManager: Mocked<UsersManager>\n let adminUsersManager: Mocked<AdminUsersManager>\n const ldapClient = {\n bind: jest.fn(),\n search: jest.fn(),\n unbind: jest.fn()\n }\n ;(Client as Mocked<any>).mockImplementation(() => ldapClient)\n\n // Local helpers (need access to authMethodLdapService and ldapClient in this scope)\n const setupLdapSuccess = (entries: any[]) => {\n mockBindResolve(ldapClient)\n mockSearchEntries(ldapClient, entries)\n }\n const spyLoggerError = () => jest.spyOn(authMethodLdapService['logger'], 'error').mockImplementation(() => undefined as any)\n\n beforeAll(async () => {\n const module: TestingModule = await Test.createTestingModule({\n providers: [\n AuthMethodLdapService,\n {\n provide: UsersManager,\n useValue: {\n findUser: jest.fn(),\n logUser: jest.fn(),\n updateAccesses: jest.fn().mockResolvedValue(undefined)\n }\n },\n {\n provide: AdminUsersManager,\n useValue: {\n createUserOrGuest: jest.fn(),\n updateUserOrGuest: jest.fn()\n }\n }\n ]\n }).compile()\n\n module.useLogger(['fatal'])\n authMethodLdapService = module.get<AuthMethodLdapService>(AuthMethodLdapService)\n adminUsersManager = module.get<Mocked<AdminUsersManager>>(AdminUsersManager)\n usersManager = module.get<Mocked<UsersManager>>(UsersManager)\n configuration.auth.ldap = {\n servers: ['ldap://localhost:389'],\n attributes: { login: 'uid', email: 'mail' },\n baseDN: 'ou=people,dc=example,dc=org',\n filter: ''\n }\n })\n\n it('should be defined', () => {\n expect(authMethodLdapService).toBeDefined()\n expect(usersManager).toBeDefined()\n expect(adminUsersManager).toBeDefined()\n expect(ldapClient).toBeDefined()\n })\n\n it('should authenticate a guest user via database and bypass LDAP', async () => {\n // Arrange\n const guestUser: any = { id: 1, login: 'guest1', isGuest: true, isActive: true }\n usersManager.findUser.mockResolvedValue(guestUser)\n const dbAuthResult: any = { ...guestUser, token: 'jwt' }\n usersManager.logUser.mockResolvedValue(dbAuthResult)\n\n const res = await authMethodLdapService.validateUser('guest1', 'pass', '127.0.0.1')\n\n expect(res).toEqual(dbAuthResult)\n expect(usersManager.logUser).toHaveBeenCalledWith(guestUser, 'pass', '127.0.0.1')\n expect(Client).not.toHaveBeenCalled() // client should not be constructed\n })\n\n it('should throw FORBIDDEN for locked account and LDAP login mismatch', async () => {\n // Phase 1: locked account\n usersManager.findUser.mockResolvedValue({ login: 'john', isGuest: false, isActive: false } as UserModel)\n const loggerErrorSpy1 = jest.spyOn(authMethodLdapService['logger'], 'error').mockImplementation(() => undefined as any)\n\n await expect(authMethodLdapService.validateUser('john', 'pwd')).rejects.toThrow(/account locked/i)\n expect(loggerErrorSpy1).toHaveBeenCalled()\n\n // Phase 2: mismatch between requested login and LDAP returned login\n const existingUser: any = buildUser({ id: 8 })\n usersManager.findUser.mockResolvedValue(existingUser)\n mockBindResolve(ldapClient)\n mockSearchEntries(ldapClient, [{ uid: 'jane', cn: 'john', mail: 'jane@example.org' }])\n\n await expect(authMethodLdapService.validateUser('john', 'pwd')).resolves.toEqual(null)\n })\n\n it('should handle invalid LDAP credentials for both existing and unknown users', async () => {\n // Phase 1: existing user -> updateAccesses invoked with success=false and logger.error intercepted\n const existingUser: any = buildUser({ id: 1 })\n usersManager.findUser.mockResolvedValue(existingUser)\n // Make LDAP bind throw InvalidCredentialsError\n mockBindRejectInvalid(ldapClient, InvalidCredentialsError, 'invalid credentials')\n // Force updateAccesses to reject to hit the catch and logger.error\n const loggerErrorSpy = jest.spyOn(authMethodLdapService['logger'], 'error').mockImplementation(() => undefined as any)\n usersManager.updateAccesses.mockRejectedValueOnce(new Error('updateAccesses boom'))\n\n const res1 = await authMethodLdapService.validateUser('john', 'badpwd', '10.0.0.1')\n\n expect(res1).toBeNull()\n expect(usersManager.updateAccesses).toHaveBeenCalledWith(existingUser, '10.0.0.1', false)\n expect(loggerErrorSpy).toHaveBeenCalled()\n\n // Phase 2: unknown user → no access update\n usersManager.updateAccesses.mockClear()\n usersManager.findUser.mockResolvedValue(null)\n ldapClient.bind.mockRejectedValue(new InvalidCredentialsError('invalid'))\n ldapClient.unbind.mockResolvedValue(undefined)\n\n const res2 = await authMethodLdapService.validateUser('jane', 'badpwd')\n\n expect(res2).toBeNull()\n expect(usersManager.updateAccesses).not.toHaveBeenCalled()\n })\n\n it('should handle LDAP new-user flow: missing fields, creation success, and multi-email selection', async () => {\n // Phase 1: incomplete LDAP entry -> null + error log, no creation\n usersManager.findUser.mockResolvedValue(null)\n mockBindResolve(ldapClient)\n // Simulate an entry with missing mail\n mockSearchEntries(ldapClient, [{ uid: 'jane', cn: 'Jane Doe', mail: undefined }])\n const loggerErrorSpy = jest.spyOn(authMethodLdapService['logger'], 'error').mockImplementation(() => undefined as any)\n\n const resA = await authMethodLdapService.validateUser('jane', 'pwd')\n\n expect(resA).toBeNull()\n expect(adminUsersManager.createUserOrGuest).not.toHaveBeenCalled()\n expect(loggerErrorSpy).toHaveBeenCalled()\n\n // Phase 2: create a new user (success, single email)\n usersManager.findUser.mockResolvedValue(null)\n setupLdapSuccess([{ uid: 'john', cn: 'John Doe', mail: 'john@example.org' }])\n\n const createdUser: any = { id: 2, login: 'john', isGuest: false, isActive: true, makePaths: jest.fn() }\n adminUsersManager.createUserOrGuest.mockResolvedValue(createdUser)\n // Cover the success-flow catch branch\n const loggerErrorSpy2 = spyLoggerError()\n usersManager.updateAccesses.mockRejectedValueOnce(new Error('updateAccesses success flow boom'))\n\n const resB = await authMethodLdapService.validateUser('john', 'pwd', '192.168.1.10')\n\n expect(adminUsersManager.createUserOrGuest).toHaveBeenCalledWith(\n { login: 'john', email: 'john@example.org', password: 'pwd', firstName: 'John', lastName: 'Doe' },\n expect.anything() // USER_ROLE.USER\n )\n expect(resB).toBe(createdUser)\n expect(usersManager.updateAccesses).toHaveBeenCalledWith(createdUser, '192.168.1.10', true)\n expect(loggerErrorSpy2).toHaveBeenCalled()\n\n // Phase 3: multiple emails -> keep the first\n usersManager.findUser.mockResolvedValue(null)\n setupLdapSuccess([{ uid: 'multi', cn: 'Multi Mail', mail: ['first@example.org', 'second@example.org'] }])\n\n const createdUser2: any = { id: 9, login: 'multi', makePaths: jest.fn() }\n adminUsersManager.createUserOrGuest.mockResolvedValue(createdUser2)\n\n const resC = await authMethodLdapService.validateUser('multi', 'pwd')\n\n expect(adminUsersManager.createUserOrGuest).toHaveBeenCalledWith(expect.objectContaining({ email: 'first@example.org' }), expect.anything())\n expect(resC).toBe(createdUser2)\n })\n\n it('should update existing user profile when LDAP identity changed (except password assigned back)', async () => {\n // Arrange: existing user with different profile and an old password\n const existingUser: any = buildUser({ id: 5 })\n usersManager.findUser.mockResolvedValue(existingUser)\n\n // LDAP succeeds and returns different email and same uid\n setupLdapSuccess([{ uid: 'john', cn: 'John Doe', mail: 'john@example.org' }])\n\n // Admin manager successfully updates a user\n adminUsersManager.updateUserOrGuest.mockResolvedValue(undefined)\n\n // Ensure password is considered changed so the update payload includes it,\n // which then triggers the deletion and local assignment branches after update\n const compareSpy = jest.spyOn(commonFunctions, 'comparePassword').mockResolvedValue(false)\n\n const res = await authMethodLdapService.validateUser('john', 'new-plain-password', '127.0.0.2')\n\n expect(adminUsersManager.updateUserOrGuest).toHaveBeenCalledWith(\n 5,\n expect.objectContaining({\n email: 'john@example.org',\n firstName: 'John',\n lastName: 'Doe'\n })\n )\n // Password should not be assigned back onto the user object (it is deleted before Object.assign)\n expect(existingUser.password).toBe('hashed')\n // Other fields should be updated locally\n expect(existingUser.email).toBe('john@example.org')\n expect(existingUser).toMatchObject({ firstName: 'John', lastName: 'Doe' })\n // Accesses updated as success\n expect(usersManager.updateAccesses).toHaveBeenCalledWith(existingUser, '127.0.0.2', true)\n // Returned user is the same instance\n expect(res).toBe(existingUser)\n\n // Second run: password unchanged (comparePassword => true) to cover the null branch for password\n adminUsersManager.updateUserOrGuest.mockClear()\n usersManager.updateAccesses.mockClear()\n // Force another non-password change so an update occurs\n existingUser.email = 'old@example.org'\n compareSpy.mockResolvedValue(true)\n\n const res2 = await authMethodLdapService.validateUser('john', 'same-plain-password', '127.0.0.3')\n\n // Update should be called without password, only with changed fields\n expect(adminUsersManager.updateUserOrGuest).toHaveBeenCalled()\n const updateArgs = adminUsersManager.updateUserOrGuest.mock.calls[0]\n expect(updateArgs[0]).toBe(5)\n expect(updateArgs[1]).toEqual(\n expect.objectContaining({\n email: 'john@example.org'\n })\n )\n expect(updateArgs[1]).toEqual(expect.not.objectContaining({ password: expect.anything() }))\n\n // Password remains unchanged locally\n expect(existingUser.password).toBe('hashed')\n // Accesses updated as success\n expect(usersManager.updateAccesses).toHaveBeenCalledWith(existingUser, '127.0.0.3', true)\n // Returned user is the same instance\n expect(res2).toBe(existingUser)\n\n // Third run: no changes at all (identityHasChanged is empty) to cover the else branch\n adminUsersManager.updateUserOrGuest.mockClear()\n usersManager.updateAccesses.mockClear()\n compareSpy.mockResolvedValue(true)\n\n // Local user already matches LDAP identity; call again\n const res3 = await authMethodLdapService.validateUser('john', 'same-plain-password', '127.0.0.4')\n\n // No update should be triggered\n expect(adminUsersManager.updateUserOrGuest).not.toHaveBeenCalled()\n // Access should still be updated as success\n expect(usersManager.updateAccesses).toHaveBeenCalledWith(existingUser, '127.0.0.4', true)\n // Returned user is the same instance\n expect(res3).toBe(existingUser)\n })\n\n it('should log failed access when LDAP search returns no entry or throws after bind', async () => {\n // Phase 1: no entry found after a successful bind -> failed access\n const existingUser: any = { id: 7, login: 'ghost', isGuest: false, isActive: true }\n usersManager.findUser.mockResolvedValue(existingUser)\n setupLdapSuccess([])\n\n const resA = await authMethodLdapService.validateUser('ghost', 'pwd', '10.10.0.1')\n\n expect(resA).toBeNull()\n expect(usersManager.updateAccesses).toHaveBeenCalledWith(existingUser, '10.10.0.1', false)\n\n // Phase 2: exception during search after a bind -> failed access\n jest.clearAllMocks()\n const existingUser2: any = { id: 10, login: 'john', isGuest: false, isActive: true }\n usersManager.findUser.mockResolvedValue(existingUser2)\n mockBindResolve(ldapClient)\n mockSearchReject(ldapClient, new Error('search failed'))\n\n const resB = await authMethodLdapService.validateUser('john', 'pwd', '1.1.1.1')\n\n expect(resB).toBeNull()\n expect(usersManager.updateAccesses).toHaveBeenCalledWith(existingUser2, '1.1.1.1', false)\n })\n\n it('should throw 500 when LDAP connection error occurs during bind', async () => {\n // Arrange: no existing user to reach checkAuth flow\n usersManager.findUser.mockResolvedValue(null)\n const err1 = new Error('socket hang up')\n const err2 = Object.assign(new Error('connect ECONNREFUSED'), { code: Array.from(CONNECT_ERROR_CODE)[0] })\n ldapClient.bind.mockRejectedValue({ errors: [err1, err2] })\n ldapClient.unbind.mockResolvedValue(undefined)\n\n // First scenario: recognized connection error -> throws 500\n await expect(authMethodLdapService.validateUser('john', 'pwd')).rejects.toThrow(/authentication service/i)\n\n // Second scenario: generic error (no code, not InvalidCredentialsError) -> resolves to null and no access update\n ldapClient.bind.mockReset()\n ldapClient.unbind.mockReset()\n usersManager.updateAccesses.mockClear()\n usersManager.findUser.mockResolvedValue(null as any)\n ldapClient.bind.mockRejectedValue(new Error('unexpected failure'))\n ldapClient.unbind.mockResolvedValue(undefined)\n\n const res = await authMethodLdapService.validateUser('john', 'pwd')\n expect(res).toBeNull()\n expect(usersManager.updateAccesses).not.toHaveBeenCalled()\n })\n\n it('should log update failure and still call makePaths when updating existing user', async () => {\n // Arrange: existing user with changed identity\n const existingUser: any = buildUser({ id: 11, email: 'old@ex.org' })\n usersManager.findUser.mockResolvedValue(existingUser)\n\n // Ensure LDAP loginAttribute matches uid for this test (a previous test sets it to 'cn')\n configuration.auth.ldap.attributes.login = 'uid'\n\n setupLdapSuccess([{ uid: 'john', cn: 'John Doe', mail: 'john@example.org' }])\n adminUsersManager.updateUserOrGuest.mockRejectedValue(new Error('db error'))\n\n // Force identity to be considered changed only for this test\n jest.spyOn(commonFunctions, 'comparePassword').mockResolvedValue(false)\n jest.spyOn(commonFunctions, 'splitFullName').mockReturnValue({ firstName: 'John', lastName: 'Doe' })\n\n const res = await authMethodLdapService.validateUser('john', 'pwd')\n\n expect(adminUsersManager.updateUserOrGuest).toHaveBeenCalled()\n // makePaths still invoked\n expect(existingUser.makePaths).toHaveBeenCalled()\n // Local fields unchanged since update failed\n expect(existingUser.email).toBe('old@ex.org')\n expect(res).toBe(existingUser)\n })\n\n it('should skip non-matching LDAP entries then update user with changed password without reassigning it', async () => {\n // Phase A: LDAP returns an entry but loginAttribute value does not match -> checkAccess returns false (covers return after loop)\n const userA: any = { id: 20, login: 'john', isGuest: false, isActive: true }\n usersManager.findUser.mockResolvedValue(userA)\n configuration.auth.ldap.attributes.login = 'uid'\n ldapClient.bind.mockResolvedValue(undefined)\n // Non-matching entry: uid !== requested uid\n ldapClient.search.mockResolvedValue({ searchEntries: [{ uid: 'jane', cn: 'Jane Doe', mail: 'jane@example.org' }] })\n ldapClient.unbind.mockResolvedValue(undefined)\n\n const resA = await authMethodLdapService.validateUser('john', 'pwd', '3.3.3.3')\n expect(resA).toBeNull()\n expect(usersManager.updateAccesses).toHaveBeenCalledWith(userA, '3.3.3.3', false)\n\n // Phase B: Matching entry + password considered changed -> updateUserOrGuest called, password not reassigned locally\n jest.clearAllMocks()\n const userB: any = buildUser({ id: 21, email: 'old@ex.org' })\n usersManager.findUser.mockResolvedValue(userB)\n configuration.auth.ldap.attributes.login = 'uid'\n setupLdapSuccess([{ uid: 'john', cn: 'John Doe', mail: 'john@example.org' }])\n adminUsersManager.updateUserOrGuest.mockResolvedValue(undefined)\n\n // Force password to be considered changed to execute deletion + Object.assign branch\n jest.spyOn(commonFunctions, 'comparePassword').mockResolvedValue(false)\n jest.spyOn(commonFunctions, 'splitFullName').mockReturnValue({ firstName: 'John', lastName: 'Doe' })\n\n const resB = await authMethodLdapService.validateUser('john', 'newpwd', '4.4.4.4')\n\n // Line 132: updateUserOrGuest call\n expect(adminUsersManager.updateUserOrGuest).toHaveBeenCalledWith(\n 21,\n expect.objectContaining({ email: 'john@example.org', firstName: 'John', lastName: 'Doe' })\n )\n // Lines 139-142: password removed from local assign, other fields assigned\n expect(userB.password).toBe('hashed')\n expect(userB.email).toBe('john@example.org')\n expect(userB).toMatchObject({ firstName: 'John', lastName: 'Doe' })\n expect(userB.makePaths).toHaveBeenCalled()\n expect(resB).toBe(userB)\n })\n})\n"],"names":["jest","mock","InvalidCredentialsError","Error","mockClientInstance","bind","fn","search","unbind","Client","mockImplementation","mockBindResolve","ldapClient","mockResolvedValue","undefined","mockBindRejectInvalid","InvalidCredentialsErrorCtor","message","mockRejectedValue","mockSearchEntries","entries","searchEntries","mockSearchReject","err","buildUser","overrides","id","login","email","password","isGuest","isActive","makePaths","describe","AuthMethodLdapService","name","authMethodLdapService","usersManager","adminUsersManager","setupLdapSuccess","spyLoggerError","spyOn","beforeAll","module","Test","createTestingModule","providers","provide","UsersManager","useValue","findUser","logUser","updateAccesses","AdminUsersManager","createUserOrGuest","updateUserOrGuest","compile","useLogger","get","configuration","auth","ldap","servers","attributes","baseDN","filter","it","expect","toBeDefined","guestUser","dbAuthResult","token","res","validateUser","toEqual","toHaveBeenCalledWith","not","toHaveBeenCalled","loggerErrorSpy1","rejects","toThrow","existingUser","uid","cn","mail","resolves","loggerErrorSpy","mockRejectedValueOnce","res1","toBeNull","mockClear","res2","resA","createdUser","loggerErrorSpy2","resB","firstName","lastName","anything","toBe","createdUser2","resC","objectContaining","compareSpy","commonFunctions","toMatchObject","updateArgs","calls","res3","clearAllMocks","existingUser2","err1","err2","Object","assign","code","Array","from","CONNECT_ERROR_CODE","errors","mockReset","mockReturnValue","userA","userB"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../backend/src/authentication/services/auth-methods/auth-method-ldap.service.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 { Test, TestingModule } from '@nestjs/testing'\nimport { Mocked } from 'jest-mock'\nimport { Client, InvalidCredentialsError } from 'ldapts'\nimport { CONNECT_ERROR_CODE } from '../../../app.constants'\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 * as commonFunctions from '../../../common/functions'\nimport { configuration } from '../../../configuration/config.environment'\nimport { LDAP_LOGIN_ATTR } from '../../constants/auth-ldap'\nimport { AuthMethodLdapService } from './auth-method-ldap.service'\n\n// Mock ldapts Client to simulate LDAP behaviors\njest.mock('ldapts', () => {\n const actual = jest.requireActual('ldapts')\n const mockClientInstance = {\n bind: jest.fn(),\n search: jest.fn(),\n unbind: jest.fn()\n }\n const Client = jest.fn().mockImplementation(() => mockClientInstance)\n // Conserver tous les autres exports réels (dont EqualityFilter, AndFilter, InvalidCredentialsError, etc.)\n return { ...actual, Client }\n})\n\n// --- Test helpers (DRY) ---\n// Reusable LDAP mocks\nconst mockBindResolve = (ldapClient: any) => {\n ldapClient.bind.mockResolvedValue(undefined)\n ldapClient.unbind.mockResolvedValue(undefined)\n}\nconst mockBindRejectInvalid = (ldapClient: any, InvalidCredentialsErrorCtor: any, message = 'invalid') => {\n ldapClient.bind.mockRejectedValue(new InvalidCredentialsErrorCtor(message))\n ldapClient.unbind.mockResolvedValue(undefined)\n}\nconst mockSearchEntries = (ldapClient: any, entries: any[]) => {\n ldapClient.search.mockResolvedValue({ searchEntries: entries })\n}\nconst mockSearchReject = (ldapClient: any, err: Error) => {\n ldapClient.search.mockRejectedValue(err)\n}\n// User factory\nconst buildUser = (overrides: Partial<UserModel> = {}) =>\n ({\n id: 0,\n login: 'john',\n email: 'old@example.org',\n password: 'hashed',\n isGuest: false,\n isActive: true,\n makePaths: jest.fn().mockResolvedValue(undefined),\n setFullName: jest.fn(), // needed when firstName/lastName change\n ...overrides\n }) as any\n\n// --------------------------\n\ndescribe(AuthMethodLdapService.name, () => {\n let authMethodLdapService: AuthMethodLdapService\n let usersManager: Mocked<UsersManager>\n let adminUsersManager: Mocked<AdminUsersManager>\n const ldapClient = {\n bind: jest.fn(),\n search: jest.fn(),\n unbind: jest.fn()\n }\n ;(Client as Mocked<any>).mockImplementation(() => ldapClient)\n\n // Local helpers (need access to authMethodLdapService and ldapClient in this scope)\n const setupLdapSuccess = (entries: any[]) => {\n mockBindResolve(ldapClient)\n mockSearchEntries(ldapClient, entries)\n }\n const spyLoggerError = () => jest.spyOn(authMethodLdapService['logger'], 'error').mockImplementation(() => undefined as any)\n\n beforeAll(async () => {\n configuration.auth.ldap = {\n servers: ['ldap://localhost:389'],\n attributes: { login: LDAP_LOGIN_ATTR.UID, email: 'mail' },\n baseDN: 'ou=people,dc=example,dc=org',\n filter: ''\n }\n\n const module: TestingModule = await Test.createTestingModule({\n providers: [\n AuthMethodLdapService,\n {\n provide: UsersManager,\n useValue: {\n findUser: jest.fn(),\n logUser: jest.fn(),\n updateAccesses: jest.fn().mockResolvedValue(undefined),\n validateAppPassword: jest.fn(),\n fromUserId: jest.fn()\n }\n },\n {\n provide: AdminUsersManager,\n useValue: {\n createUserOrGuest: jest.fn(),\n updateUserOrGuest: jest.fn()\n }\n }\n ]\n }).compile()\n\n module.useLogger(['fatal'])\n authMethodLdapService = module.get<AuthMethodLdapService>(AuthMethodLdapService)\n adminUsersManager = module.get<Mocked<AdminUsersManager>>(AdminUsersManager)\n usersManager = module.get<Mocked<UsersManager>>(UsersManager)\n })\n\n it('should be defined', () => {\n expect(authMethodLdapService).toBeDefined()\n expect(usersManager).toBeDefined()\n expect(adminUsersManager).toBeDefined()\n expect(ldapClient).toBeDefined()\n })\n\n it('should authenticate a guest user via database and bypass LDAP', async () => {\n // Arrange\n const guestUser: any = { id: 1, login: 'guest1', isGuest: true, isActive: true }\n usersManager.findUser.mockResolvedValue(guestUser)\n const dbAuthResult: any = { ...guestUser, token: 'jwt' }\n usersManager.logUser.mockResolvedValue(dbAuthResult)\n const res = await authMethodLdapService.validateUser('guest1', 'pass', '127.0.0.1')\n expect(res).toEqual(dbAuthResult)\n expect(usersManager.logUser).toHaveBeenCalledWith(guestUser, 'pass', '127.0.0.1')\n expect(Client).not.toHaveBeenCalled() // client should not be constructed\n })\n\n it('should throw FORBIDDEN for locked account and resolve null for LDAP login mismatch', async () => {\n // Phase 1: locked account\n usersManager.findUser.mockResolvedValue({ login: 'john', isGuest: false, isActive: false } as UserModel)\n const loggerErrorSpy1 = jest.spyOn(authMethodLdapService['logger'], 'error').mockImplementation(() => undefined as any)\n await expect(authMethodLdapService.validateUser('john', 'pwd')).rejects.toThrow(/account locked/i)\n expect(loggerErrorSpy1).toHaveBeenCalled()\n\n // Phase 2: mismatch between requested login and LDAP returned login -> service renvoie null\n const existingUser: any = buildUser({ id: 8 })\n usersManager.findUser.mockResolvedValue(existingUser)\n mockBindResolve(ldapClient)\n mockSearchEntries(ldapClient, [{ uid: 'jane', cn: 'john', mail: 'jane@example.org' }])\n await expect(authMethodLdapService.validateUser('john', 'pwd')).rejects.toThrow(/account matching error/i)\n })\n\n it('should handle invalid LDAP credentials for both existing and unknown users', async () => {\n // Phase 1: existing user -> updateAccesses invoked with success=false and logger.error intercepted\n const existingUser: any = buildUser({ id: 1 })\n usersManager.findUser.mockResolvedValue(existingUser)\n // Make LDAP bind throw InvalidCredentialsError\n mockBindRejectInvalid(ldapClient, InvalidCredentialsError, 'invalid credentials')\n // Force updateAccesses to reject to hit the catch and logger.error\n const loggerErrorSpy = jest.spyOn(authMethodLdapService['logger'], 'error').mockImplementation(() => undefined as any)\n usersManager.updateAccesses.mockRejectedValueOnce(new Error('updateAccesses boom'))\n const res1 = await authMethodLdapService.validateUser('john', 'badpwd', '10.0.0.1')\n expect(res1).toBeNull()\n expect(usersManager.updateAccesses).toHaveBeenCalledWith(existingUser, '10.0.0.1', false)\n expect(loggerErrorSpy).toHaveBeenCalled()\n\n // Phase 2: unknown user → no access update\n usersManager.updateAccesses.mockClear()\n usersManager.findUser.mockResolvedValue(null)\n ldapClient.bind.mockRejectedValue(new InvalidCredentialsError('invalid'))\n ldapClient.unbind.mockResolvedValue(undefined)\n const res2 = await authMethodLdapService.validateUser('jane', 'badpwd')\n expect(res2).toBeNull()\n expect(usersManager.updateAccesses).not.toHaveBeenCalled()\n })\n\n it('should handle LDAP new-user flow: missing fields, creation success, and multi-email selection', async () => {\n // Phase 1: incomplete LDAP entry -> null + error log, no creation\n usersManager.findUser.mockResolvedValue(null)\n mockBindResolve(ldapClient)\n // Simulate an entry with missing mail\n mockSearchEntries(ldapClient, [{ uid: 'jane', cn: 'Jane Doe', mail: undefined }])\n const loggerErrorSpy = jest.spyOn(authMethodLdapService['logger'], 'error').mockImplementation(() => undefined as any)\n const resA = await authMethodLdapService.validateUser('jane', 'pwd')\n expect(resA).toBeNull()\n expect(adminUsersManager.createUserOrGuest).not.toHaveBeenCalled()\n expect(loggerErrorSpy).toHaveBeenCalled()\n\n // Phase 2: create a new user (success, single email)\n // Stub directement checkAuth pour retourner une entrée LDAP valide\n const checkAuthSpy = jest.spyOn<any, any>(authMethodLdapService as any, 'checkAuth')\n checkAuthSpy.mockResolvedValueOnce({ uid: 'john', cn: 'John Doe', mail: 'john@example.org' } as any)\n adminUsersManager.createUserOrGuest.mockClear()\n usersManager.findUser.mockResolvedValue(null)\n const createdUser: any = { id: 2, login: 'john', isGuest: false, isActive: true, makePaths: jest.fn() }\n adminUsersManager.createUserOrGuest.mockResolvedValue(createdUser)\n // If the service reloads the user via fromUserId after creation\n usersManager.fromUserId.mockResolvedValue(createdUser)\n // Cover the success-flow catch branch\n const loggerErrorSpy2 = spyLoggerError()\n usersManager.updateAccesses.mockRejectedValueOnce(new Error('updateAccesses success flow boom'))\n const resB = await authMethodLdapService.validateUser('john', 'pwd', '192.168.1.10')\n expect(adminUsersManager.createUserOrGuest).toHaveBeenCalledWith(\n { login: 'john', email: 'john@example.org', password: 'pwd', firstName: 'John', lastName: 'Doe', role: 1 },\n expect.anything() // USER_ROLE.USER\n )\n expect(resB).toBe(createdUser)\n expect(usersManager.updateAccesses).toHaveBeenCalledWith(createdUser, '192.168.1.10', true)\n expect(loggerErrorSpy2).toHaveBeenCalled()\n // Phase 3: multiple emails -> keep the first\n adminUsersManager.createUserOrGuest.mockClear()\n usersManager.findUser.mockResolvedValue(null)\n setupLdapSuccess([{ uid: 'multi', cn: 'Multi Mail', mail: ['first@example.org', 'second@example.org'] }])\n const createdUser2: any = { id: 9, login: 'multi', makePaths: jest.fn() }\n adminUsersManager.createUserOrGuest.mockResolvedValue(createdUser2)\n usersManager.fromUserId.mockResolvedValue(createdUser2)\n const resC = await authMethodLdapService.validateUser('multi', 'pwd')\n expect(adminUsersManager.createUserOrGuest).toHaveBeenCalledWith(expect.objectContaining({ email: 'first@example.org' }), expect.anything())\n expect(resC).toBe(createdUser2)\n })\n\n it('should update existing user profile when LDAP identity changed (except password assigned back)', async () => {\n // Arrange: existing user with different profile and an old password\n const existingUser: any = buildUser({ id: 5 })\n usersManager.findUser.mockResolvedValue(existingUser)\n // LDAP succeeds and returns different email and same uid\n setupLdapSuccess([{ uid: 'john', cn: 'John Doe', mail: 'john@example.org' }])\n // Admin manager successfully updates a user\n adminUsersManager.updateUserOrGuest.mockResolvedValue(undefined)\n // Ensure password is considered changed so the update payload includes it,\n // which then triggers the deletion and local assignment branches after update\n const compareSpy = jest.spyOn(commonFunctions, 'comparePassword').mockResolvedValue(false)\n const res = await authMethodLdapService.validateUser('john', 'new-plain-password', '127.0.0.2')\n expect(adminUsersManager.updateUserOrGuest).toHaveBeenCalledWith(\n 5,\n expect.objectContaining({\n email: 'john@example.org',\n firstName: 'John',\n lastName: 'Doe'\n })\n )\n // Password should not be assigned back onto the user object (it is deleted before Object.assign)\n expect(existingUser.password).toBe('hashed')\n // Other fields should be updated locally\n expect(existingUser.email).toBe('john@example.org')\n expect(existingUser).toMatchObject({ firstName: 'John', lastName: 'Doe' })\n // Accesses updated as success\n expect(usersManager.updateAccesses).toHaveBeenCalledWith(existingUser, '127.0.0.2', true)\n // Returned user is the same instance\n expect(res).toBe(existingUser)\n\n // Second run: password unchanged (comparePassword => true) to cover the null branch for password\n adminUsersManager.updateUserOrGuest.mockClear()\n usersManager.updateAccesses.mockClear()\n // Force another non-password change so an update occurs\n existingUser.email = 'old@example.org'\n compareSpy.mockResolvedValue(true)\n const res2 = await authMethodLdapService.validateUser('john', 'same-plain-password', '127.0.0.3')\n // Update should be called without password, only with changed fields\n expect(adminUsersManager.updateUserOrGuest).toHaveBeenCalled()\n const updateArgs = adminUsersManager.updateUserOrGuest.mock.calls[0]\n expect(updateArgs[0]).toBe(5)\n expect(updateArgs[1]).toEqual(\n expect.objectContaining({\n email: 'john@example.org'\n })\n )\n expect(updateArgs[1]).toEqual(expect.not.objectContaining({ password: expect.anything() }))\n // Password remains unchanged locally\n expect(existingUser.password).toBe('hashed')\n // Accesses updated as success\n expect(usersManager.updateAccesses).toHaveBeenCalledWith(existingUser, '127.0.0.3', true)\n // Returned user is the same instance\n expect(res2).toBe(existingUser)\n // Third run: no changes at all (identityHasChanged is empty) to cover the else branch\n adminUsersManager.updateUserOrGuest.mockClear()\n usersManager.updateAccesses.mockClear()\n compareSpy.mockResolvedValue(true)\n // Local user already matches LDAP identity; call again\n const res3 = await authMethodLdapService.validateUser('john', 'same-plain-password', '127.0.0.4')\n // No update should be triggered\n expect(adminUsersManager.updateUserOrGuest).not.toHaveBeenCalled()\n // Access should still be updated as success\n expect(usersManager.updateAccesses).toHaveBeenCalledWith(existingUser, '127.0.0.4', true)\n // Returned user is the same instance\n expect(res3).toBe(existingUser)\n })\n\n it('should log failed access when LDAP search returns no entry or throws after bind', async () => {\n // Phase 1: no entry found after a successful bind -> failed access\n const existingUser: any = { id: 7, login: 'ghost', isGuest: false, isActive: true }\n usersManager.findUser.mockResolvedValue(existingUser)\n setupLdapSuccess([])\n const resA = await authMethodLdapService.validateUser('ghost', 'pwd', '10.10.0.1')\n expect(resA).toBeNull()\n expect(usersManager.updateAccesses).toHaveBeenCalledWith(existingUser, '10.10.0.1', false)\n\n // Phase 2: exception during search after a bind -> failed access\n jest.clearAllMocks()\n const existingUser2: any = { id: 10, login: 'john', isGuest: false, isActive: true }\n usersManager.findUser.mockResolvedValue(existingUser2)\n mockBindResolve(ldapClient)\n mockSearchReject(ldapClient, new Error('search failed'))\n const resB = await authMethodLdapService.validateUser('john', 'pwd', '1.1.1.1')\n expect(resB).toBeNull()\n expect(usersManager.updateAccesses).toHaveBeenCalledWith(existingUser2, '1.1.1.1', false)\n })\n\n it('should allow app password when LDAP fails and scope is provided', async () => {\n const existingUser: any = buildUser({ id: 42 })\n usersManager.findUser.mockResolvedValue(existingUser)\n // LDAP invalid credentials\n mockBindRejectInvalid(ldapClient, InvalidCredentialsError, 'invalid credentials')\n // App password success\n usersManager.validateAppPassword.mockResolvedValue(true)\n const res = await authMethodLdapService.validateUser('john', 'app-password', '10.0.0.2', 'webdav' as any)\n expect(res).toBe(existingUser)\n expect(usersManager.validateAppPassword).toHaveBeenCalledWith(existingUser, 'app-password', '10.0.0.2', 'webdav')\n expect(usersManager.updateAccesses).toHaveBeenCalledWith(existingUser, '10.0.0.2', true)\n })\n\n it('should throw 500 when LDAP connection error occurs during bind', async () => {\n // Arrange: no existing user to reach checkAuth flow\n usersManager.findUser.mockResolvedValue(null)\n const err1 = new Error('socket hang up')\n const err2 = Object.assign(new Error('connect ECONNREFUSED'), { code: Array.from(CONNECT_ERROR_CODE)[0] })\n ldapClient.bind.mockRejectedValue({ errors: [err1, err2] })\n ldapClient.unbind.mockResolvedValue(undefined)\n\n // First scenario: recognized connection error -> throws 500\n await expect(authMethodLdapService.validateUser('john', 'pwd')).rejects.toThrow(/authentication service/i)\n\n // Second scenario: generic error (no code, not InvalidCredentialsError) -> resolves to null and no access update\n ldapClient.bind.mockReset()\n ldapClient.unbind.mockReset()\n usersManager.updateAccesses.mockClear()\n usersManager.findUser.mockResolvedValue(null as any)\n ldapClient.bind.mockRejectedValue(new Error('unexpected failure'))\n ldapClient.unbind.mockResolvedValue(undefined)\n\n const res = await authMethodLdapService.validateUser('john', 'pwd')\n expect(res).toBeNull()\n expect(usersManager.updateAccesses).not.toHaveBeenCalled()\n })\n\n it('should log update failure when updating existing user', async () => {\n // Arrange: existing user with changed identity\n const existingUser: any = buildUser({ id: 11, email: 'old@ex.org' })\n usersManager.findUser.mockResolvedValue(existingUser)\n // Ensure LDAP loginAttribute matches uid for this test (a previous test sets it to 'cn')\n setupLdapSuccess([{ uid: 'john', cn: 'John Doe', mail: 'john@example.org' }])\n adminUsersManager.updateUserOrGuest.mockRejectedValue(new Error('db error'))\n // Force identity to be considered changed only for this test\n jest.spyOn(commonFunctions, 'comparePassword').mockResolvedValue(false)\n jest.spyOn(commonFunctions, 'splitFullName').mockReturnValue({ firstName: 'John', lastName: 'Doe' })\n const res = await authMethodLdapService.validateUser('john', 'pwd')\n expect(adminUsersManager.updateUserOrGuest).toHaveBeenCalled()\n // Local fields unchanged since update failed\n expect(existingUser.email).toBe('old@ex.org')\n expect(res).toBe(existingUser)\n })\n\n it('should skip non-matching LDAP entries then update user with changed password without reassigning it', async () => {\n // Phase A: LDAP returns an entry but loginAttribute value does not match -> checkAccess returns false (covers return after loop)\n const userA: any = { id: 20, login: 'john', isGuest: false, isActive: true }\n usersManager.findUser.mockResolvedValue(userA)\n ldapClient.bind.mockResolvedValue(undefined)\n\n // Phase B: Matching entry + password considered changed -> updateUserOrGuest called, password not reassigned locally\n jest.clearAllMocks()\n const userB: any = buildUser({ id: 21, email: 'old@ex.org' })\n usersManager.findUser.mockResolvedValue(userB)\n setupLdapSuccess([{ uid: 'john', cn: 'John Doe', mail: 'john@example.org' }])\n adminUsersManager.updateUserOrGuest.mockResolvedValue(undefined)\n\n // Force password to be considered changed to execute deletion + Object.assign branch\n jest.spyOn(commonFunctions, 'comparePassword').mockResolvedValue(false)\n jest.spyOn(commonFunctions, 'splitFullName').mockReturnValue({ firstName: 'John', lastName: 'Doe' })\n const resB = await authMethodLdapService.validateUser('john', 'newpwd', '4.4.4.4')\n\n // Line 132: updateUserOrGuest call\n expect(adminUsersManager.updateUserOrGuest).toHaveBeenCalledWith(\n 21,\n expect.objectContaining({ email: 'john@example.org', firstName: 'John', lastName: 'Doe' })\n )\n\n // Lines 139-142: password removed from local assign, other fields assigned\n expect(userB.password).toBe('hashed')\n expect(userB.email).toBe('john@example.org')\n expect(userB).toMatchObject({ firstName: 'John', lastName: 'Doe' })\n expect(resB).toBe(userB)\n })\n})\n"],"names":["jest","mock","actual","requireActual","mockClientInstance","bind","fn","search","unbind","Client","mockImplementation","mockBindResolve","ldapClient","mockResolvedValue","undefined","mockBindRejectInvalid","InvalidCredentialsErrorCtor","message","mockRejectedValue","mockSearchEntries","entries","searchEntries","mockSearchReject","err","buildUser","overrides","id","login","email","password","isGuest","isActive","makePaths","setFullName","describe","AuthMethodLdapService","name","authMethodLdapService","usersManager","adminUsersManager","setupLdapSuccess","spyLoggerError","spyOn","beforeAll","configuration","auth","ldap","servers","attributes","LDAP_LOGIN_ATTR","UID","baseDN","filter","module","Test","createTestingModule","providers","provide","UsersManager","useValue","findUser","logUser","updateAccesses","validateAppPassword","fromUserId","AdminUsersManager","createUserOrGuest","updateUserOrGuest","compile","useLogger","get","it","expect","toBeDefined","guestUser","dbAuthResult","token","res","validateUser","toEqual","toHaveBeenCalledWith","not","toHaveBeenCalled","loggerErrorSpy1","rejects","toThrow","existingUser","uid","cn","mail","InvalidCredentialsError","loggerErrorSpy","mockRejectedValueOnce","Error","res1","toBeNull","mockClear","res2","resA","checkAuthSpy","mockResolvedValueOnce","createdUser","loggerErrorSpy2","resB","firstName","lastName","role","anything","toBe","createdUser2","resC","objectContaining","compareSpy","commonFunctions","toMatchObject","updateArgs","calls","res3","clearAllMocks","existingUser2","err1","err2","Object","assign","code","Array","from","CONNECT_ERROR_CODE","errors","mockReset","mockReturnValue","userA","userB"],"mappings":"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"}