@sync-in/server 2.0.0 → 2.1.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 (232) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/README.md +38 -28
  3. package/environment/environment.dist.yaml +4 -0
  4. package/package.json +17 -17
  5. package/server/applications/files/utils/doc-textify/adapters/pdf.js +1 -1
  6. package/server/applications/files/utils/doc-textify/adapters/pdf.js.map +1 -1
  7. package/server/applications/files/utils/files.js +37 -24
  8. package/server/applications/files/utils/files.js.map +1 -1
  9. package/server/applications/notifications/i18n/index.js +5 -0
  10. package/server/applications/notifications/i18n/index.js.map +1 -1
  11. package/server/applications/notifications/i18n/nl.js +54 -0
  12. package/server/applications/notifications/i18n/nl.js.map +1 -0
  13. package/server/applications/sync/services/sync-clients-manager.service.js +1 -1
  14. package/server/applications/sync/services/sync-clients-manager.service.js.map +1 -1
  15. package/server/authentication/providers/oidc/auth-oidc.config.js +5 -0
  16. package/server/authentication/providers/oidc/auth-oidc.config.js.map +1 -1
  17. package/server/authentication/providers/oidc/auth-provider-oidc.service.js +10 -7
  18. package/server/authentication/providers/oidc/auth-provider-oidc.service.js.map +1 -1
  19. package/server/authentication/providers/oidc/auth-provider-oidc.service.spec.js +16 -0
  20. package/server/authentication/providers/oidc/auth-provider-oidc.service.spec.js.map +1 -1
  21. package/server/common/i18n.js +1 -0
  22. package/server/common/i18n.js.map +1 -1
  23. package/server/infrastructure/database/database.module.js +7 -0
  24. package/server/infrastructure/database/database.module.js.map +1 -1
  25. package/server/infrastructure/database/scripts/check-db.js +20 -0
  26. package/server/infrastructure/database/scripts/check-db.js.map +1 -0
  27. package/static/3rdpartylicenses.txt +391 -391
  28. package/static/assets/pdfjs/build/pdf.mjs +512 -417
  29. package/static/assets/pdfjs/build/pdf.mjs.map +1 -1
  30. package/static/assets/pdfjs/build/pdf.sandbox.mjs +390 -4
  31. package/static/assets/pdfjs/build/pdf.sandbox.mjs.map +1 -1
  32. package/static/assets/pdfjs/build/pdf.worker.mjs +2378 -401
  33. package/static/assets/pdfjs/build/pdf.worker.mjs.map +1 -1
  34. package/static/assets/pdfjs/version +1 -1
  35. package/static/assets/pdfjs/web/debugger.mjs +6 -15
  36. package/static/assets/pdfjs/web/locale/be/viewer.ftl +78 -0
  37. package/static/assets/pdfjs/web/locale/cs/viewer.ftl +82 -0
  38. package/static/assets/pdfjs/web/locale/cy/viewer.ftl +90 -0
  39. package/static/assets/pdfjs/web/locale/de/viewer.ftl +75 -0
  40. package/static/assets/pdfjs/web/locale/dsb/viewer.ftl +82 -0
  41. package/static/assets/pdfjs/web/locale/el/viewer.ftl +74 -0
  42. package/static/assets/pdfjs/web/locale/en-CA/viewer.ftl +74 -0
  43. package/static/assets/pdfjs/web/locale/en-GB/viewer.ftl +81 -0
  44. package/static/assets/pdfjs/web/locale/en-US/viewer.ftl +14 -14
  45. package/static/assets/pdfjs/web/locale/eo/viewer.ftl +74 -0
  46. package/static/assets/pdfjs/web/locale/es-AR/viewer.ftl +74 -0
  47. package/static/assets/pdfjs/web/locale/es-CL/viewer.ftl +74 -0
  48. package/static/assets/pdfjs/web/locale/es-ES/viewer.ftl +74 -0
  49. package/static/assets/pdfjs/web/locale/es-MX/viewer.ftl +75 -0
  50. package/static/assets/pdfjs/web/locale/eu/viewer.ftl +74 -0
  51. package/static/assets/pdfjs/web/locale/fi/viewer.ftl +74 -0
  52. package/static/assets/pdfjs/web/locale/fr/viewer.ftl +81 -0
  53. package/static/assets/pdfjs/web/locale/fy-NL/viewer.ftl +74 -0
  54. package/static/assets/pdfjs/web/locale/gn/viewer.ftl +77 -0
  55. package/static/assets/pdfjs/web/locale/he/viewer.ftl +81 -0
  56. package/static/assets/pdfjs/web/locale/hsb/viewer.ftl +87 -0
  57. package/static/assets/pdfjs/web/locale/hu/viewer.ftl +74 -0
  58. package/static/assets/pdfjs/web/locale/hy-AM/viewer.ftl +10 -10
  59. package/static/assets/pdfjs/web/locale/ia/viewer.ftl +75 -0
  60. package/static/assets/pdfjs/web/locale/it/viewer.ftl +81 -0
  61. package/static/assets/pdfjs/web/locale/ja/viewer.ftl +58 -0
  62. package/static/assets/pdfjs/web/locale/ka/viewer.ftl +74 -0
  63. package/static/assets/pdfjs/web/locale/kk/viewer.ftl +81 -0
  64. package/static/assets/pdfjs/web/locale/km/viewer.ftl +44 -0
  65. package/static/assets/pdfjs/web/locale/ko/viewer.ftl +65 -0
  66. package/static/assets/pdfjs/web/locale/nb-NO/viewer.ftl +74 -0
  67. package/static/assets/pdfjs/web/locale/nl/viewer.ftl +81 -0
  68. package/static/assets/pdfjs/web/locale/nn-NO/viewer.ftl +75 -0
  69. package/static/assets/pdfjs/web/locale/pa-IN/viewer.ftl +74 -0
  70. package/static/assets/pdfjs/web/locale/pl/viewer.ftl +78 -0
  71. package/static/assets/pdfjs/web/locale/pt-BR/viewer.ftl +74 -0
  72. package/static/assets/pdfjs/web/locale/ro/viewer.ftl +81 -3
  73. package/static/assets/pdfjs/web/locale/ru/viewer.ftl +85 -0
  74. package/static/assets/pdfjs/web/locale/sk/viewer.ftl +83 -1
  75. package/static/assets/pdfjs/web/locale/sl/viewer.ftl +82 -0
  76. package/static/assets/pdfjs/web/locale/sv-SE/viewer.ftl +81 -0
  77. package/static/assets/pdfjs/web/locale/tg/viewer.ftl +74 -0
  78. package/static/assets/pdfjs/web/locale/th/viewer.ftl +58 -0
  79. package/static/assets/pdfjs/web/locale/tr/viewer.ftl +74 -0
  80. package/static/assets/pdfjs/web/locale/uk/viewer.ftl +144 -0
  81. package/static/assets/pdfjs/web/locale/vi/viewer.ftl +65 -0
  82. package/static/assets/pdfjs/web/locale/zh-CN/viewer.ftl +61 -3
  83. package/static/assets/pdfjs/web/locale/zh-TW/viewer.ftl +65 -0
  84. package/static/assets/pdfjs/web/viewer.css +292 -152
  85. package/static/assets/pdfjs/web/viewer.html +14 -21
  86. package/static/assets/pdfjs/web/viewer.mjs +782 -327
  87. package/static/assets/pdfjs/web/viewer.mjs.map +1 -1
  88. package/static/assets/pdfjs/web/wasm/jbig2.wasm +0 -0
  89. package/static/{chunk-7WOPGQXB.js → chunk-25XD7GNL.js} +1 -1
  90. package/static/{chunk-E5WI5725.js → chunk-2W4Z5VTY.js} +1 -1
  91. package/static/chunk-2XNPCG4F.js +1 -0
  92. package/static/chunk-3B2RHO2H.js +1 -0
  93. package/static/{chunk-UMDRE4S7.js → chunk-3DNAFXGN.js} +1 -1
  94. package/static/{chunk-OANZITPM.js → chunk-3EPN4WD7.js} +1 -1
  95. package/static/chunk-4L2WBHB6.js +1 -0
  96. package/static/chunk-4RYZNAIZ.js +1 -0
  97. package/static/chunk-5C77PEFA.js +1 -0
  98. package/static/chunk-5JA3DZWQ.js +1 -0
  99. package/static/chunk-5MSTJGPE.js +1 -0
  100. package/static/{chunk-VSBFNFOM.js → chunk-5O7KTREI.js} +1 -1
  101. package/static/chunk-6KORLSUZ.js +1 -0
  102. package/static/chunk-6LIHQVPW.js +2 -0
  103. package/static/{chunk-2CKLZ3FM.js → chunk-72BXGTUG.js} +1 -1
  104. package/static/chunk-7423QJQN.js +1 -0
  105. package/static/chunk-77HB4DE6.js +1 -0
  106. package/static/{chunk-RWAAC3A4.js → chunk-7PVTMTWH.js} +1 -1
  107. package/static/{chunk-IWWBV6EM.js → chunk-7UIOW5AD.js} +1 -1
  108. package/static/chunk-7YMONFZZ.js +1 -0
  109. package/static/chunk-AUPNSQGJ.js +1 -0
  110. package/static/chunk-AYF6ZI6L.js +5 -0
  111. package/static/chunk-BEESW4BF.js +1 -0
  112. package/static/{chunk-2R6IBBPZ.js → chunk-BI3VI6XG.js} +1 -1
  113. package/static/chunk-BMGKB3JK.js +4 -0
  114. package/static/chunk-CFXAKPPB.js +563 -0
  115. package/static/chunk-CGYZG6CB.js +2 -0
  116. package/static/{chunk-K25E7YGG.js → chunk-CHAYERHQ.js} +1 -1
  117. package/static/chunk-CLNKPQMZ.js +1 -0
  118. package/static/{chunk-ISV3BO6R.js → chunk-CVLSRA2W.js} +1 -1
  119. package/static/chunk-D373HBMN.js +1 -0
  120. package/static/{chunk-MTRXBVWZ.js → chunk-D43PHZEY.js} +1 -1
  121. package/static/chunk-D44RAVFK.js +1 -0
  122. package/static/chunk-DFVVEGWC.js +1 -0
  123. package/static/chunk-E43LXI27.js +1 -0
  124. package/static/{chunk-GQFMWVFD.js → chunk-E5M4ZDID.js} +1 -1
  125. package/static/chunk-F7JS3YWP.js +2 -0
  126. package/static/chunk-FRWEEOOM.js +1 -0
  127. package/static/chunk-HIZGSP7R.js +1 -0
  128. package/static/chunk-HX6ZWXQD.js +1 -0
  129. package/static/chunk-HZ6LLXLE.js +1 -0
  130. package/static/{chunk-RLL634K4.js → chunk-IFHFPV5W.js} +1 -1
  131. package/static/{chunk-KJD3KFF3.js → chunk-JICKUOQ6.js} +1 -1
  132. package/static/chunk-KY2BW2LP.js +1 -0
  133. package/static/{chunk-CWYHOPOP.js → chunk-KYA3HPDX.js} +1 -1
  134. package/static/{chunk-RWCNTCU5.js → chunk-KYLATNX5.js} +1 -1
  135. package/static/chunk-LCZNSV4Z.js +1 -0
  136. package/static/{chunk-GVNTC564.js → chunk-MA6DYTMQ.js} +1 -1
  137. package/static/{chunk-KZS7CTNR.js → chunk-MXH5XYBD.js} +1 -1
  138. package/static/{chunk-NMTBMHUL.js → chunk-MXIJGSDQ.js} +1 -1
  139. package/static/{chunk-RS2OFKWP.js → chunk-NE75E3GD.js} +1 -1
  140. package/static/{chunk-MZQK6LNV.js → chunk-NJKIHMOH.js} +1 -1
  141. package/static/chunk-NMDGST45.js +1 -0
  142. package/static/{chunk-CU76ATCF.js → chunk-OF7P4LTI.js} +1 -1
  143. package/static/chunk-OK5K5CYJ.js +13 -0
  144. package/static/chunk-OZ6LXMUH.js +1 -0
  145. package/static/chunk-PFY6YUPS.js +2 -0
  146. package/static/chunk-PQ2YFCVM.js +1 -0
  147. package/static/{chunk-Y4AUYQTG.js → chunk-QPX34S6P.js} +4 -4
  148. package/static/{chunk-27ATUHBH.js → chunk-QQO7S2VY.js} +1 -1
  149. package/static/chunk-RANZZPRG.js +1 -0
  150. package/static/{chunk-WN4WXCVK.js → chunk-RNQB2QJB.js} +1 -1
  151. package/static/{chunk-Q3EGCMF5.js → chunk-S7QYAKG5.js} +1 -1
  152. package/static/chunk-SCEWOW5C.js +2 -0
  153. package/static/chunk-SQ2KIOJ7.js +1 -0
  154. package/static/{chunk-QVRVFYJH.js → chunk-SW5GCGOI.js} +1 -1
  155. package/static/chunk-SWCYWB2T.js +1 -0
  156. package/static/{chunk-4FIGEBNL.js → chunk-SWZ3L2LK.js} +1 -1
  157. package/static/{chunk-TWCGKSYE.js → chunk-TDK62XEQ.js} +1 -1
  158. package/static/chunk-TH4L5H6P.js +1 -0
  159. package/static/chunk-U3ZGYOLA.js +1 -0
  160. package/static/chunk-U6V4Y234.js +1 -0
  161. package/static/chunk-U7KNV64D.js +2 -0
  162. package/static/chunk-UC66HJV4.js +1 -0
  163. package/static/{chunk-7GWW6MJO.js → chunk-V4HAIERK.js} +1 -1
  164. package/static/chunk-V55LDRV7.js +1 -0
  165. package/static/{chunk-5CZOSAMZ.js → chunk-WF6D7CQ4.js} +1 -1
  166. package/static/chunk-WJPVHM7T.js +1 -0
  167. package/static/chunk-WM4XBDC2.js +1 -0
  168. package/static/chunk-X2EKQYMZ.js +1 -0
  169. package/static/{chunk-L5IHUVXL.js → chunk-XEDABMM5.js} +1 -1
  170. package/static/{chunk-OGE4SAHU.js → chunk-Y3YKVK3K.js} +1 -1
  171. package/static/{chunk-QIGUDEZF.js → chunk-YEOAMYRK.js} +1 -1
  172. package/static/{chunk-A4UGPSWX.js → chunk-ZLTL7I7D.js} +1 -1
  173. package/static/chunk-ZYNBYM7I.js +3 -0
  174. package/static/index.html +2 -2
  175. package/static/main-KFUSRCP5.js +5 -0
  176. package/static/media/inter-cyrillic-ext-wght-normal-IYF56FF6.woff2 +0 -0
  177. package/static/media/inter-cyrillic-wght-normal-JEOLYBOO.woff2 +0 -0
  178. package/static/media/inter-greek-ext-wght-normal-EOVOK2B5.woff2 +0 -0
  179. package/static/media/inter-greek-wght-normal-IRE366VL.woff2 +0 -0
  180. package/static/media/inter-latin-ext-wght-normal-HA22NDSG.woff2 +0 -0
  181. package/static/media/inter-latin-wght-normal-NRMW37G5.woff2 +0 -0
  182. package/static/media/inter-vietnamese-wght-normal-CE5GGD3W.woff2 +0 -0
  183. package/static/styles-66AEF62D.css +1 -0
  184. package/static/chunk-22TZP6HW.js +0 -1
  185. package/static/chunk-2QZPX7LO.js +0 -1
  186. package/static/chunk-4P3JABAP.js +0 -13
  187. package/static/chunk-677WUBCT.js +0 -1
  188. package/static/chunk-74CAHBFM.js +0 -1
  189. package/static/chunk-AHO37FKW.js +0 -1
  190. package/static/chunk-AQCXMKP3.js +0 -1
  191. package/static/chunk-B6PDYCRO.js +0 -3
  192. package/static/chunk-FC5HTKVM.js +0 -1
  193. package/static/chunk-FOSM7EYI.js +0 -1
  194. package/static/chunk-GAZO25PI.js +0 -1
  195. package/static/chunk-GB7ABR5N.js +0 -1
  196. package/static/chunk-GEHFKZQ5.js +0 -2
  197. package/static/chunk-HGL3NYP2.js +0 -2
  198. package/static/chunk-HLIWPWRA.js +0 -1
  199. package/static/chunk-HNYB3M4S.js +0 -1
  200. package/static/chunk-HUXAUQMN.js +0 -1
  201. package/static/chunk-I2XA6PPK.js +0 -1
  202. package/static/chunk-JV3AGU5B.js +0 -1
  203. package/static/chunk-K46PUTZB.js +0 -1
  204. package/static/chunk-KERFLJ56.js +0 -1
  205. package/static/chunk-KPKSI23S.js +0 -1
  206. package/static/chunk-L7RRX2M3.js +0 -1
  207. package/static/chunk-LGWJ2WKU.js +0 -1
  208. package/static/chunk-LUSVISM6.js +0 -1
  209. package/static/chunk-MLC7JK2H.js +0 -2
  210. package/static/chunk-MOHNYW2A.js +0 -1
  211. package/static/chunk-NCDUOVMW.js +0 -1
  212. package/static/chunk-NGUAJIGI.js +0 -1
  213. package/static/chunk-NIPP6JDI.js +0 -1
  214. package/static/chunk-O4XXMZFX.js +0 -4
  215. package/static/chunk-OI3ME22C.js +0 -1
  216. package/static/chunk-QF2NSHZA.js +0 -1
  217. package/static/chunk-QJVC3SRJ.js +0 -562
  218. package/static/chunk-QKN6LAAA.js +0 -1
  219. package/static/chunk-QRFESU5O.js +0 -2
  220. package/static/chunk-RFJIPIOK.js +0 -2
  221. package/static/chunk-S5Y64DDS.js +0 -1
  222. package/static/chunk-SLG5KDU6.js +0 -1
  223. package/static/chunk-TJ4CVFEL.js +0 -1
  224. package/static/chunk-VRIOLRYR.js +0 -5
  225. package/static/chunk-VS4O2XDP.js +0 -1
  226. package/static/chunk-WX7RXW7K.js +0 -1
  227. package/static/chunk-XC4POKR3.js +0 -2
  228. package/static/chunk-Y67J3BOL.js +0 -1
  229. package/static/chunk-YMIXHRJQ.js +0 -1
  230. package/static/chunk-ZUNKFAKP.js +0 -1
  231. package/static/main-QN4UCOC5.js +0 -5
  232. package/static/styles-46GLIE7Y.css +0 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../backend/src/applications/sync/services/sync-clients-manager.service.ts"],"sourcesContent":["import { HttpService } from '@nestjs/axios'\nimport { HttpException, HttpStatus, Injectable, Logger } from '@nestjs/common'\nimport { AxiosResponse } from 'axios'\nimport { FastifyReply } from 'fastify'\nimport crypto from 'node:crypto'\nimport fs from 'node:fs/promises'\nimport path from 'node:path'\nimport { AuthManager } from '../../../authentication/auth.service'\nimport { AUTH_SCOPE } from '../../../authentication/constants/scope'\nimport { FastifyAuthenticatedRequest } from '../../../authentication/interfaces/auth-request.interface'\nimport { AuthProvider } from '../../../authentication/providers/auth-providers.models'\nimport { AuthProvider2FA } from '../../../authentication/providers/two-fa/auth-provider-two-fa.service'\nimport { convertHumanTimeToSeconds } from '../../../common/functions'\nimport { currentTimeStamp, RELEASES_URL } from '../../../common/shared'\nimport { STATIC_PATH } from '../../../configuration/config.constants'\nimport { configuration } from '../../../configuration/config.environment'\nimport { CacheDecorator } from '../../../infrastructure/cache/cache.decorator'\nimport { HTTP_METHOD } from '../../applications.constants'\nimport { isPathExists } from '../../files/utils/files'\nimport { USER_PERMISSION } from '../../users/constants/user'\nimport { UserModel } from '../../users/models/user.model'\nimport { UsersManager } from '../../users/services/users-manager.service'\nimport { CLIENT_AUTH_TYPE, CLIENT_TOKEN_EXPIRATION_TIME, CLIENT_TOKEN_EXPIRED_ERROR, CLIENT_TOKEN_RENEW_TIME } from '../constants/auth'\nimport { APP_STORE_DIRNAME, APP_STORE_MANIFEST_FILE, APP_STORE_REPOSITORY } from '../constants/store'\nimport { SYNC_CLIENT_TYPE } from '../constants/sync'\nimport type { SyncClientAuthDto } from '../dtos/sync-client-auth.dto'\nimport { SyncClientAuthRegistrationDto, SyncClientRegistrationDto } from '../dtos/sync-client-registration.dto'\nimport { AppStoreManifest } from '../interfaces/store-manifest.interface'\nimport { SyncClientAuthCookie, SyncClientAuthRegistration, SyncClientAuthToken } from '../interfaces/sync-client-auth.interface'\nimport { SyncClientPaths } from '../interfaces/sync-client-paths.interface'\nimport { SyncClientInfo } from '../interfaces/sync-client.interface'\nimport { SyncClient } from '../schemas/sync-client.interface'\nimport { SyncQueries } from './sync-queries.service'\n\n@Injectable()\nexport class SyncClientsManager {\n private readonly logger = new Logger(SyncClientsManager.name)\n\n constructor(\n private readonly http: HttpService,\n private readonly authManager: AuthManager,\n private readonly authProvider: AuthProvider,\n private readonly authProvider2FA: AuthProvider2FA,\n private readonly usersManager: UsersManager,\n private readonly syncQueries: SyncQueries\n ) {}\n\n async register(clientRegistrationDto: SyncClientRegistrationDto, ip: string): Promise<SyncClientAuthRegistration> {\n const user: UserModel = await this.authProvider.validateUser(clientRegistrationDto.login, clientRegistrationDto.password, ip, AUTH_SCOPE.CLIENT)\n if (!user) {\n this.logger.warn({ tag: this.register.name, msg: `auth failed for user *${clientRegistrationDto.login}*` })\n throw new HttpException('Wrong login or password', HttpStatus.UNAUTHORIZED)\n }\n if (!user.havePermission(USER_PERMISSION.DESKTOP_APP)) {\n this.logger.warn({\n tag: this.register.name,\n msg: `user *${user.login}* (${user.id}) does not have permission : ${USER_PERMISSION.DESKTOP_APP}`\n })\n throw new HttpException('Missing permission', HttpStatus.FORBIDDEN)\n }\n if (configuration.auth.mfa.totp.enabled && user.twoFaEnabled) {\n // Checking TOTP code and recovery code\n if (!clientRegistrationDto.code) {\n this.logger.warn({ tag: this.register.name, msg: `missing two-fa code for user *${user.login}* (${user.id})` })\n throw new HttpException('Missing TWO-FA code', HttpStatus.UNAUTHORIZED)\n }\n const authCode = this.authProvider2FA.validateTwoFactorCode(clientRegistrationDto.code, user.secrets.twoFaSecret)\n if (!authCode.success) {\n this.logger.warn({ tag: this.register.name, msg: `two-fa code for *${user.login}* (${user.id}) - ${authCode.message}` })\n const authRCode = await this.authProvider2FA.validateRecoveryCode(user.id, clientRegistrationDto.code, user.secrets.recoveryCodes)\n if (!authRCode.success) {\n this.logger.warn({ tag: this.register.name, msg: `two-fa recovery code for *${user.login}* (${user.id}) - ${authRCode.message}` })\n this.usersManager.updateAccesses(user, ip, false).catch((e: Error) => this.logger.error({ tag: this.register.name, msg: `${e}` }))\n throw new HttpException(authCode.message, HttpStatus.UNAUTHORIZED)\n }\n }\n }\n return this.getOrCreateClient(user, clientRegistrationDto.clientId, clientRegistrationDto.info, ip)\n }\n\n async registerWithAuth(\n clientAuthenticatedRegistrationDto: SyncClientAuthRegistrationDto,\n req: FastifyAuthenticatedRequest\n ): Promise<SyncClientAuthRegistration> {\n const clientId = clientAuthenticatedRegistrationDto.clientId || crypto.randomUUID()\n return this.getOrCreateClient(req.user, clientId, clientAuthenticatedRegistrationDto.info, req.ip)\n }\n\n async unregister(user: UserModel): Promise<void> {\n try {\n await this.syncQueries.deleteClient(user.id, user.clientId)\n } catch (e) {\n this.logger.error({ tag: this.unregister.name, msg: `${e}` })\n throw new HttpException('Error during the removing of client registration', HttpStatus.INTERNAL_SERVER_ERROR)\n }\n }\n\n async authenticate(\n authType: CLIENT_AUTH_TYPE,\n syncClientAuthDto: SyncClientAuthDto,\n ip: string,\n res: FastifyReply\n ): Promise<SyncClientAuthToken | SyncClientAuthCookie> {\n const client = await this.syncQueries.getClient(syncClientAuthDto.clientId, null, syncClientAuthDto.token)\n if (!client) {\n throw new HttpException('Client is unknown', HttpStatus.FORBIDDEN)\n }\n if (!client.enabled) {\n throw new HttpException('Client is disabled', HttpStatus.FORBIDDEN)\n }\n if (currentTimeStamp() >= client.tokenExpiration) {\n throw new HttpException(CLIENT_TOKEN_EXPIRED_ERROR, HttpStatus.FORBIDDEN)\n }\n this.syncQueries.updateClientInfo(client, client.info, ip).catch((e: Error) => this.logger.error({ tag: this.authenticate.name, msg: `${e}` }))\n const user: UserModel = await this.usersManager.fromUserId(client.ownerId)\n if (!user) {\n throw new HttpException('User does not exist', HttpStatus.FORBIDDEN)\n }\n if (!user.isActive) {\n throw new HttpException('Account suspended or not authorized', HttpStatus.FORBIDDEN)\n }\n if (!user.havePermission(USER_PERMISSION.DESKTOP_APP)) {\n this.logger.warn({ tag: this.authenticate.name, msg: `does not have permission : ${USER_PERMISSION.DESKTOP_APP}` })\n throw new HttpException('Missing permission', HttpStatus.FORBIDDEN)\n }\n // set clientId\n user.clientId = client.id\n // update accesses\n this.usersManager.updateAccesses(user, ip, true).catch((e: Error) => this.logger.error({ tag: this.authenticate.name, msg: `${e}` }))\n let r: SyncClientAuthToken | SyncClientAuthCookie\n if (authType === CLIENT_AUTH_TYPE.COOKIE) {\n // used by the desktop app to perform the login setup using cookies\n r = await this.authManager.setCookies(user, res)\n } else if (authType === CLIENT_AUTH_TYPE.TOKEN) {\n // used by the cli app and the sync core\n r = await this.authManager.getTokens(user)\n }\n // check if the client token must be updated\n r.client_token_update = await this.renewTokenAndExpiration(client, user)\n return r\n }\n\n getClients(user: UserModel): Promise<SyncClientPaths[]> {\n return this.syncQueries.getClients(user)\n }\n\n async renewTokenAndExpiration(client: SyncClient, owner: UserModel): Promise<string | undefined> {\n if (currentTimeStamp() + convertHumanTimeToSeconds(CLIENT_TOKEN_RENEW_TIME) < client.tokenExpiration) {\n // client token expiration is not close enough\n return undefined\n }\n const token = crypto.randomUUID()\n const expiration = currentTimeStamp() + convertHumanTimeToSeconds(CLIENT_TOKEN_EXPIRATION_TIME)\n this.logger.log({ tag: this.renewTokenAndExpiration.name, msg: `renew token for user *${owner.login}* and client *${client.id}*` })\n try {\n await this.syncQueries.renewClientTokenAndExpiration(client.id, token, expiration)\n } catch (e) {\n this.logger.error({\n tag: this.renewTokenAndExpiration.name,\n msg: `unable to renew token for user *${owner.login}* and client *${client.id}* : ${e}`\n })\n throw new HttpException('Unable to update client token', HttpStatus.BAD_REQUEST)\n }\n return token\n }\n\n async deleteClient(user: UserModel, clientId: string): Promise<void> {\n try {\n await this.syncQueries.deleteClient(user.id, clientId)\n } catch (e) {\n this.logger.error({ tag: this.deleteClient.name, msg: `${e}` })\n throw new HttpException('Unable to delete client', HttpStatus.INTERNAL_SERVER_ERROR)\n }\n }\n\n @CacheDecorator(3600)\n async checkAppStore(): Promise<AppStoreManifest> {\n let manifest: AppStoreManifest = null\n if (configuration.applications.appStore.repository === APP_STORE_REPOSITORY.PUBLIC) {\n const url = `${RELEASES_URL}/${APP_STORE_MANIFEST_FILE}`\n try {\n const res: AxiosResponse = await this.http.axiosRef({\n method: HTTP_METHOD.GET,\n url: url\n })\n manifest = res.data\n manifest.repository = APP_STORE_REPOSITORY.PUBLIC\n } catch (e) {\n this.logger.warn({ tag: this.checkAppStore.name, msg: `unable to retrieve ${url} : ${e}` })\n }\n } else {\n const latestFile = path.join(STATIC_PATH, APP_STORE_DIRNAME, APP_STORE_MANIFEST_FILE)\n if (!(await isPathExists(latestFile))) {\n this.logger.warn({ tag: this.checkAppStore.name, msg: `${latestFile} does not exist` })\n } else {\n try {\n manifest = JSON.parse(await fs.readFile(latestFile, 'utf8'))\n manifest.repository = APP_STORE_REPOSITORY.LOCAL\n // rewrite urls to local repository\n for (const [os, packages] of Object.entries(manifest.platform)) {\n for (const p of packages) {\n if (p.package.toLowerCase().startsWith(SYNC_CLIENT_TYPE.DESKTOP)) {\n p.url = `${APP_STORE_DIRNAME}/${SYNC_CLIENT_TYPE.DESKTOP}/${os}/${p.package}`\n } else {\n p.url = `${APP_STORE_DIRNAME}/${SYNC_CLIENT_TYPE.CLI}/${p.package}`\n }\n }\n }\n } catch (e) {\n this.logger.error({ tag: this.checkAppStore.name, msg: `${latestFile} : ${e}` })\n }\n }\n }\n return manifest\n }\n\n private async getOrCreateClient(user: UserModel, clientId: string, clientInfo: SyncClientInfo, ip: string): Promise<SyncClientAuthRegistration> {\n try {\n const token = await this.syncQueries.getOrCreateClient(user.id, clientId, clientInfo, ip)\n this.logger.log({ tag: this.getOrCreateClient.name, msg: `client *${clientInfo.type}* was registered for user *${user.login}* (${user.id})` })\n return { clientId: clientId, clientToken: token } satisfies SyncClientAuthRegistration\n } catch (e) {\n this.logger.error({ tag: this.getOrCreateClient.name, msg: `${e}` })\n throw new HttpException('Error during the client registration', HttpStatus.INTERNAL_SERVER_ERROR)\n }\n }\n}\n"],"names":["SyncClientsManager","register","clientRegistrationDto","ip","user","authProvider","validateUser","login","password","AUTH_SCOPE","CLIENT","logger","warn","tag","name","msg","HttpException","HttpStatus","UNAUTHORIZED","havePermission","USER_PERMISSION","DESKTOP_APP","id","FORBIDDEN","configuration","auth","mfa","totp","enabled","twoFaEnabled","code","authCode","authProvider2FA","validateTwoFactorCode","secrets","twoFaSecret","success","message","authRCode","validateRecoveryCode","recoveryCodes","usersManager","updateAccesses","catch","e","error","getOrCreateClient","clientId","info","registerWithAuth","clientAuthenticatedRegistrationDto","req","crypto","randomUUID","unregister","syncQueries","deleteClient","INTERNAL_SERVER_ERROR","authenticate","authType","syncClientAuthDto","res","client","getClient","token","currentTimeStamp","tokenExpiration","CLIENT_TOKEN_EXPIRED_ERROR","updateClientInfo","fromUserId","ownerId","isActive","r","CLIENT_AUTH_TYPE","COOKIE","authManager","setCookies","TOKEN","getTokens","client_token_update","renewTokenAndExpiration","getClients","owner","convertHumanTimeToSeconds","CLIENT_TOKEN_RENEW_TIME","undefined","expiration","CLIENT_TOKEN_EXPIRATION_TIME","log","renewClientTokenAndExpiration","BAD_REQUEST","checkAppStore","manifest","applications","appStore","repository","APP_STORE_REPOSITORY","PUBLIC","url","RELEASES_URL","APP_STORE_MANIFEST_FILE","http","axiosRef","method","HTTP_METHOD","GET","data","latestFile","path","join","STATIC_PATH","APP_STORE_DIRNAME","isPathExists","JSON","parse","fs","readFile","LOCAL","os","packages","Object","entries","platform","p","package","toLowerCase","startsWith","SYNC_CLIENT_TYPE","DESKTOP","CLI","clientInfo","type","clientToken","Logger"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../backend/src/applications/sync/services/sync-clients-manager.service.ts"],"sourcesContent":["import { HttpService } from '@nestjs/axios'\nimport { HttpException, HttpStatus, Injectable, Logger } from '@nestjs/common'\nimport { AxiosResponse } from 'axios'\nimport { FastifyReply } from 'fastify'\nimport crypto from 'node:crypto'\nimport fs from 'node:fs/promises'\nimport path from 'node:path'\nimport { AuthManager } from '../../../authentication/auth.service'\nimport { AUTH_SCOPE } from '../../../authentication/constants/scope'\nimport { FastifyAuthenticatedRequest } from '../../../authentication/interfaces/auth-request.interface'\nimport { AuthProvider } from '../../../authentication/providers/auth-providers.models'\nimport { AuthProvider2FA } from '../../../authentication/providers/two-fa/auth-provider-two-fa.service'\nimport { convertHumanTimeToSeconds } from '../../../common/functions'\nimport { currentTimeStamp, RELEASES_URL } from '../../../common/shared'\nimport { STATIC_PATH } from '../../../configuration/config.constants'\nimport { configuration } from '../../../configuration/config.environment'\nimport { CacheDecorator } from '../../../infrastructure/cache/cache.decorator'\nimport { HTTP_METHOD } from '../../applications.constants'\nimport { isPathExists } from '../../files/utils/files'\nimport { USER_PERMISSION } from '../../users/constants/user'\nimport { UserModel } from '../../users/models/user.model'\nimport { UsersManager } from '../../users/services/users-manager.service'\nimport { CLIENT_AUTH_TYPE, CLIENT_TOKEN_EXPIRATION_TIME, CLIENT_TOKEN_EXPIRED_ERROR, CLIENT_TOKEN_RENEW_TIME } from '../constants/auth'\nimport { APP_STORE_DIRNAME, APP_STORE_MANIFEST_FILE, APP_STORE_REPOSITORY } from '../constants/store'\nimport { SYNC_CLIENT_TYPE } from '../constants/sync'\nimport type { SyncClientAuthDto } from '../dtos/sync-client-auth.dto'\nimport { SyncClientAuthRegistrationDto, SyncClientRegistrationDto } from '../dtos/sync-client-registration.dto'\nimport { AppStoreManifest } from '../interfaces/store-manifest.interface'\nimport { SyncClientAuthCookie, SyncClientAuthRegistration, SyncClientAuthToken } from '../interfaces/sync-client-auth.interface'\nimport { SyncClientPaths } from '../interfaces/sync-client-paths.interface'\nimport { SyncClientInfo } from '../interfaces/sync-client.interface'\nimport { SyncClient } from '../schemas/sync-client.interface'\nimport { SyncQueries } from './sync-queries.service'\n\n@Injectable()\nexport class SyncClientsManager {\n private readonly logger = new Logger(SyncClientsManager.name)\n\n constructor(\n private readonly http: HttpService,\n private readonly authManager: AuthManager,\n private readonly authProvider: AuthProvider,\n private readonly authProvider2FA: AuthProvider2FA,\n private readonly usersManager: UsersManager,\n private readonly syncQueries: SyncQueries\n ) {}\n\n async register(clientRegistrationDto: SyncClientRegistrationDto, ip: string): Promise<SyncClientAuthRegistration> {\n const user: UserModel = await this.authProvider.validateUser(clientRegistrationDto.login, clientRegistrationDto.password, ip, AUTH_SCOPE.CLIENT)\n if (!user) {\n this.logger.warn({ tag: this.register.name, msg: `auth failed for user *${clientRegistrationDto.login}*` })\n throw new HttpException('Wrong login or password', HttpStatus.UNAUTHORIZED)\n }\n if (!user.havePermission(USER_PERMISSION.DESKTOP_APP)) {\n this.logger.warn({\n tag: this.register.name,\n msg: `user *${user.login}* (${user.id}) does not have permission : ${USER_PERMISSION.DESKTOP_APP}`\n })\n throw new HttpException('Desktop app permission required', HttpStatus.FORBIDDEN)\n }\n if (configuration.auth.mfa.totp.enabled && user.twoFaEnabled) {\n // Checking TOTP code and recovery code\n if (!clientRegistrationDto.code) {\n this.logger.warn({ tag: this.register.name, msg: `missing two-fa code for user *${user.login}* (${user.id})` })\n throw new HttpException('Missing TWO-FA code', HttpStatus.UNAUTHORIZED)\n }\n const authCode = this.authProvider2FA.validateTwoFactorCode(clientRegistrationDto.code, user.secrets.twoFaSecret)\n if (!authCode.success) {\n this.logger.warn({ tag: this.register.name, msg: `two-fa code for *${user.login}* (${user.id}) - ${authCode.message}` })\n const authRCode = await this.authProvider2FA.validateRecoveryCode(user.id, clientRegistrationDto.code, user.secrets.recoveryCodes)\n if (!authRCode.success) {\n this.logger.warn({ tag: this.register.name, msg: `two-fa recovery code for *${user.login}* (${user.id}) - ${authRCode.message}` })\n this.usersManager.updateAccesses(user, ip, false).catch((e: Error) => this.logger.error({ tag: this.register.name, msg: `${e}` }))\n throw new HttpException(authCode.message, HttpStatus.UNAUTHORIZED)\n }\n }\n }\n return this.getOrCreateClient(user, clientRegistrationDto.clientId, clientRegistrationDto.info, ip)\n }\n\n async registerWithAuth(\n clientAuthenticatedRegistrationDto: SyncClientAuthRegistrationDto,\n req: FastifyAuthenticatedRequest\n ): Promise<SyncClientAuthRegistration> {\n const clientId = clientAuthenticatedRegistrationDto.clientId || crypto.randomUUID()\n return this.getOrCreateClient(req.user, clientId, clientAuthenticatedRegistrationDto.info, req.ip)\n }\n\n async unregister(user: UserModel): Promise<void> {\n try {\n await this.syncQueries.deleteClient(user.id, user.clientId)\n } catch (e) {\n this.logger.error({ tag: this.unregister.name, msg: `${e}` })\n throw new HttpException('Error during the removing of client registration', HttpStatus.INTERNAL_SERVER_ERROR)\n }\n }\n\n async authenticate(\n authType: CLIENT_AUTH_TYPE,\n syncClientAuthDto: SyncClientAuthDto,\n ip: string,\n res: FastifyReply\n ): Promise<SyncClientAuthToken | SyncClientAuthCookie> {\n const client = await this.syncQueries.getClient(syncClientAuthDto.clientId, null, syncClientAuthDto.token)\n if (!client) {\n throw new HttpException('Client is unknown', HttpStatus.FORBIDDEN)\n }\n if (!client.enabled) {\n throw new HttpException('Client is disabled', HttpStatus.FORBIDDEN)\n }\n if (currentTimeStamp() >= client.tokenExpiration) {\n throw new HttpException(CLIENT_TOKEN_EXPIRED_ERROR, HttpStatus.FORBIDDEN)\n }\n this.syncQueries.updateClientInfo(client, client.info, ip).catch((e: Error) => this.logger.error({ tag: this.authenticate.name, msg: `${e}` }))\n const user: UserModel = await this.usersManager.fromUserId(client.ownerId)\n if (!user) {\n throw new HttpException('User does not exist', HttpStatus.FORBIDDEN)\n }\n if (!user.isActive) {\n throw new HttpException('Account suspended or not authorized', HttpStatus.FORBIDDEN)\n }\n if (!user.havePermission(USER_PERMISSION.DESKTOP_APP)) {\n this.logger.warn({ tag: this.authenticate.name, msg: `does not have permission : ${USER_PERMISSION.DESKTOP_APP}` })\n throw new HttpException('Missing permission', HttpStatus.FORBIDDEN)\n }\n // set clientId\n user.clientId = client.id\n // update accesses\n this.usersManager.updateAccesses(user, ip, true).catch((e: Error) => this.logger.error({ tag: this.authenticate.name, msg: `${e}` }))\n let r: SyncClientAuthToken | SyncClientAuthCookie\n if (authType === CLIENT_AUTH_TYPE.COOKIE) {\n // used by the desktop app to perform the login setup using cookies\n r = await this.authManager.setCookies(user, res)\n } else if (authType === CLIENT_AUTH_TYPE.TOKEN) {\n // used by the cli app and the sync core\n r = await this.authManager.getTokens(user)\n }\n // check if the client token must be updated\n r.client_token_update = await this.renewTokenAndExpiration(client, user)\n return r\n }\n\n getClients(user: UserModel): Promise<SyncClientPaths[]> {\n return this.syncQueries.getClients(user)\n }\n\n async renewTokenAndExpiration(client: SyncClient, owner: UserModel): Promise<string | undefined> {\n if (currentTimeStamp() + convertHumanTimeToSeconds(CLIENT_TOKEN_RENEW_TIME) < client.tokenExpiration) {\n // client token expiration is not close enough\n return undefined\n }\n const token = crypto.randomUUID()\n const expiration = currentTimeStamp() + convertHumanTimeToSeconds(CLIENT_TOKEN_EXPIRATION_TIME)\n this.logger.log({ tag: this.renewTokenAndExpiration.name, msg: `renew token for user *${owner.login}* and client *${client.id}*` })\n try {\n await this.syncQueries.renewClientTokenAndExpiration(client.id, token, expiration)\n } catch (e) {\n this.logger.error({\n tag: this.renewTokenAndExpiration.name,\n msg: `unable to renew token for user *${owner.login}* and client *${client.id}* : ${e}`\n })\n throw new HttpException('Unable to update client token', HttpStatus.BAD_REQUEST)\n }\n return token\n }\n\n async deleteClient(user: UserModel, clientId: string): Promise<void> {\n try {\n await this.syncQueries.deleteClient(user.id, clientId)\n } catch (e) {\n this.logger.error({ tag: this.deleteClient.name, msg: `${e}` })\n throw new HttpException('Unable to delete client', HttpStatus.INTERNAL_SERVER_ERROR)\n }\n }\n\n @CacheDecorator(3600)\n async checkAppStore(): Promise<AppStoreManifest> {\n let manifest: AppStoreManifest = null\n if (configuration.applications.appStore.repository === APP_STORE_REPOSITORY.PUBLIC) {\n const url = `${RELEASES_URL}/${APP_STORE_MANIFEST_FILE}`\n try {\n const res: AxiosResponse = await this.http.axiosRef({\n method: HTTP_METHOD.GET,\n url: url\n })\n manifest = res.data\n manifest.repository = APP_STORE_REPOSITORY.PUBLIC\n } catch (e) {\n this.logger.warn({ tag: this.checkAppStore.name, msg: `unable to retrieve ${url} : ${e}` })\n }\n } else {\n const latestFile = path.join(STATIC_PATH, APP_STORE_DIRNAME, APP_STORE_MANIFEST_FILE)\n if (!(await isPathExists(latestFile))) {\n this.logger.warn({ tag: this.checkAppStore.name, msg: `${latestFile} does not exist` })\n } else {\n try {\n manifest = JSON.parse(await fs.readFile(latestFile, 'utf8'))\n manifest.repository = APP_STORE_REPOSITORY.LOCAL\n // rewrite urls to local repository\n for (const [os, packages] of Object.entries(manifest.platform)) {\n for (const p of packages) {\n if (p.package.toLowerCase().startsWith(SYNC_CLIENT_TYPE.DESKTOP)) {\n p.url = `${APP_STORE_DIRNAME}/${SYNC_CLIENT_TYPE.DESKTOP}/${os}/${p.package}`\n } else {\n p.url = `${APP_STORE_DIRNAME}/${SYNC_CLIENT_TYPE.CLI}/${p.package}`\n }\n }\n }\n } catch (e) {\n this.logger.error({ tag: this.checkAppStore.name, msg: `${latestFile} : ${e}` })\n }\n }\n }\n return manifest\n }\n\n private async getOrCreateClient(user: UserModel, clientId: string, clientInfo: SyncClientInfo, ip: string): Promise<SyncClientAuthRegistration> {\n try {\n const token = await this.syncQueries.getOrCreateClient(user.id, clientId, clientInfo, ip)\n this.logger.log({ tag: this.getOrCreateClient.name, msg: `client *${clientInfo.type}* was registered for user *${user.login}* (${user.id})` })\n return { clientId: clientId, clientToken: token } satisfies SyncClientAuthRegistration\n } catch (e) {\n this.logger.error({ tag: this.getOrCreateClient.name, msg: `${e}` })\n throw new HttpException('Error during the client registration', HttpStatus.INTERNAL_SERVER_ERROR)\n }\n }\n}\n"],"names":["SyncClientsManager","register","clientRegistrationDto","ip","user","authProvider","validateUser","login","password","AUTH_SCOPE","CLIENT","logger","warn","tag","name","msg","HttpException","HttpStatus","UNAUTHORIZED","havePermission","USER_PERMISSION","DESKTOP_APP","id","FORBIDDEN","configuration","auth","mfa","totp","enabled","twoFaEnabled","code","authCode","authProvider2FA","validateTwoFactorCode","secrets","twoFaSecret","success","message","authRCode","validateRecoveryCode","recoveryCodes","usersManager","updateAccesses","catch","e","error","getOrCreateClient","clientId","info","registerWithAuth","clientAuthenticatedRegistrationDto","req","crypto","randomUUID","unregister","syncQueries","deleteClient","INTERNAL_SERVER_ERROR","authenticate","authType","syncClientAuthDto","res","client","getClient","token","currentTimeStamp","tokenExpiration","CLIENT_TOKEN_EXPIRED_ERROR","updateClientInfo","fromUserId","ownerId","isActive","r","CLIENT_AUTH_TYPE","COOKIE","authManager","setCookies","TOKEN","getTokens","client_token_update","renewTokenAndExpiration","getClients","owner","convertHumanTimeToSeconds","CLIENT_TOKEN_RENEW_TIME","undefined","expiration","CLIENT_TOKEN_EXPIRATION_TIME","log","renewClientTokenAndExpiration","BAD_REQUEST","checkAppStore","manifest","applications","appStore","repository","APP_STORE_REPOSITORY","PUBLIC","url","RELEASES_URL","APP_STORE_MANIFEST_FILE","http","axiosRef","method","HTTP_METHOD","GET","data","latestFile","path","join","STATIC_PATH","APP_STORE_DIRNAME","isPathExists","JSON","parse","fs","readFile","LOCAL","os","packages","Object","entries","platform","p","package","toLowerCase","startsWith","SYNC_CLIENT_TYPE","DESKTOP","CLI","clientInfo","type","clientToken","Logger"],"mappings":"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"}
@@ -35,6 +35,7 @@ function _ts_metadata(k, v) {
35
35
  let AuthProviderOIDCSecurityConfig = class AuthProviderOIDCSecurityConfig {
36
36
  constructor(){
37
37
  this.scope = 'openid email profile';
38
+ this.supportPKCE = true;
38
39
  this.tokenEndpointAuthMethod = _authoidcconstants.OAuthTokenEndpoint.ClientSecretBasic;
39
40
  this.tokenSigningAlg = 'RS256';
40
41
  this.userInfoSigningAlg = undefined;
@@ -47,6 +48,10 @@ _ts_decorate([
47
48
  message: 'OIDC scope must include "openid"'
48
49
  })
49
50
  ], AuthProviderOIDCSecurityConfig.prototype, "scope", void 0);
51
+ _ts_decorate([
52
+ (0, _classvalidator.IsOptional)(),
53
+ (0, _classvalidator.IsBoolean)()
54
+ ], AuthProviderOIDCSecurityConfig.prototype, "supportPKCE", void 0);
50
55
  _ts_decorate([
51
56
  (0, _classtransformer.Transform)(({ value })=>value || _authoidcconstants.OAuthTokenEndpoint.ClientSecretBasic),
52
57
  (0, _classvalidator.IsEnum)(_authoidcconstants.OAuthTokenEndpoint),
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../backend/src/authentication/providers/oidc/auth-oidc.config.ts"],"sourcesContent":["import { Transform, Type } from 'class-transformer'\nimport {\n IsArray,\n IsBoolean,\n IsDefined,\n IsEnum,\n IsNotEmpty,\n IsNotEmptyObject,\n IsObject,\n IsOptional,\n IsString,\n Matches,\n ValidateNested\n} from 'class-validator'\nimport { USER_PERMISSION } from '../../../applications/users/constants/user'\nimport { OAuthTokenEndpoint } from './auth-oidc.constants'\n\nexport class AuthProviderOIDCSecurityConfig {\n @IsString()\n @Matches(/\\bopenid\\b/, { message: 'OIDC scope must include \"openid\"' })\n scope = 'openid email profile'\n\n @Transform(({ value }) => value || OAuthTokenEndpoint.ClientSecretBasic)\n @IsEnum(OAuthTokenEndpoint)\n tokenEndpointAuthMethod: OAuthTokenEndpoint = OAuthTokenEndpoint.ClientSecretBasic\n\n @IsString()\n @IsNotEmpty()\n tokenSigningAlg = 'RS256'\n\n @IsOptional()\n @IsString()\n userInfoSigningAlg? = undefined\n\n @IsOptional()\n @IsBoolean()\n skipSubjectCheck? = false\n}\n\nexport class AuthProviderOIDCOptionsConfig {\n @IsOptional()\n @IsBoolean()\n autoCreateUser? = true\n\n @IsOptional()\n @IsArray()\n @IsEnum(USER_PERMISSION, { each: true })\n autoCreatePermissions?: USER_PERMISSION[] = []\n\n @IsOptional()\n @IsBoolean()\n autoRedirect? = false\n\n @IsOptional()\n @IsBoolean()\n enablePasswordAuth? = true\n\n @IsOptional()\n @IsString()\n adminRoleOrGroup?: string\n\n @IsString()\n @IsNotEmpty()\n buttonText: string = 'Continue with OpenID Connect'\n}\n\nexport class AuthProviderOIDCConfig {\n @IsString()\n @IsNotEmpty()\n issuerUrl: string\n\n @IsString()\n @IsNotEmpty()\n clientId: string\n\n @IsString()\n @IsNotEmpty()\n clientSecret: string\n\n @IsString()\n @IsNotEmpty()\n redirectUri: string\n\n @IsDefined()\n @IsNotEmptyObject()\n @IsObject()\n @ValidateNested()\n @Type(() => AuthProviderOIDCOptionsConfig)\n options: AuthProviderOIDCOptionsConfig = new AuthProviderOIDCOptionsConfig()\n\n @IsDefined()\n @IsNotEmptyObject()\n @IsObject()\n @ValidateNested()\n @Type(() => AuthProviderOIDCSecurityConfig)\n security: AuthProviderOIDCSecurityConfig = new AuthProviderOIDCSecurityConfig()\n}\n"],"names":["AuthProviderOIDCConfig","AuthProviderOIDCOptionsConfig","AuthProviderOIDCSecurityConfig","scope","tokenEndpointAuthMethod","OAuthTokenEndpoint","ClientSecretBasic","tokenSigningAlg","userInfoSigningAlg","undefined","skipSubjectCheck","message","value","autoCreateUser","autoCreatePermissions","autoRedirect","enablePasswordAuth","buttonText","each","options","security"],"mappings":";;;;;;;;;;;QAkEaA;eAAAA;;QA3BAC;eAAAA;;QAtBAC;eAAAA;;;kCAjBmB;gCAazB;sBACyB;mCACG;;;;;;;;;;AAE5B,IAAA,AAAMA,iCAAN,MAAMA;;aAGXC,QAAQ;aAIRC,0BAA8CC,qCAAkB,CAACC,iBAAiB;aAIlFC,kBAAkB;aAIlBC,qBAAsBC;aAItBC,mBAAoB;;AACtB;;;;QAlB2BC,SAAS;;;;sCAGtB,EAAEC,KAAK,EAAE,GAAKA,SAASP,qCAAkB,CAACC,iBAAiB;;;;;;;;;;;;;;;;AAiBlE,IAAA,AAAML,gCAAN,MAAMA;;aAGXY,iBAAkB;aAKlBC,wBAA4C,EAAE;aAI9CC,eAAgB;aAIhBC,qBAAsB;aAQtBC,aAAqB;;AACvB;;;;;;;;;QAlB6BC,MAAM;;;;;;;;;;;;;;;;;;;;;;AAoB5B,IAAA,AAAMlB,yBAAN,MAAMA;;aAsBXmB,UAAyC,IAAIlB;aAO7CmB,WAA2C,IAAIlB;;AACjD;;;;;;;;;;;;;;;;;;;;;;;;;;oCATcD;;;;;;;;oCAOAC"}
1
+ {"version":3,"sources":["../../../../../backend/src/authentication/providers/oidc/auth-oidc.config.ts"],"sourcesContent":["import { Transform, Type } from 'class-transformer'\nimport {\n IsArray,\n IsBoolean,\n IsDefined,\n IsEnum,\n IsNotEmpty,\n IsNotEmptyObject,\n IsObject,\n IsOptional,\n IsString,\n Matches,\n ValidateNested\n} from 'class-validator'\nimport { USER_PERMISSION } from '../../../applications/users/constants/user'\nimport { OAuthTokenEndpoint } from './auth-oidc.constants'\n\nexport class AuthProviderOIDCSecurityConfig {\n @IsString()\n @Matches(/\\bopenid\\b/, { message: 'OIDC scope must include \"openid\"' })\n scope = 'openid email profile'\n\n @IsOptional()\n @IsBoolean()\n supportPKCE? = true\n\n @Transform(({ value }) => value || OAuthTokenEndpoint.ClientSecretBasic)\n @IsEnum(OAuthTokenEndpoint)\n tokenEndpointAuthMethod: OAuthTokenEndpoint = OAuthTokenEndpoint.ClientSecretBasic\n\n @IsString()\n @IsNotEmpty()\n tokenSigningAlg = 'RS256'\n\n @IsOptional()\n @IsString()\n userInfoSigningAlg? = undefined\n\n @IsOptional()\n @IsBoolean()\n skipSubjectCheck? = false\n}\n\nexport class AuthProviderOIDCOptionsConfig {\n @IsOptional()\n @IsBoolean()\n autoCreateUser? = true\n\n @IsOptional()\n @IsArray()\n @IsEnum(USER_PERMISSION, { each: true })\n autoCreatePermissions?: USER_PERMISSION[] = []\n\n @IsOptional()\n @IsBoolean()\n autoRedirect? = false\n\n @IsOptional()\n @IsBoolean()\n enablePasswordAuth? = true\n\n @IsOptional()\n @IsString()\n adminRoleOrGroup?: string\n\n @IsString()\n @IsNotEmpty()\n buttonText: string = 'Continue with OpenID Connect'\n}\n\nexport class AuthProviderOIDCConfig {\n @IsString()\n @IsNotEmpty()\n issuerUrl: string\n\n @IsString()\n @IsNotEmpty()\n clientId: string\n\n @IsString()\n @IsNotEmpty()\n clientSecret: string\n\n @IsString()\n @IsNotEmpty()\n redirectUri: string\n\n @IsDefined()\n @IsNotEmptyObject()\n @IsObject()\n @ValidateNested()\n @Type(() => AuthProviderOIDCOptionsConfig)\n options: AuthProviderOIDCOptionsConfig = new AuthProviderOIDCOptionsConfig()\n\n @IsDefined()\n @IsNotEmptyObject()\n @IsObject()\n @ValidateNested()\n @Type(() => AuthProviderOIDCSecurityConfig)\n security: AuthProviderOIDCSecurityConfig = new AuthProviderOIDCSecurityConfig()\n}\n"],"names":["AuthProviderOIDCConfig","AuthProviderOIDCOptionsConfig","AuthProviderOIDCSecurityConfig","scope","supportPKCE","tokenEndpointAuthMethod","OAuthTokenEndpoint","ClientSecretBasic","tokenSigningAlg","userInfoSigningAlg","undefined","skipSubjectCheck","message","value","autoCreateUser","autoCreatePermissions","autoRedirect","enablePasswordAuth","buttonText","each","options","security"],"mappings":";;;;;;;;;;;QAsEaA;eAAAA;;QA3BAC;eAAAA;;QA1BAC;eAAAA;;;kCAjBmB;gCAazB;sBACyB;mCACG;;;;;;;;;;AAE5B,IAAA,AAAMA,iCAAN,MAAMA;;aAGXC,QAAQ;aAIRC,cAAe;aAIfC,0BAA8CC,qCAAkB,CAACC,iBAAiB;aAIlFC,kBAAkB;aAIlBC,qBAAsBC;aAItBC,mBAAoB;;AACtB;;;;QAtB2BC,SAAS;;;;;;;;sCAOtB,EAAEC,KAAK,EAAE,GAAKA,SAASP,qCAAkB,CAACC,iBAAiB;;;;;;;;;;;;;;;;AAiBlE,IAAA,AAAMN,gCAAN,MAAMA;;aAGXa,iBAAkB;aAKlBC,wBAA4C,EAAE;aAI9CC,eAAgB;aAIhBC,qBAAsB;aAQtBC,aAAqB;;AACvB;;;;;;;;;QAlB6BC,MAAM;;;;;;;;;;;;;;;;;;;;;;AAoB5B,IAAA,AAAMnB,yBAAN,MAAMA;;aAsBXoB,UAAyC,IAAInB;aAO7CoB,WAA2C,IAAInB;;AACjD;;;;;;;;;;;;;;;;;;;;;;;;;;oCATcD;;;;;;;;oCAOAC"}
@@ -58,8 +58,8 @@ let AuthProviderOIDC = class AuthProviderOIDC {
58
58
  // state: CSRF protection, nonce: binds the ID Token to this auth request (replay protection)
59
59
  const state = (0, _openidclient.randomState)();
60
60
  const nonce = (0, _openidclient.randomNonce)();
61
- const supportsPKCE = config.serverMetadata().supportsPKCE();
62
- const codeVerifier = supportsPKCE ? (0, _openidclient.randomPKCECodeVerifier)() : undefined;
61
+ const isPKCEEnabled = this.isPKCEEnabled(config);
62
+ const codeVerifier = isPKCEEnabled ? (0, _openidclient.randomPKCECodeVerifier)() : undefined;
63
63
  const authUrl = new URL(config.serverMetadata().authorization_endpoint);
64
64
  authUrl.searchParams.set('client_id', this.oidcConfig.clientId);
65
65
  authUrl.searchParams.set('redirect_uri', redirectURI);
@@ -67,7 +67,7 @@ let AuthProviderOIDC = class AuthProviderOIDC {
67
67
  authUrl.searchParams.set('scope', this.oidcConfig.security.scope);
68
68
  authUrl.searchParams.set('state', state);
69
69
  authUrl.searchParams.set('nonce', nonce);
70
- if (supportsPKCE) {
70
+ if (isPKCEEnabled) {
71
71
  const codeChallenge = await (0, _openidclient.calculatePKCECodeChallenge)(codeVerifier);
72
72
  authUrl.searchParams.set('code_challenge', codeChallenge);
73
73
  authUrl.searchParams.set('code_challenge_method', 'S256');
@@ -77,14 +77,14 @@ let AuthProviderOIDC = class AuthProviderOIDC {
77
77
  // Store state, nonce, and codeVerifier in httpOnly cookies (expires in 10 minutes)
78
78
  res.setCookie(_authoidcconstants.OAuthCookie.State, state, _authoidcconstants.OAuthCookieSettings);
79
79
  res.setCookie(_authoidcconstants.OAuthCookie.Nonce, nonce, _authoidcconstants.OAuthCookieSettings);
80
- if (supportsPKCE) {
80
+ if (isPKCEEnabled) {
81
81
  res.setCookie(_authoidcconstants.OAuthCookie.CodeVerifier, codeVerifier, _authoidcconstants.OAuthCookieSettings);
82
82
  }
83
83
  return authUrl.toString();
84
84
  }
85
85
  async handleCallback(req, res, query) {
86
86
  const config = await this.getConfig();
87
- const supportsPKCE = config.serverMetadata().supportsPKCE();
87
+ const isPKCEEnabled = this.isPKCEEnabled(config);
88
88
  const [expectedState, expectedNonce, codeVerifier] = [
89
89
  req.cookies[_authoidcconstants.OAuthCookie.State],
90
90
  req.cookies[_authoidcconstants.OAuthCookie.Nonce],
@@ -94,10 +94,10 @@ let AuthProviderOIDC = class AuthProviderOIDC {
94
94
  if (!expectedState?.length) {
95
95
  throw new _common.HttpException('OAuth state is missing', _common.HttpStatus.BAD_REQUEST);
96
96
  }
97
- if (supportsPKCE && !codeVerifier?.length) {
97
+ if (isPKCEEnabled && !codeVerifier?.length) {
98
98
  throw new _common.HttpException('OAuth code verifier is missing', _common.HttpStatus.BAD_REQUEST);
99
99
  }
100
- const pkceCodeVerifier = supportsPKCE ? codeVerifier : undefined;
100
+ const pkceCodeVerifier = isPKCEEnabled ? codeVerifier : undefined;
101
101
  const callbackParams = new URLSearchParams(query);
102
102
  // Get Desktop Port if defined
103
103
  const desktopPort = callbackParams.get(_authoidcdesktopconstants.OAuthDesktopPortParam);
@@ -236,6 +236,9 @@ let AuthProviderOIDC = class AuthProviderOIDC {
236
236
  }
237
237
  }
238
238
  }
239
+ isPKCEEnabled(config) {
240
+ return (this.oidcConfig.security.supportPKCE ?? true) && config.serverMetadata().supportsPKCE();
241
+ }
239
242
  async processUserInfo(userInfo, ip) {
240
243
  // Extract user information
241
244
  const { login, email } = this.extractLoginAndEmail(userInfo);
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../backend/src/authentication/providers/oidc/auth-provider-oidc.service.ts"],"sourcesContent":["import { HttpException, HttpStatus, Injectable, Logger } from '@nestjs/common'\nimport { FastifyReply, FastifyRequest } from 'fastify'\nimport {\n allowInsecureRequests,\n authorizationCodeGrant,\n AuthorizationResponseError,\n calculatePKCECodeChallenge,\n ClientSecretBasic,\n ClientSecretPost,\n Configuration,\n discovery,\n fetchUserInfo,\n IDToken,\n None,\n randomNonce,\n randomPKCECodeVerifier,\n randomState,\n skipSubjectCheck,\n UserInfoResponse\n} from 'openid-client'\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 { generateShortUUID, splitFullName } from '../../../common/functions'\nimport { configuration } from '../../../configuration/config.environment'\nimport { AUTH_ROUTE } from '../../constants/routes'\nimport type { AUTH_SCOPE } from '../../constants/scope'\nimport { TOKEN_TYPE } from '../../interfaces/token.interface'\nimport { AUTH_PROVIDER } from '../auth-providers.constants'\nimport { AuthProvider } from '../auth-providers.models'\nimport { OAuthDesktopCallBackURI, OAuthDesktopLoopbackPorts, OAuthDesktopPortParam } from './auth-oidc-desktop.constants'\nimport type { AuthProviderOIDCConfig } from './auth-oidc.config'\nimport { OAuthCookie, OAuthCookieSettings, OAuthTokenEndpoint } from './auth-oidc.constants'\n\n@Injectable()\nexport class AuthProviderOIDC implements AuthProvider {\n private readonly logger = new Logger(AuthProviderOIDC.name)\n private readonly oidcConfig: AuthProviderOIDCConfig = configuration.auth.oidc\n private frontendBaseUrl: string\n private config: Configuration = null\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 // Local password authentication path (non-OIDC)\n const user: UserModel = await this.usersManager.findUser(login, false)\n\n if (!user) {\n return null\n }\n\n if (user.isGuest || user.isAdmin || scope || this.oidcConfig.options.enablePasswordAuth) {\n // Allow local password authentication for:\n // - guest users\n // - admin users (break-glass access)\n // - application scopes (app passwords)\n // - regular users when password authentication is enabled\n return this.usersManager.logUser(user, password, ip, scope)\n }\n\n return null\n }\n\n async getConfig(): Promise<Configuration> {\n if (!this.config) {\n this.config = await this.initializeOIDCClient()\n }\n return this.config\n }\n\n async getAuthorizationUrl(res: FastifyReply, desktopPort?: number): Promise<string> {\n const redirectURI = this.getRedirectURI(desktopPort)\n const config = await this.getConfig()\n\n // state: CSRF protection, nonce: binds the ID Token to this auth request (replay protection)\n const state = randomState()\n const nonce = randomNonce()\n\n const supportsPKCE = config.serverMetadata().supportsPKCE()\n const codeVerifier = supportsPKCE ? randomPKCECodeVerifier() : undefined\n\n const authUrl = new URL(config.serverMetadata().authorization_endpoint!)\n authUrl.searchParams.set('client_id', this.oidcConfig.clientId!)\n authUrl.searchParams.set('redirect_uri', redirectURI)\n authUrl.searchParams.set('response_type', 'code')\n authUrl.searchParams.set('scope', this.oidcConfig.security.scope)\n authUrl.searchParams.set('state', state)\n authUrl.searchParams.set('nonce', nonce)\n if (supportsPKCE) {\n const codeChallenge = await calculatePKCECodeChallenge(codeVerifier!)\n authUrl.searchParams.set('code_challenge', codeChallenge)\n authUrl.searchParams.set('code_challenge_method', 'S256')\n }\n\n // Avoid cache\n res\n .header('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0')\n .header('Pragma', 'no-cache')\n .header('Expires', '0')\n .header('X-Robots-Tag', 'noindex, nofollow')\n .header('Referrer-Policy', 'no-referrer')\n\n // Store state, nonce, and codeVerifier in httpOnly cookies (expires in 10 minutes)\n res.setCookie(OAuthCookie.State, state, OAuthCookieSettings)\n res.setCookie(OAuthCookie.Nonce, nonce, OAuthCookieSettings)\n if (supportsPKCE) {\n res.setCookie(OAuthCookie.CodeVerifier, codeVerifier, OAuthCookieSettings)\n }\n return authUrl.toString()\n }\n\n async handleCallback(req: FastifyRequest, res: FastifyReply, query: Record<string, string>): Promise<UserModel> {\n const config = await this.getConfig()\n const supportsPKCE = config.serverMetadata().supportsPKCE()\n const [expectedState, expectedNonce, codeVerifier] = [\n req.cookies[OAuthCookie.State],\n req.cookies[OAuthCookie.Nonce],\n req.cookies[OAuthCookie.CodeVerifier]\n ]\n\n try {\n if (!expectedState?.length) {\n throw new HttpException('OAuth state is missing', HttpStatus.BAD_REQUEST)\n }\n\n if (supportsPKCE && !codeVerifier?.length) {\n throw new HttpException('OAuth code verifier is missing', HttpStatus.BAD_REQUEST)\n }\n\n const pkceCodeVerifier = supportsPKCE ? codeVerifier : undefined\n const callbackParams = new URLSearchParams(query)\n\n // Get Desktop Port if defined\n const desktopPort: string | null = callbackParams.get(OAuthDesktopPortParam)\n if (desktopPort) {\n callbackParams.delete(OAuthDesktopPortParam)\n }\n\n // Exchange authorization code for tokens\n const callbackUrl = new URL(this.getRedirectURI(desktopPort))\n callbackUrl.search = callbackParams.toString()\n const tokens = await authorizationCodeGrant(config, callbackUrl, {\n expectedState,\n pkceCodeVerifier,\n expectedNonce\n })\n\n // Get validated ID token claims\n const claims: IDToken = tokens.claims()\n if (!claims) {\n throw new HttpException('No ID token claims found', HttpStatus.BAD_REQUEST)\n }\n if (!claims.sub) {\n throw new HttpException('Unexpected profile response, no `sub`', HttpStatus.BAD_REQUEST)\n }\n\n // ID token claims may be minimal depending on the IdP; use the UserInfo endpoint to retrieve user details.\n // Get user info from the userinfo endpoint (requires access token and subject from ID token).\n const subject = this.oidcConfig.security.skipSubjectCheck ? skipSubjectCheck : claims.sub\n const userInfo: UserInfoResponse = await fetchUserInfo(config, tokens.access_token, subject)\n\n if (!userInfo.sub) {\n throw new Error('Unexpected profile response, no `sub`')\n }\n\n // Process the user info and create/update the user\n return await this.processUserInfo(userInfo, req.ip)\n } catch (error: AuthorizationResponseError | HttpException | any) {\n if (error instanceof AuthorizationResponseError) {\n this.logger.error({ tag: this.handleCallback.name, msg: `OIDC callback error: ${error.code} - ${error.error_description}` })\n throw new HttpException(error.error_description, HttpStatus.BAD_REQUEST)\n } else {\n this.logger.error({ tag: this.handleCallback.name, msg: `OIDC callback error: ${error}` })\n throw new HttpException(\n error.error_description ?? 'OIDC authentication failed',\n error instanceof HttpException ? error.getStatus() : (error.status ?? HttpStatus.INTERNAL_SERVER_ERROR)\n )\n }\n } finally {\n // Always clear temporary OIDC cookies (success or failure)\n Object.values(OAuthCookie).forEach((value) => {\n res.clearCookie(value, { path: '/' })\n })\n }\n }\n\n getRedirectCallbackUrl(accessExpiration: number, refreshExpiration: number) {\n if (!this.frontendBaseUrl) {\n const url = new URL(this.oidcConfig.redirectUri)\n const apiIndex = url.pathname.indexOf(AUTH_ROUTE.BASE)\n this.frontendBaseUrl = apiIndex >= 0 ? `${url.origin}${url.pathname.slice(0, apiIndex)}` : url.origin\n }\n const url = new URL(this.frontendBaseUrl)\n const params = new URLSearchParams({\n [AUTH_PROVIDER.OIDC]: 'true',\n [`${TOKEN_TYPE.ACCESS}_expiration`]: `${accessExpiration}`,\n [`${TOKEN_TYPE.REFRESH}_expiration`]: `${refreshExpiration}`\n })\n url.hash = `/?${params.toString()}`\n return url.toString()\n }\n\n getRedirectURI(desktopPort?: number | string): string {\n // web / default callback\n if (!desktopPort) return this.oidcConfig.redirectUri\n // desktop app callback\n if (typeof desktopPort === 'string') {\n desktopPort = Number(desktopPort)\n }\n if (!Number.isInteger(desktopPort) || !OAuthDesktopLoopbackPorts.has(desktopPort)) {\n throw new HttpException('Invalid desktop_port', HttpStatus.BAD_REQUEST)\n }\n // The redirect url must be known from provider\n return `http://127.0.0.1:${desktopPort}${OAuthDesktopCallBackURI}`\n }\n\n private async initializeOIDCClient(): Promise<Configuration> {\n try {\n const issuerUrl = new URL(this.oidcConfig.issuerUrl)\n const config: Configuration = await discovery(\n issuerUrl,\n this.oidcConfig.clientId,\n {\n client_secret: this.oidcConfig.clientSecret,\n response_types: ['code'],\n id_token_signed_response_alg: this.oidcConfig.security.tokenSigningAlg,\n userinfo_signed_response_alg: this.oidcConfig.security.userInfoSigningAlg\n },\n this.getTokenAuthMethod(this.oidcConfig.security.tokenEndpointAuthMethod, this.oidcConfig.clientSecret),\n {\n execute: [allowInsecureRequests],\n timeout: 6000\n }\n )\n this.logger.log({ tag: this.initializeOIDCClient.name, msg: `OIDC client initialized successfully for issuer: ${this.oidcConfig.issuerUrl}` })\n return config\n } catch (error) {\n this.logger.error({ tag: this.initializeOIDCClient.name, msg: `OIDC client initialization failed: ${error?.cause || error}` })\n switch (error.cause?.code) {\n case 'ECONNREFUSED':\n case 'ENOTFOUND':\n throw new HttpException('OIDC provider unavailable', HttpStatus.SERVICE_UNAVAILABLE)\n\n case 'ETIMEDOUT':\n throw new HttpException('OIDC provider timeout', HttpStatus.GATEWAY_TIMEOUT)\n\n default:\n throw new HttpException('OIDC client initialization failed', HttpStatus.INTERNAL_SERVER_ERROR)\n }\n }\n }\n\n private getTokenAuthMethod(tokenEndpointAuthMethod: OAuthTokenEndpoint, clientSecret?: string) {\n if (!clientSecret) {\n return None()\n }\n\n switch (tokenEndpointAuthMethod) {\n case OAuthTokenEndpoint.ClientSecretPost: {\n return ClientSecretPost(clientSecret)\n }\n\n case OAuthTokenEndpoint.ClientSecretBasic: {\n return ClientSecretBasic(clientSecret)\n }\n\n default: {\n return None()\n }\n }\n }\n\n private async processUserInfo(userInfo: UserInfoResponse, ip?: string): Promise<UserModel> {\n // Extract user information\n const { login, email } = this.extractLoginAndEmail(userInfo)\n\n // Check if user exists\n let user: UserModel = await this.usersManager.findUser(email || login, false)\n\n if (!user && !this.oidcConfig.options.autoCreateUser) {\n this.logger.warn({ tag: this.processUserInfo.name, msg: `User not found and autoCreateUser is disabled` })\n throw new HttpException('User not found', HttpStatus.UNAUTHORIZED)\n }\n\n // Determine if user should be admin based on groups/roles\n const isAdmin = this.checkAdminRole(userInfo)\n\n // Create identity\n const identity = this.createIdentity(login, email, userInfo, isAdmin)\n\n // Create or update user\n user = await this.updateOrCreateUser(identity, user)\n\n // Update user access log\n this.usersManager.updateAccesses(user, ip, true).catch((e: Error) => this.logger.error({ tag: this.processUserInfo.name, msg: `${e}` }))\n\n return user\n }\n\n private checkAdminRole(userInfo: UserInfoResponse): boolean {\n if (!this.oidcConfig.options.adminRoleOrGroup) {\n return false\n }\n\n // Check claims\n const claims = [...(Array.isArray(userInfo.groups) ? userInfo.groups : []), ...(Array.isArray(userInfo.roles) ? userInfo.roles : [])]\n\n return claims.includes(this.oidcConfig.options.adminRoleOrGroup)\n }\n\n private createIdentity(\n login: string,\n email: string,\n userInfo: UserInfoResponse,\n isAdmin: boolean\n ): Omit<CreateUserDto, 'password'> & { password?: string } {\n // Get first name and last name\n let firstName = userInfo.given_name || ''\n let lastName = userInfo.family_name || ''\n\n // If structured names not available, try to split the full name\n if (!firstName && !lastName && userInfo.name) {\n const names = splitFullName(userInfo.name)\n firstName = names.firstName\n lastName = names.lastName\n }\n\n return {\n login,\n email,\n role: isAdmin ? USER_ROLE.ADMINISTRATOR : USER_ROLE.USER,\n firstName,\n lastName\n }\n }\n\n private async updateOrCreateUser(identity: Omit<CreateUserDto, 'password'> & { password?: string }, user: UserModel | null): Promise<UserModel> {\n if (user === null) {\n // Create new user with a random password (required by the system but not used for OIDC login)\n const userWithPassword = {\n ...identity,\n password: generateShortUUID(24),\n permissions: this.oidcConfig.options.autoCreatePermissions.join(',')\n } as CreateUserDto\n const createdUser = await this.adminUsersManager.createUserOrGuest(userWithPassword, identity.role)\n const freshUser = await this.usersManager.fromUserId(createdUser.id)\n if (!freshUser) {\n this.logger.error({ tag: this.updateOrCreateUser.name, msg: `user was not found : ${createdUser.login} (${createdUser.id})` })\n throw new HttpException('User not found', HttpStatus.NOT_FOUND)\n }\n return freshUser\n }\n\n // Check if user information has changed (excluding password)\n const identityHasChanged: UpdateUserDto = Object.fromEntries(\n Object.keys(identity)\n .filter((key) => key !== 'password')\n .map((key: string) => (identity[key] !== user[key] ? [key, identity[key]] : null))\n .filter(Boolean)\n )\n\n if (Object.keys(identityHasChanged).length > 0) {\n try {\n if (identityHasChanged?.role != null) {\n if (user.role === USER_ROLE.ADMINISTRATOR && !this.oidcConfig.options.adminRoleOrGroup) {\n // Prevent removing the admin role when adminGroup was removed or not defined\n delete identityHasChanged.role\n }\n }\n\n // Update user properties\n await this.adminUsersManager.updateUserOrGuest(user.id, identityHasChanged)\n\n // Update local user object\n Object.assign(user, identityHasChanged)\n\n if ('lastName' in identityHasChanged || 'firstName' in identityHasChanged) {\n // Force fullName update in the current user model\n user.setFullName(true)\n }\n } catch (e) {\n this.logger.warn({ tag: this.updateOrCreateUser.name, msg: `unable to update user *${user.login}* : ${e}` })\n }\n }\n\n return user\n }\n\n private extractLoginAndEmail(userInfo: UserInfoResponse) {\n const email = userInfo.email ? userInfo.email.trim() : undefined\n if (!email) {\n throw new HttpException('No email address found in the OIDC profile', HttpStatus.BAD_REQUEST)\n }\n\n const login = userInfo.preferred_username ?? (email ? email.split('@')[0] : undefined) ?? userInfo.sub\n if (!login) {\n throw new HttpException('Unable to determine the OIDC profile login', HttpStatus.BAD_REQUEST)\n }\n\n return { login: login.trim().toLowerCase(), email }\n }\n}\n"],"names":["AuthProviderOIDC","validateUser","login","password","ip","scope","user","usersManager","findUser","isGuest","isAdmin","oidcConfig","options","enablePasswordAuth","logUser","getConfig","config","initializeOIDCClient","getAuthorizationUrl","res","desktopPort","redirectURI","getRedirectURI","state","randomState","nonce","randomNonce","supportsPKCE","serverMetadata","codeVerifier","randomPKCECodeVerifier","undefined","authUrl","URL","authorization_endpoint","searchParams","set","clientId","security","codeChallenge","calculatePKCECodeChallenge","header","setCookie","OAuthCookie","State","OAuthCookieSettings","Nonce","CodeVerifier","toString","handleCallback","req","query","expectedState","expectedNonce","cookies","length","HttpException","HttpStatus","BAD_REQUEST","pkceCodeVerifier","callbackParams","URLSearchParams","get","OAuthDesktopPortParam","delete","callbackUrl","search","tokens","authorizationCodeGrant","claims","sub","subject","skipSubjectCheck","userInfo","fetchUserInfo","access_token","Error","processUserInfo","error","AuthorizationResponseError","logger","tag","name","msg","code","error_description","getStatus","status","INTERNAL_SERVER_ERROR","Object","values","forEach","value","clearCookie","path","getRedirectCallbackUrl","accessExpiration","refreshExpiration","frontendBaseUrl","url","redirectUri","apiIndex","pathname","indexOf","AUTH_ROUTE","BASE","origin","slice","params","AUTH_PROVIDER","OIDC","TOKEN_TYPE","ACCESS","REFRESH","hash","Number","isInteger","OAuthDesktopLoopbackPorts","has","OAuthDesktopCallBackURI","issuerUrl","discovery","client_secret","clientSecret","response_types","id_token_signed_response_alg","tokenSigningAlg","userinfo_signed_response_alg","userInfoSigningAlg","getTokenAuthMethod","tokenEndpointAuthMethod","execute","allowInsecureRequests","timeout","log","cause","SERVICE_UNAVAILABLE","GATEWAY_TIMEOUT","None","OAuthTokenEndpoint","ClientSecretPost","ClientSecretBasic","email","extractLoginAndEmail","autoCreateUser","warn","UNAUTHORIZED","checkAdminRole","identity","createIdentity","updateOrCreateUser","updateAccesses","catch","e","adminRoleOrGroup","Array","isArray","groups","roles","includes","firstName","given_name","lastName","family_name","names","splitFullName","role","USER_ROLE","ADMINISTRATOR","USER","userWithPassword","generateShortUUID","permissions","autoCreatePermissions","join","createdUser","adminUsersManager","createUserOrGuest","freshUser","fromUserId","id","NOT_FOUND","identityHasChanged","fromEntries","keys","filter","key","map","Boolean","updateUserOrGuest","assign","setFullName","trim","preferred_username","split","toLowerCase","Logger","configuration","auth","oidc"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../backend/src/authentication/providers/oidc/auth-provider-oidc.service.ts"],"sourcesContent":["import { HttpException, HttpStatus, Injectable, Logger } from '@nestjs/common'\nimport { FastifyReply, FastifyRequest } from 'fastify'\nimport {\n allowInsecureRequests,\n authorizationCodeGrant,\n AuthorizationResponseError,\n calculatePKCECodeChallenge,\n ClientSecretBasic,\n ClientSecretPost,\n Configuration,\n discovery,\n fetchUserInfo,\n IDToken,\n None,\n randomNonce,\n randomPKCECodeVerifier,\n randomState,\n skipSubjectCheck,\n UserInfoResponse\n} from 'openid-client'\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 { generateShortUUID, splitFullName } from '../../../common/functions'\nimport { configuration } from '../../../configuration/config.environment'\nimport { AUTH_ROUTE } from '../../constants/routes'\nimport type { AUTH_SCOPE } from '../../constants/scope'\nimport { TOKEN_TYPE } from '../../interfaces/token.interface'\nimport { AUTH_PROVIDER } from '../auth-providers.constants'\nimport { AuthProvider } from '../auth-providers.models'\nimport { OAuthDesktopCallBackURI, OAuthDesktopLoopbackPorts, OAuthDesktopPortParam } from './auth-oidc-desktop.constants'\nimport type { AuthProviderOIDCConfig } from './auth-oidc.config'\nimport { OAuthCookie, OAuthCookieSettings, OAuthTokenEndpoint } from './auth-oidc.constants'\n\n@Injectable()\nexport class AuthProviderOIDC implements AuthProvider {\n private readonly logger = new Logger(AuthProviderOIDC.name)\n private readonly oidcConfig: AuthProviderOIDCConfig = configuration.auth.oidc\n private frontendBaseUrl: string\n private config: Configuration = null\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 // Local password authentication path (non-OIDC)\n const user: UserModel = await this.usersManager.findUser(login, false)\n\n if (!user) {\n return null\n }\n\n if (user.isGuest || user.isAdmin || scope || this.oidcConfig.options.enablePasswordAuth) {\n // Allow local password authentication for:\n // - guest users\n // - admin users (break-glass access)\n // - application scopes (app passwords)\n // - regular users when password authentication is enabled\n return this.usersManager.logUser(user, password, ip, scope)\n }\n\n return null\n }\n\n async getConfig(): Promise<Configuration> {\n if (!this.config) {\n this.config = await this.initializeOIDCClient()\n }\n return this.config\n }\n\n async getAuthorizationUrl(res: FastifyReply, desktopPort?: number): Promise<string> {\n const redirectURI = this.getRedirectURI(desktopPort)\n const config = await this.getConfig()\n\n // state: CSRF protection, nonce: binds the ID Token to this auth request (replay protection)\n const state = randomState()\n const nonce = randomNonce()\n\n const isPKCEEnabled = this.isPKCEEnabled(config)\n const codeVerifier = isPKCEEnabled ? randomPKCECodeVerifier() : undefined\n\n const authUrl = new URL(config.serverMetadata().authorization_endpoint!)\n authUrl.searchParams.set('client_id', this.oidcConfig.clientId!)\n authUrl.searchParams.set('redirect_uri', redirectURI)\n authUrl.searchParams.set('response_type', 'code')\n authUrl.searchParams.set('scope', this.oidcConfig.security.scope)\n authUrl.searchParams.set('state', state)\n authUrl.searchParams.set('nonce', nonce)\n if (isPKCEEnabled) {\n const codeChallenge = await calculatePKCECodeChallenge(codeVerifier!)\n authUrl.searchParams.set('code_challenge', codeChallenge)\n authUrl.searchParams.set('code_challenge_method', 'S256')\n }\n\n // Avoid cache\n res\n .header('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0')\n .header('Pragma', 'no-cache')\n .header('Expires', '0')\n .header('X-Robots-Tag', 'noindex, nofollow')\n .header('Referrer-Policy', 'no-referrer')\n\n // Store state, nonce, and codeVerifier in httpOnly cookies (expires in 10 minutes)\n res.setCookie(OAuthCookie.State, state, OAuthCookieSettings)\n res.setCookie(OAuthCookie.Nonce, nonce, OAuthCookieSettings)\n if (isPKCEEnabled) {\n res.setCookie(OAuthCookie.CodeVerifier, codeVerifier, OAuthCookieSettings)\n }\n return authUrl.toString()\n }\n\n async handleCallback(req: FastifyRequest, res: FastifyReply, query: Record<string, string>): Promise<UserModel> {\n const config = await this.getConfig()\n const isPKCEEnabled = this.isPKCEEnabled(config)\n const [expectedState, expectedNonce, codeVerifier] = [\n req.cookies[OAuthCookie.State],\n req.cookies[OAuthCookie.Nonce],\n req.cookies[OAuthCookie.CodeVerifier]\n ]\n\n try {\n if (!expectedState?.length) {\n throw new HttpException('OAuth state is missing', HttpStatus.BAD_REQUEST)\n }\n\n if (isPKCEEnabled && !codeVerifier?.length) {\n throw new HttpException('OAuth code verifier is missing', HttpStatus.BAD_REQUEST)\n }\n\n const pkceCodeVerifier = isPKCEEnabled ? codeVerifier : undefined\n const callbackParams = new URLSearchParams(query)\n\n // Get Desktop Port if defined\n const desktopPort: string | null = callbackParams.get(OAuthDesktopPortParam)\n if (desktopPort) {\n callbackParams.delete(OAuthDesktopPortParam)\n }\n\n // Exchange authorization code for tokens\n const callbackUrl = new URL(this.getRedirectURI(desktopPort))\n callbackUrl.search = callbackParams.toString()\n const tokens = await authorizationCodeGrant(config, callbackUrl, {\n expectedState,\n pkceCodeVerifier,\n expectedNonce\n })\n\n // Get validated ID token claims\n const claims: IDToken = tokens.claims()\n if (!claims) {\n throw new HttpException('No ID token claims found', HttpStatus.BAD_REQUEST)\n }\n if (!claims.sub) {\n throw new HttpException('Unexpected profile response, no `sub`', HttpStatus.BAD_REQUEST)\n }\n\n // ID token claims may be minimal depending on the IdP; use the UserInfo endpoint to retrieve user details.\n // Get user info from the userinfo endpoint (requires access token and subject from ID token).\n const subject = this.oidcConfig.security.skipSubjectCheck ? skipSubjectCheck : claims.sub\n const userInfo: UserInfoResponse = await fetchUserInfo(config, tokens.access_token, subject)\n\n if (!userInfo.sub) {\n throw new Error('Unexpected profile response, no `sub`')\n }\n\n // Process the user info and create/update the user\n return await this.processUserInfo(userInfo, req.ip)\n } catch (error: AuthorizationResponseError | HttpException | any) {\n if (error instanceof AuthorizationResponseError) {\n this.logger.error({ tag: this.handleCallback.name, msg: `OIDC callback error: ${error.code} - ${error.error_description}` })\n throw new HttpException(error.error_description, HttpStatus.BAD_REQUEST)\n } else {\n this.logger.error({ tag: this.handleCallback.name, msg: `OIDC callback error: ${error}` })\n throw new HttpException(\n error.error_description ?? 'OIDC authentication failed',\n error instanceof HttpException ? error.getStatus() : (error.status ?? HttpStatus.INTERNAL_SERVER_ERROR)\n )\n }\n } finally {\n // Always clear temporary OIDC cookies (success or failure)\n Object.values(OAuthCookie).forEach((value) => {\n res.clearCookie(value, { path: '/' })\n })\n }\n }\n\n getRedirectCallbackUrl(accessExpiration: number, refreshExpiration: number) {\n if (!this.frontendBaseUrl) {\n const url = new URL(this.oidcConfig.redirectUri)\n const apiIndex = url.pathname.indexOf(AUTH_ROUTE.BASE)\n this.frontendBaseUrl = apiIndex >= 0 ? `${url.origin}${url.pathname.slice(0, apiIndex)}` : url.origin\n }\n const url = new URL(this.frontendBaseUrl)\n const params = new URLSearchParams({\n [AUTH_PROVIDER.OIDC]: 'true',\n [`${TOKEN_TYPE.ACCESS}_expiration`]: `${accessExpiration}`,\n [`${TOKEN_TYPE.REFRESH}_expiration`]: `${refreshExpiration}`\n })\n url.hash = `/?${params.toString()}`\n return url.toString()\n }\n\n getRedirectURI(desktopPort?: number | string): string {\n // web / default callback\n if (!desktopPort) return this.oidcConfig.redirectUri\n // desktop app callback\n if (typeof desktopPort === 'string') {\n desktopPort = Number(desktopPort)\n }\n if (!Number.isInteger(desktopPort) || !OAuthDesktopLoopbackPorts.has(desktopPort)) {\n throw new HttpException('Invalid desktop_port', HttpStatus.BAD_REQUEST)\n }\n // The redirect url must be known from provider\n return `http://127.0.0.1:${desktopPort}${OAuthDesktopCallBackURI}`\n }\n\n private async initializeOIDCClient(): Promise<Configuration> {\n try {\n const issuerUrl = new URL(this.oidcConfig.issuerUrl)\n const config: Configuration = await discovery(\n issuerUrl,\n this.oidcConfig.clientId,\n {\n client_secret: this.oidcConfig.clientSecret,\n response_types: ['code'],\n id_token_signed_response_alg: this.oidcConfig.security.tokenSigningAlg,\n userinfo_signed_response_alg: this.oidcConfig.security.userInfoSigningAlg\n },\n this.getTokenAuthMethod(this.oidcConfig.security.tokenEndpointAuthMethod, this.oidcConfig.clientSecret),\n {\n execute: [allowInsecureRequests],\n timeout: 6000\n }\n )\n this.logger.log({ tag: this.initializeOIDCClient.name, msg: `OIDC client initialized successfully for issuer: ${this.oidcConfig.issuerUrl}` })\n return config\n } catch (error) {\n this.logger.error({ tag: this.initializeOIDCClient.name, msg: `OIDC client initialization failed: ${error?.cause || error}` })\n switch (error.cause?.code) {\n case 'ECONNREFUSED':\n case 'ENOTFOUND':\n throw new HttpException('OIDC provider unavailable', HttpStatus.SERVICE_UNAVAILABLE)\n\n case 'ETIMEDOUT':\n throw new HttpException('OIDC provider timeout', HttpStatus.GATEWAY_TIMEOUT)\n\n default:\n throw new HttpException('OIDC client initialization failed', HttpStatus.INTERNAL_SERVER_ERROR)\n }\n }\n }\n\n private getTokenAuthMethod(tokenEndpointAuthMethod: OAuthTokenEndpoint, clientSecret?: string) {\n if (!clientSecret) {\n return None()\n }\n\n switch (tokenEndpointAuthMethod) {\n case OAuthTokenEndpoint.ClientSecretPost: {\n return ClientSecretPost(clientSecret)\n }\n\n case OAuthTokenEndpoint.ClientSecretBasic: {\n return ClientSecretBasic(clientSecret)\n }\n\n default: {\n return None()\n }\n }\n }\n\n private isPKCEEnabled(config: Configuration): boolean {\n return (this.oidcConfig.security.supportPKCE ?? true) && config.serverMetadata().supportsPKCE()\n }\n\n private async processUserInfo(userInfo: UserInfoResponse, ip?: string): Promise<UserModel> {\n // Extract user information\n const { login, email } = this.extractLoginAndEmail(userInfo)\n\n // Check if user exists\n let user: UserModel = await this.usersManager.findUser(email || login, false)\n\n if (!user && !this.oidcConfig.options.autoCreateUser) {\n this.logger.warn({ tag: this.processUserInfo.name, msg: `User not found and autoCreateUser is disabled` })\n throw new HttpException('User not found', HttpStatus.UNAUTHORIZED)\n }\n\n // Determine if user should be admin based on groups/roles\n const isAdmin = this.checkAdminRole(userInfo)\n\n // Create identity\n const identity = this.createIdentity(login, email, userInfo, isAdmin)\n\n // Create or update user\n user = await this.updateOrCreateUser(identity, user)\n\n // Update user access log\n this.usersManager.updateAccesses(user, ip, true).catch((e: Error) => this.logger.error({ tag: this.processUserInfo.name, msg: `${e}` }))\n\n return user\n }\n\n private checkAdminRole(userInfo: UserInfoResponse): boolean {\n if (!this.oidcConfig.options.adminRoleOrGroup) {\n return false\n }\n\n // Check claims\n const claims = [...(Array.isArray(userInfo.groups) ? userInfo.groups : []), ...(Array.isArray(userInfo.roles) ? userInfo.roles : [])]\n\n return claims.includes(this.oidcConfig.options.adminRoleOrGroup)\n }\n\n private createIdentity(\n login: string,\n email: string,\n userInfo: UserInfoResponse,\n isAdmin: boolean\n ): Omit<CreateUserDto, 'password'> & { password?: string } {\n // Get first name and last name\n let firstName = userInfo.given_name || ''\n let lastName = userInfo.family_name || ''\n\n // If structured names not available, try to split the full name\n if (!firstName && !lastName && userInfo.name) {\n const names = splitFullName(userInfo.name)\n firstName = names.firstName\n lastName = names.lastName\n }\n\n return {\n login,\n email,\n role: isAdmin ? USER_ROLE.ADMINISTRATOR : USER_ROLE.USER,\n firstName,\n lastName\n }\n }\n\n private async updateOrCreateUser(identity: Omit<CreateUserDto, 'password'> & { password?: string }, user: UserModel | null): Promise<UserModel> {\n if (user === null) {\n // Create new user with a random password (required by the system but not used for OIDC login)\n const userWithPassword = {\n ...identity,\n password: generateShortUUID(24),\n permissions: this.oidcConfig.options.autoCreatePermissions.join(',')\n } as CreateUserDto\n const createdUser = await this.adminUsersManager.createUserOrGuest(userWithPassword, identity.role)\n const freshUser = await this.usersManager.fromUserId(createdUser.id)\n if (!freshUser) {\n this.logger.error({ tag: this.updateOrCreateUser.name, msg: `user was not found : ${createdUser.login} (${createdUser.id})` })\n throw new HttpException('User not found', HttpStatus.NOT_FOUND)\n }\n return freshUser\n }\n\n // Check if user information has changed (excluding password)\n const identityHasChanged: UpdateUserDto = Object.fromEntries(\n Object.keys(identity)\n .filter((key) => key !== 'password')\n .map((key: string) => (identity[key] !== user[key] ? [key, identity[key]] : null))\n .filter(Boolean)\n )\n\n if (Object.keys(identityHasChanged).length > 0) {\n try {\n if (identityHasChanged?.role != null) {\n if (user.role === USER_ROLE.ADMINISTRATOR && !this.oidcConfig.options.adminRoleOrGroup) {\n // Prevent removing the admin role when adminGroup was removed or not defined\n delete identityHasChanged.role\n }\n }\n\n // Update user properties\n await this.adminUsersManager.updateUserOrGuest(user.id, identityHasChanged)\n\n // Update local user object\n Object.assign(user, identityHasChanged)\n\n if ('lastName' in identityHasChanged || 'firstName' in identityHasChanged) {\n // Force fullName update in the current user model\n user.setFullName(true)\n }\n } catch (e) {\n this.logger.warn({ tag: this.updateOrCreateUser.name, msg: `unable to update user *${user.login}* : ${e}` })\n }\n }\n\n return user\n }\n\n private extractLoginAndEmail(userInfo: UserInfoResponse) {\n const email = userInfo.email ? userInfo.email.trim() : undefined\n if (!email) {\n throw new HttpException('No email address found in the OIDC profile', HttpStatus.BAD_REQUEST)\n }\n\n const login = userInfo.preferred_username ?? (email ? email.split('@')[0] : undefined) ?? userInfo.sub\n if (!login) {\n throw new HttpException('Unable to determine the OIDC profile login', HttpStatus.BAD_REQUEST)\n }\n\n return { login: login.trim().toLowerCase(), email }\n }\n}\n"],"names":["AuthProviderOIDC","validateUser","login","password","ip","scope","user","usersManager","findUser","isGuest","isAdmin","oidcConfig","options","enablePasswordAuth","logUser","getConfig","config","initializeOIDCClient","getAuthorizationUrl","res","desktopPort","redirectURI","getRedirectURI","state","randomState","nonce","randomNonce","isPKCEEnabled","codeVerifier","randomPKCECodeVerifier","undefined","authUrl","URL","serverMetadata","authorization_endpoint","searchParams","set","clientId","security","codeChallenge","calculatePKCECodeChallenge","header","setCookie","OAuthCookie","State","OAuthCookieSettings","Nonce","CodeVerifier","toString","handleCallback","req","query","expectedState","expectedNonce","cookies","length","HttpException","HttpStatus","BAD_REQUEST","pkceCodeVerifier","callbackParams","URLSearchParams","get","OAuthDesktopPortParam","delete","callbackUrl","search","tokens","authorizationCodeGrant","claims","sub","subject","skipSubjectCheck","userInfo","fetchUserInfo","access_token","Error","processUserInfo","error","AuthorizationResponseError","logger","tag","name","msg","code","error_description","getStatus","status","INTERNAL_SERVER_ERROR","Object","values","forEach","value","clearCookie","path","getRedirectCallbackUrl","accessExpiration","refreshExpiration","frontendBaseUrl","url","redirectUri","apiIndex","pathname","indexOf","AUTH_ROUTE","BASE","origin","slice","params","AUTH_PROVIDER","OIDC","TOKEN_TYPE","ACCESS","REFRESH","hash","Number","isInteger","OAuthDesktopLoopbackPorts","has","OAuthDesktopCallBackURI","issuerUrl","discovery","client_secret","clientSecret","response_types","id_token_signed_response_alg","tokenSigningAlg","userinfo_signed_response_alg","userInfoSigningAlg","getTokenAuthMethod","tokenEndpointAuthMethod","execute","allowInsecureRequests","timeout","log","cause","SERVICE_UNAVAILABLE","GATEWAY_TIMEOUT","None","OAuthTokenEndpoint","ClientSecretPost","ClientSecretBasic","supportPKCE","supportsPKCE","email","extractLoginAndEmail","autoCreateUser","warn","UNAUTHORIZED","checkAdminRole","identity","createIdentity","updateOrCreateUser","updateAccesses","catch","e","adminRoleOrGroup","Array","isArray","groups","roles","includes","firstName","given_name","lastName","family_name","names","splitFullName","role","USER_ROLE","ADMINISTRATOR","USER","userWithPassword","generateShortUUID","permissions","autoCreatePermissions","join","createdUser","adminUsersManager","createUserOrGuest","freshUser","fromUserId","id","NOT_FOUND","identityHasChanged","fromEntries","keys","filter","key","map","Boolean","updateUserOrGuest","assign","setFullName","trim","preferred_username","split","toLowerCase","Logger","configuration","auth","oidc"],"mappings":"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"}
@@ -20,6 +20,7 @@ jest.mock('../../../configuration/config.environment', ()=>({
20
20
  redirectUri: 'https://api.example.test/auth/oidc/callback',
21
21
  security: {
22
22
  scope: 'openid profile email',
23
+ supportPKCE: true,
23
24
  tokenSigningAlg: 'RS256',
24
25
  userInfoSigningAlg: 'RS256',
25
26
  tokenEndpointAuthMethod: 'client_secret_basic',
@@ -150,6 +151,21 @@ describe(_authprovideroidcservice.AuthProviderOIDC.name, ()=>{
150
151
  expect(url.searchParams.get('code_challenge')).toBe('challenge-1');
151
152
  expect(url.searchParams.get('client_id')).toBe('client-id');
152
153
  });
154
+ it('does not use PKCE when supportPKCE is false', async ()=>{
155
+ ;
156
+ service.oidcConfig.security.supportPKCE = false;
157
+ jest.spyOn(service, 'getConfig').mockResolvedValue(makeConfig(true));
158
+ _openidclient.randomState.mockReturnValue('state-1');
159
+ _openidclient.randomNonce.mockReturnValue('nonce-1');
160
+ const reply = makeReply();
161
+ const authUrl = await service.getAuthorizationUrl(reply);
162
+ expect(_openidclient.randomPKCECodeVerifier).not.toHaveBeenCalled();
163
+ expect(_openidclient.calculatePKCECodeChallenge).not.toHaveBeenCalled();
164
+ expect(reply.setCookie).not.toHaveBeenCalledWith(_authoidcconstants.OAuthCookie.CodeVerifier, expect.anything(), expect.any(Object));
165
+ const url = new URL(authUrl);
166
+ expect(url.searchParams.get('code_challenge')).toBeNull();
167
+ service.oidcConfig.security.supportPKCE = true;
168
+ });
153
169
  it('handles callback success and clears cookies', async ()=>{
154
170
  const config = makeConfig(true);
155
171
  jest.spyOn(service, 'getConfig').mockResolvedValue(config);
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../backend/src/authentication/providers/oidc/auth-provider-oidc.service.spec.ts"],"sourcesContent":["import { HttpStatus } from '@nestjs/common'\nimport { Test, TestingModule } from '@nestjs/testing'\nimport {\n authorizationCodeGrant,\n AuthorizationResponseError,\n calculatePKCECodeChallenge,\n fetchUserInfo,\n randomNonce,\n randomPKCECodeVerifier,\n randomState\n} from 'openid-client'\nimport { USER_ROLE } from '../../../applications/users/constants/user'\nimport { AdminUsersManager } from '../../../applications/users/services/admin-users-manager.service'\nimport { UsersManager } from '../../../applications/users/services/users-manager.service'\nimport { OAuthCookie } from './auth-oidc.constants'\nimport { AuthProviderOIDC } from './auth-provider-oidc.service'\n\njest.mock('../../../configuration/config.environment', () => ({\n configuration: {\n auth: {\n oidc: {\n issuerUrl: 'https://issuer.example.test',\n clientId: 'client-id',\n clientSecret: 'client-secret',\n redirectUri: 'https://api.example.test/auth/oidc/callback',\n security: {\n scope: 'openid profile email',\n tokenSigningAlg: 'RS256',\n userInfoSigningAlg: 'RS256',\n tokenEndpointAuthMethod: 'client_secret_basic',\n skipSubjectCheck: false\n },\n options: {\n enablePasswordAuth: false,\n autoCreateUser: true,\n adminRoleOrGroup: 'admins',\n autoCreatePermissions: ['read']\n }\n }\n }\n }\n}))\n\njest.mock('openid-client', () => {\n class AuthorizationResponseError extends Error {\n code: string\n error_description: string\n constructor(message: string, options: { cause: URLSearchParams }) {\n super(message)\n this.code = 'authorization_response_error'\n this.error_description = options?.cause?.get('error_description') ?? message\n }\n }\n\n return {\n allowInsecureRequests: jest.fn(),\n authorizationCodeGrant: jest.fn(),\n AuthorizationResponseError,\n calculatePKCECodeChallenge: jest.fn(),\n ClientSecretBasic: jest.fn(),\n ClientSecretPost: jest.fn(),\n Configuration: class {},\n discovery: jest.fn(),\n fetchUserInfo: jest.fn(),\n IDToken: class {},\n None: jest.fn(),\n randomNonce: jest.fn(),\n randomPKCECodeVerifier: jest.fn(),\n randomState: jest.fn(),\n skipSubjectCheck: Symbol('skipSubjectCheck'),\n UserInfoResponse: class {}\n }\n})\n\ndescribe(AuthProviderOIDC.name, () => {\n let service: AuthProviderOIDC\n let usersManager: {\n findUser: jest.Mock\n logUser: jest.Mock\n updateAccesses: jest.Mock\n fromUserId: jest.Mock\n }\n let adminUsersManager: {\n createUserOrGuest: jest.Mock\n updateUserOrGuest: jest.Mock\n }\n\n const makeConfig = (supportsPKCE = true) => ({\n serverMetadata: () => ({\n supportsPKCE: () => supportsPKCE,\n authorization_endpoint: 'https://issuer.example.test/authorize'\n })\n })\n\n const makeReply = () => ({\n header: jest.fn().mockReturnThis(),\n setCookie: jest.fn(),\n clearCookie: jest.fn()\n })\n\n beforeAll(async () => {\n usersManager = {\n findUser: jest.fn(),\n logUser: jest.fn(),\n updateAccesses: jest.fn().mockResolvedValue(undefined),\n fromUserId: jest.fn()\n }\n adminUsersManager = {\n createUserOrGuest: jest.fn(),\n updateUserOrGuest: jest.fn()\n }\n\n const module: TestingModule = await Test.createTestingModule({\n providers: [{ provide: UsersManager, useValue: usersManager }, { provide: AdminUsersManager, useValue: adminUsersManager }, AuthProviderOIDC]\n }).compile()\n\n module.useLogger(['fatal'])\n service = module.get<AuthProviderOIDC>(AuthProviderOIDC)\n })\n\n beforeEach(() => {\n jest.restoreAllMocks()\n jest.clearAllMocks()\n })\n\n it('returns null when user is not found', async () => {\n usersManager.findUser.mockResolvedValue(null)\n\n const result = await service.validateUser('john', 'secret')\n\n expect(result).toBeNull()\n expect(usersManager.findUser).toHaveBeenCalledWith('john', false)\n expect(usersManager.logUser).not.toHaveBeenCalled()\n })\n\n it('allows local password auth for guest users', async () => {\n const guestUser = { id: 1, isGuest: true, isAdmin: false } as any\n usersManager.findUser.mockResolvedValue(guestUser)\n usersManager.logUser.mockResolvedValue(guestUser)\n\n const result = await service.validateUser('guest', 'secret')\n\n expect(usersManager.logUser).toHaveBeenCalledWith(guestUser, 'secret', undefined, undefined)\n expect(result).toBe(guestUser)\n })\n\n it('builds the authorization url with PKCE data and cookies', async () => {\n jest.spyOn(service, 'getConfig').mockResolvedValue(makeConfig(true) as any)\n ;(randomState as jest.Mock).mockReturnValue('state-1')\n ;(randomNonce as jest.Mock).mockReturnValue('nonce-1')\n ;(randomPKCECodeVerifier as jest.Mock).mockReturnValue('verifier-1')\n ;(calculatePKCECodeChallenge as jest.Mock).mockResolvedValue('challenge-1')\n const reply = makeReply()\n\n const authUrl = await service.getAuthorizationUrl(reply as any)\n\n expect(reply.header).toHaveBeenCalled()\n expect(reply.setCookie).toHaveBeenCalledWith(OAuthCookie.State, 'state-1', expect.any(Object))\n expect(reply.setCookie).toHaveBeenCalledWith(OAuthCookie.Nonce, 'nonce-1', expect.any(Object))\n expect(reply.setCookie).toHaveBeenCalledWith(OAuthCookie.CodeVerifier, 'verifier-1', expect.any(Object))\n const url = new URL(authUrl)\n expect(url.searchParams.get('code_challenge')).toBe('challenge-1')\n expect(url.searchParams.get('client_id')).toBe('client-id')\n })\n\n it('handles callback success and clears cookies', async () => {\n const config = makeConfig(true)\n jest.spyOn(service, 'getConfig').mockResolvedValue(config as any)\n const processSpy = jest.spyOn(service as any, 'processUserInfo').mockResolvedValue({ id: 7 } as any)\n ;(authorizationCodeGrant as jest.Mock).mockResolvedValue({\n claims: () => ({ sub: 'subject-1' }),\n access_token: 'access-token'\n })\n ;(fetchUserInfo as jest.Mock).mockResolvedValue({ sub: 'subject-1', email: 'a@b.c', preferred_username: 'alice' })\n const req = {\n cookies: {\n [OAuthCookie.State]: 'state-1',\n [OAuthCookie.Nonce]: 'nonce-1',\n [OAuthCookie.CodeVerifier]: 'verifier-1'\n },\n ip: '127.0.0.1'\n }\n const reply = makeReply()\n\n const result = await service.handleCallback(req as any, reply as any, { code: 'abc' })\n\n expect(result).toEqual({ id: 7 })\n expect(processSpy).toHaveBeenCalledWith({ sub: 'subject-1', email: 'a@b.c', preferred_username: 'alice' }, '127.0.0.1')\n expect(reply.clearCookie).toHaveBeenCalledWith(OAuthCookie.State, { path: '/' })\n expect(reply.clearCookie).toHaveBeenCalledWith(OAuthCookie.Nonce, { path: '/' })\n expect(reply.clearCookie).toHaveBeenCalledWith(OAuthCookie.CodeVerifier, { path: '/' })\n })\n\n it('rejects callback when state is missing', async () => {\n jest.spyOn(service, 'getConfig').mockResolvedValue(makeConfig(false) as any)\n const reply = makeReply()\n const req = { cookies: {}, ip: '127.0.0.1' }\n\n await expect(service.handleCallback(req as any, reply as any, { code: 'abc' })).rejects.toMatchObject({ status: HttpStatus.BAD_REQUEST })\n expect(reply.clearCookie).toHaveBeenCalledWith(OAuthCookie.State, { path: '/' })\n })\n\n it('maps AuthorizationResponseError to BAD_REQUEST', async () => {\n jest.spyOn(service, 'getConfig').mockResolvedValue(makeConfig(false) as any)\n ;(authorizationCodeGrant as jest.Mock).mockRejectedValue(\n new AuthorizationResponseError('access_denied', {\n cause: new URLSearchParams('error=access_denied&error_description=No access')\n })\n )\n const req = {\n cookies: {\n [OAuthCookie.State]: 'state-1',\n [OAuthCookie.Nonce]: 'nonce-1'\n },\n ip: '127.0.0.1'\n }\n const reply = makeReply()\n\n await expect(service.handleCallback(req as any, reply as any, { code: 'abc' })).rejects.toMatchObject({\n status: HttpStatus.BAD_REQUEST,\n message: 'No access'\n })\n })\n\n it('builds the redirect callback url with token expirations', () => {\n const url = service.getRedirectCallbackUrl(10, 20)\n const parsed = new URL(url)\n expect(parsed.hash).toContain('access_expiration=10')\n expect(parsed.hash).toContain('refresh_expiration=20')\n })\n\n it('creates identities with admin role when claims match', async () => {\n usersManager.findUser.mockResolvedValue(null)\n adminUsersManager.createUserOrGuest.mockResolvedValue({ id: 10, login: 'bob' })\n usersManager.fromUserId.mockResolvedValue({ id: 10, role: USER_ROLE.ADMINISTRATOR, login: 'bob', setFullName: jest.fn() } as any)\n const userInfo = { sub: 'x', email: 'b@c.d', preferred_username: 'bob', groups: ['admins'] }\n\n const result = await (service as any).processUserInfo(userInfo, '127.0.0.1')\n\n expect(adminUsersManager.createUserOrGuest).toHaveBeenCalledWith(\n expect.objectContaining({ role: USER_ROLE.ADMINISTRATOR }),\n USER_ROLE.ADMINISTRATOR\n )\n expect(result.role).toBe(USER_ROLE.ADMINISTRATOR)\n })\n})\n"],"names":["jest","mock","configuration","auth","oidc","issuerUrl","clientId","clientSecret","redirectUri","security","scope","tokenSigningAlg","userInfoSigningAlg","tokenEndpointAuthMethod","skipSubjectCheck","options","enablePasswordAuth","autoCreateUser","adminRoleOrGroup","autoCreatePermissions","AuthorizationResponseError","Error","message","code","error_description","cause","get","allowInsecureRequests","fn","authorizationCodeGrant","calculatePKCECodeChallenge","ClientSecretBasic","ClientSecretPost","Configuration","discovery","fetchUserInfo","IDToken","None","randomNonce","randomPKCECodeVerifier","randomState","Symbol","UserInfoResponse","describe","AuthProviderOIDC","name","service","usersManager","adminUsersManager","makeConfig","supportsPKCE","serverMetadata","authorization_endpoint","makeReply","header","mockReturnThis","setCookie","clearCookie","beforeAll","findUser","logUser","updateAccesses","mockResolvedValue","undefined","fromUserId","createUserOrGuest","updateUserOrGuest","module","Test","createTestingModule","providers","provide","UsersManager","useValue","AdminUsersManager","compile","useLogger","beforeEach","restoreAllMocks","clearAllMocks","it","result","validateUser","expect","toBeNull","toHaveBeenCalledWith","not","toHaveBeenCalled","guestUser","id","isGuest","isAdmin","toBe","spyOn","mockReturnValue","reply","authUrl","getAuthorizationUrl","OAuthCookie","State","any","Object","Nonce","CodeVerifier","url","URL","searchParams","config","processSpy","claims","sub","access_token","email","preferred_username","req","cookies","ip","handleCallback","toEqual","path","rejects","toMatchObject","status","HttpStatus","BAD_REQUEST","mockRejectedValue","URLSearchParams","getRedirectCallbackUrl","parsed","hash","toContain","login","role","USER_ROLE","ADMINISTRATOR","setFullName","userInfo","groups","processUserInfo","objectContaining"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../backend/src/authentication/providers/oidc/auth-provider-oidc.service.spec.ts"],"sourcesContent":["import { HttpStatus } from '@nestjs/common'\nimport { Test, TestingModule } from '@nestjs/testing'\nimport {\n authorizationCodeGrant,\n AuthorizationResponseError,\n calculatePKCECodeChallenge,\n fetchUserInfo,\n randomNonce,\n randomPKCECodeVerifier,\n randomState\n} from 'openid-client'\nimport { USER_ROLE } from '../../../applications/users/constants/user'\nimport { AdminUsersManager } from '../../../applications/users/services/admin-users-manager.service'\nimport { UsersManager } from '../../../applications/users/services/users-manager.service'\nimport { OAuthCookie } from './auth-oidc.constants'\nimport { AuthProviderOIDC } from './auth-provider-oidc.service'\n\njest.mock('../../../configuration/config.environment', () => ({\n configuration: {\n auth: {\n oidc: {\n issuerUrl: 'https://issuer.example.test',\n clientId: 'client-id',\n clientSecret: 'client-secret',\n redirectUri: 'https://api.example.test/auth/oidc/callback',\n security: {\n scope: 'openid profile email',\n supportPKCE: true,\n tokenSigningAlg: 'RS256',\n userInfoSigningAlg: 'RS256',\n tokenEndpointAuthMethod: 'client_secret_basic',\n skipSubjectCheck: false\n },\n options: {\n enablePasswordAuth: false,\n autoCreateUser: true,\n adminRoleOrGroup: 'admins',\n autoCreatePermissions: ['read']\n }\n }\n }\n }\n}))\n\njest.mock('openid-client', () => {\n class AuthorizationResponseError extends Error {\n code: string\n error_description: string\n constructor(message: string, options: { cause: URLSearchParams }) {\n super(message)\n this.code = 'authorization_response_error'\n this.error_description = options?.cause?.get('error_description') ?? message\n }\n }\n\n return {\n allowInsecureRequests: jest.fn(),\n authorizationCodeGrant: jest.fn(),\n AuthorizationResponseError,\n calculatePKCECodeChallenge: jest.fn(),\n ClientSecretBasic: jest.fn(),\n ClientSecretPost: jest.fn(),\n Configuration: class {},\n discovery: jest.fn(),\n fetchUserInfo: jest.fn(),\n IDToken: class {},\n None: jest.fn(),\n randomNonce: jest.fn(),\n randomPKCECodeVerifier: jest.fn(),\n randomState: jest.fn(),\n skipSubjectCheck: Symbol('skipSubjectCheck'),\n UserInfoResponse: class {}\n }\n})\n\ndescribe(AuthProviderOIDC.name, () => {\n let service: AuthProviderOIDC\n let usersManager: {\n findUser: jest.Mock\n logUser: jest.Mock\n updateAccesses: jest.Mock\n fromUserId: jest.Mock\n }\n let adminUsersManager: {\n createUserOrGuest: jest.Mock\n updateUserOrGuest: jest.Mock\n }\n\n const makeConfig = (supportsPKCE = true) => ({\n serverMetadata: () => ({\n supportsPKCE: () => supportsPKCE,\n authorization_endpoint: 'https://issuer.example.test/authorize'\n })\n })\n\n const makeReply = () => ({\n header: jest.fn().mockReturnThis(),\n setCookie: jest.fn(),\n clearCookie: jest.fn()\n })\n\n beforeAll(async () => {\n usersManager = {\n findUser: jest.fn(),\n logUser: jest.fn(),\n updateAccesses: jest.fn().mockResolvedValue(undefined),\n fromUserId: jest.fn()\n }\n adminUsersManager = {\n createUserOrGuest: jest.fn(),\n updateUserOrGuest: jest.fn()\n }\n\n const module: TestingModule = await Test.createTestingModule({\n providers: [{ provide: UsersManager, useValue: usersManager }, { provide: AdminUsersManager, useValue: adminUsersManager }, AuthProviderOIDC]\n }).compile()\n\n module.useLogger(['fatal'])\n service = module.get<AuthProviderOIDC>(AuthProviderOIDC)\n })\n\n beforeEach(() => {\n jest.restoreAllMocks()\n jest.clearAllMocks()\n })\n\n it('returns null when user is not found', async () => {\n usersManager.findUser.mockResolvedValue(null)\n\n const result = await service.validateUser('john', 'secret')\n\n expect(result).toBeNull()\n expect(usersManager.findUser).toHaveBeenCalledWith('john', false)\n expect(usersManager.logUser).not.toHaveBeenCalled()\n })\n\n it('allows local password auth for guest users', async () => {\n const guestUser = { id: 1, isGuest: true, isAdmin: false } as any\n usersManager.findUser.mockResolvedValue(guestUser)\n usersManager.logUser.mockResolvedValue(guestUser)\n\n const result = await service.validateUser('guest', 'secret')\n\n expect(usersManager.logUser).toHaveBeenCalledWith(guestUser, 'secret', undefined, undefined)\n expect(result).toBe(guestUser)\n })\n\n it('builds the authorization url with PKCE data and cookies', async () => {\n jest.spyOn(service, 'getConfig').mockResolvedValue(makeConfig(true) as any)\n ;(randomState as jest.Mock).mockReturnValue('state-1')\n ;(randomNonce as jest.Mock).mockReturnValue('nonce-1')\n ;(randomPKCECodeVerifier as jest.Mock).mockReturnValue('verifier-1')\n ;(calculatePKCECodeChallenge as jest.Mock).mockResolvedValue('challenge-1')\n const reply = makeReply()\n\n const authUrl = await service.getAuthorizationUrl(reply as any)\n\n expect(reply.header).toHaveBeenCalled()\n expect(reply.setCookie).toHaveBeenCalledWith(OAuthCookie.State, 'state-1', expect.any(Object))\n expect(reply.setCookie).toHaveBeenCalledWith(OAuthCookie.Nonce, 'nonce-1', expect.any(Object))\n expect(reply.setCookie).toHaveBeenCalledWith(OAuthCookie.CodeVerifier, 'verifier-1', expect.any(Object))\n const url = new URL(authUrl)\n expect(url.searchParams.get('code_challenge')).toBe('challenge-1')\n expect(url.searchParams.get('client_id')).toBe('client-id')\n })\n\n it('does not use PKCE when supportPKCE is false', async () => {\n ;(service as any).oidcConfig.security.supportPKCE = false\n jest.spyOn(service, 'getConfig').mockResolvedValue(makeConfig(true) as any)\n ;(randomState as jest.Mock).mockReturnValue('state-1')\n ;(randomNonce as jest.Mock).mockReturnValue('nonce-1')\n const reply = makeReply()\n\n const authUrl = await service.getAuthorizationUrl(reply as any)\n\n expect(randomPKCECodeVerifier).not.toHaveBeenCalled()\n expect(calculatePKCECodeChallenge).not.toHaveBeenCalled()\n expect(reply.setCookie).not.toHaveBeenCalledWith(OAuthCookie.CodeVerifier, expect.anything(), expect.any(Object))\n const url = new URL(authUrl)\n expect(url.searchParams.get('code_challenge')).toBeNull()\n ;(service as any).oidcConfig.security.supportPKCE = true\n })\n\n it('handles callback success and clears cookies', async () => {\n const config = makeConfig(true)\n jest.spyOn(service, 'getConfig').mockResolvedValue(config as any)\n const processSpy = jest.spyOn(service as any, 'processUserInfo').mockResolvedValue({ id: 7 } as any)\n ;(authorizationCodeGrant as jest.Mock).mockResolvedValue({\n claims: () => ({ sub: 'subject-1' }),\n access_token: 'access-token'\n })\n ;(fetchUserInfo as jest.Mock).mockResolvedValue({ sub: 'subject-1', email: 'a@b.c', preferred_username: 'alice' })\n const req = {\n cookies: {\n [OAuthCookie.State]: 'state-1',\n [OAuthCookie.Nonce]: 'nonce-1',\n [OAuthCookie.CodeVerifier]: 'verifier-1'\n },\n ip: '127.0.0.1'\n }\n const reply = makeReply()\n\n const result = await service.handleCallback(req as any, reply as any, { code: 'abc' })\n\n expect(result).toEqual({ id: 7 })\n expect(processSpy).toHaveBeenCalledWith({ sub: 'subject-1', email: 'a@b.c', preferred_username: 'alice' }, '127.0.0.1')\n expect(reply.clearCookie).toHaveBeenCalledWith(OAuthCookie.State, { path: '/' })\n expect(reply.clearCookie).toHaveBeenCalledWith(OAuthCookie.Nonce, { path: '/' })\n expect(reply.clearCookie).toHaveBeenCalledWith(OAuthCookie.CodeVerifier, { path: '/' })\n })\n\n it('rejects callback when state is missing', async () => {\n jest.spyOn(service, 'getConfig').mockResolvedValue(makeConfig(false) as any)\n const reply = makeReply()\n const req = { cookies: {}, ip: '127.0.0.1' }\n\n await expect(service.handleCallback(req as any, reply as any, { code: 'abc' })).rejects.toMatchObject({ status: HttpStatus.BAD_REQUEST })\n expect(reply.clearCookie).toHaveBeenCalledWith(OAuthCookie.State, { path: '/' })\n })\n\n it('maps AuthorizationResponseError to BAD_REQUEST', async () => {\n jest.spyOn(service, 'getConfig').mockResolvedValue(makeConfig(false) as any)\n ;(authorizationCodeGrant as jest.Mock).mockRejectedValue(\n new AuthorizationResponseError('access_denied', {\n cause: new URLSearchParams('error=access_denied&error_description=No access')\n })\n )\n const req = {\n cookies: {\n [OAuthCookie.State]: 'state-1',\n [OAuthCookie.Nonce]: 'nonce-1'\n },\n ip: '127.0.0.1'\n }\n const reply = makeReply()\n\n await expect(service.handleCallback(req as any, reply as any, { code: 'abc' })).rejects.toMatchObject({\n status: HttpStatus.BAD_REQUEST,\n message: 'No access'\n })\n })\n\n it('builds the redirect callback url with token expirations', () => {\n const url = service.getRedirectCallbackUrl(10, 20)\n const parsed = new URL(url)\n expect(parsed.hash).toContain('access_expiration=10')\n expect(parsed.hash).toContain('refresh_expiration=20')\n })\n\n it('creates identities with admin role when claims match', async () => {\n usersManager.findUser.mockResolvedValue(null)\n adminUsersManager.createUserOrGuest.mockResolvedValue({ id: 10, login: 'bob' })\n usersManager.fromUserId.mockResolvedValue({ id: 10, role: USER_ROLE.ADMINISTRATOR, login: 'bob', setFullName: jest.fn() } as any)\n const userInfo = { sub: 'x', email: 'b@c.d', preferred_username: 'bob', groups: ['admins'] }\n\n const result = await (service as any).processUserInfo(userInfo, '127.0.0.1')\n\n expect(adminUsersManager.createUserOrGuest).toHaveBeenCalledWith(\n expect.objectContaining({ role: USER_ROLE.ADMINISTRATOR }),\n USER_ROLE.ADMINISTRATOR\n )\n expect(result.role).toBe(USER_ROLE.ADMINISTRATOR)\n })\n})\n"],"names":["jest","mock","configuration","auth","oidc","issuerUrl","clientId","clientSecret","redirectUri","security","scope","supportPKCE","tokenSigningAlg","userInfoSigningAlg","tokenEndpointAuthMethod","skipSubjectCheck","options","enablePasswordAuth","autoCreateUser","adminRoleOrGroup","autoCreatePermissions","AuthorizationResponseError","Error","message","code","error_description","cause","get","allowInsecureRequests","fn","authorizationCodeGrant","calculatePKCECodeChallenge","ClientSecretBasic","ClientSecretPost","Configuration","discovery","fetchUserInfo","IDToken","None","randomNonce","randomPKCECodeVerifier","randomState","Symbol","UserInfoResponse","describe","AuthProviderOIDC","name","service","usersManager","adminUsersManager","makeConfig","supportsPKCE","serverMetadata","authorization_endpoint","makeReply","header","mockReturnThis","setCookie","clearCookie","beforeAll","findUser","logUser","updateAccesses","mockResolvedValue","undefined","fromUserId","createUserOrGuest","updateUserOrGuest","module","Test","createTestingModule","providers","provide","UsersManager","useValue","AdminUsersManager","compile","useLogger","beforeEach","restoreAllMocks","clearAllMocks","it","result","validateUser","expect","toBeNull","toHaveBeenCalledWith","not","toHaveBeenCalled","guestUser","id","isGuest","isAdmin","toBe","spyOn","mockReturnValue","reply","authUrl","getAuthorizationUrl","OAuthCookie","State","any","Object","Nonce","CodeVerifier","url","URL","searchParams","oidcConfig","anything","config","processSpy","claims","sub","access_token","email","preferred_username","req","cookies","ip","handleCallback","toEqual","path","rejects","toMatchObject","status","HttpStatus","BAD_REQUEST","mockRejectedValue","URLSearchParams","getRedirectCallbackUrl","parsed","hash","toContain","login","role","USER_ROLE","ADMINISTRATOR","setFullName","userInfo","groups","processUserInfo","objectContaining"],"mappings":"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"}
@@ -29,6 +29,7 @@ const LANG_SUPPORTED = new Set([
29
29
  'it',
30
30
  'ja',
31
31
  'ko',
32
+ 'nl',
32
33
  'pl',
33
34
  'pt',
34
35
  'pt-BR',
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../backend/src/common/i18n.ts"],"sourcesContent":["export const LANG_DEFAULT = 'en' as const\nexport type i18nLocaleSupported = 'de' | 'en' | 'es' | 'fr' | 'hi' | 'it' | 'ja' | 'ko' | 'pl' | 'pt' | 'pt-BR' | 'ru' | 'tr' | 'zh'\nexport const LANG_SUPPORTED = new Set<i18nLocaleSupported>(['de', 'en', 'es', 'fr', 'hi', 'it', 'ja', 'ko', 'pl', 'pt', 'pt-BR', 'ru', 'tr', 'zh'])\nexport type i18nLocale = Exclude<i18nLocaleSupported, typeof LANG_DEFAULT>\n\nexport function normalizeLanguage(language: string): i18nLocaleSupported | null {\n if (!language) return null\n if (LANG_SUPPORTED.has(language as i18nLocaleSupported)) {\n return language as i18nLocaleSupported\n }\n const code = language.split('-')[0]\n return LANG_SUPPORTED.has(code as i18nLocaleSupported) ? (code as i18nLocaleSupported) : null\n}\n"],"names":["LANG_DEFAULT","LANG_SUPPORTED","normalizeLanguage","Set","language","has","code","split"],"mappings":";;;;;;;;;;;QAAaA;eAAAA;;QAEAC;eAAAA;;QAGGC;eAAAA;;;AALT,MAAMF,eAAe;AAErB,MAAMC,iBAAiB,IAAIE,IAAyB;IAAC;IAAM;IAAM;IAAM;IAAM;IAAM;IAAM;IAAM;IAAM;IAAM;IAAM;IAAS;IAAM;IAAM;CAAK;AAG3I,SAASD,kBAAkBE,QAAgB;IAChD,IAAI,CAACA,UAAU,OAAO;IACtB,IAAIH,eAAeI,GAAG,CAACD,WAAkC;QACvD,OAAOA;IACT;IACA,MAAME,OAAOF,SAASG,KAAK,CAAC,IAAI,CAAC,EAAE;IACnC,OAAON,eAAeI,GAAG,CAACC,QAAgCA,OAA+B;AAC3F"}
1
+ {"version":3,"sources":["../../../backend/src/common/i18n.ts"],"sourcesContent":["export const LANG_DEFAULT = 'en' as const\nexport const LANG_SUPPORTED = new Set(['de', 'en', 'es', 'fr', 'hi', 'it', 'ja', 'ko', 'nl', 'pl', 'pt', 'pt-BR', 'ru', 'tr', 'zh'] as const)\nexport type i18nLocaleSupported = typeof LANG_SUPPORTED extends Set<infer T> ? T : never\nexport type i18nLocale = Exclude<i18nLocaleSupported, typeof LANG_DEFAULT>\n\nexport function normalizeLanguage(language: string): i18nLocaleSupported | null {\n if (!language) return null\n if (LANG_SUPPORTED.has(language as i18nLocaleSupported)) {\n return language as i18nLocaleSupported\n }\n const code = language.split('-')[0]\n return LANG_SUPPORTED.has(code as i18nLocaleSupported) ? (code as i18nLocaleSupported) : null\n}\n"],"names":["LANG_DEFAULT","LANG_SUPPORTED","normalizeLanguage","Set","language","has","code","split"],"mappings":";;;;;;;;;;;QAAaA;eAAAA;;QACAC;eAAAA;;QAIGC;eAAAA;;;AALT,MAAMF,eAAe;AACrB,MAAMC,iBAAiB,IAAIE,IAAI;IAAC;IAAM;IAAM;IAAM;IAAM;IAAM;IAAM;IAAM;IAAM;IAAM;IAAM;IAAM;IAAS;IAAM;IAAM;CAAK;AAI5H,SAASD,kBAAkBE,QAAgB;IAChD,IAAI,CAACA,UAAU,OAAO;IACtB,IAAIH,eAAeI,GAAG,CAACD,WAAkC;QACvD,OAAOA;IACT;IACA,MAAME,OAAOF,SAASG,KAAK,CAAC,IAAI,CAAC,EAAE;IACnC,OAAON,eAAeI,GAAG,CAACC,QAAgCA,OAA+B;AAC3F"}
@@ -70,6 +70,13 @@ function _ts_param(paramIndex, decorator) {
70
70
  };
71
71
  }
72
72
  let DatabaseModule = class DatabaseModule {
73
+ onModuleInit() {
74
+ const pool = this.db.$client;
75
+ pool.on('connection', (conn)=>{
76
+ // Force UTC timezone for every new MySQL connection.
77
+ conn.query(`SET time_zone = '+00:00'`);
78
+ });
79
+ }
73
80
  async beforeApplicationShutdown() {
74
81
  await this.db.session.client.end();
75
82
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../backend/src/infrastructure/database/database.module.ts"],"sourcesContent":["import { DrizzleMySqlConfig, DrizzleMySqlModule } from '@knaadh/nestjs-drizzle-mysql2'\nimport { BeforeApplicationShutdown, Global, Inject, Module } from '@nestjs/common'\nimport { MySql2Client } from 'drizzle-orm/mysql2'\nimport { configuration } from '../../configuration/config.environment'\nimport { DB_TOKEN_PROVIDER } from './constants'\nimport { DatabaseLogger } from './database.logger'\nimport type { DBSchema } from './interfaces/database.interface'\nimport * as schema from './schema'\n\n@Global()\n@Module({\n imports: [\n DrizzleMySqlModule.registerAsync({\n tag: DB_TOKEN_PROVIDER,\n useFactory: async (): Promise<DrizzleMySqlConfig> => ({\n mysql: {\n connection: 'pool',\n config: configuration.mysql.url\n },\n config: {\n schema: { ...schema },\n mode: 'default',\n logger: configuration.mysql.logQueries ? new DatabaseLogger() : false\n }\n })\n })\n ]\n})\nexport class DatabaseModule implements BeforeApplicationShutdown {\n constructor(@Inject(DB_TOKEN_PROVIDER) private readonly db: DBSchema & { session: { client: MySql2Client } }) {}\n\n async beforeApplicationShutdown() {\n await this.db.session.client.end()\n }\n}\n"],"names":["DatabaseModule","beforeApplicationShutdown","db","session","client","end","imports","DrizzleMySqlModule","registerAsync","tag","DB_TOKEN_PROVIDER","useFactory","mysql","connection","config","configuration","url","schema","mode","logger","logQueries","DatabaseLogger"],"mappings":";;;;+BA4BaA;;;eAAAA;;;qCA5B0C;wBACW;mCAEpC;2BACI;gCACH;gEAEP;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqBjB,IAAA,AAAMA,iBAAN,MAAMA;IAGX,MAAMC,4BAA4B;QAChC,MAAM,IAAI,CAACC,EAAE,CAACC,OAAO,CAACC,MAAM,CAACC,GAAG;IAClC;IAJA,YAAY,AAA4CH,EAAoD,CAAE;aAAtDA,KAAAA;IAAuD;AAKjH;;;;QAvBEI,SAAS;YACPC,uCAAkB,CAACC,aAAa,CAAC;gBAC/BC,KAAKC,4BAAiB;gBACtBC,YAAY,UAA0C,CAAA;wBACpDC,OAAO;4BACLC,YAAY;4BACZC,QAAQC,gCAAa,CAACH,KAAK,CAACI,GAAG;wBACjC;wBACAF,QAAQ;4BACNG,QAAQ;gCAAE,GAAGA,OAAM;4BAAC;4BACpBC,MAAM;4BACNC,QAAQJ,gCAAa,CAACH,KAAK,CAACQ,UAAU,GAAG,IAAIC,8BAAc,KAAK;wBAClE;oBACF,CAAA;YACF;SACD"}
1
+ {"version":3,"sources":["../../../../backend/src/infrastructure/database/database.module.ts"],"sourcesContent":["import { DrizzleMySqlConfig, DrizzleMySqlModule } from '@knaadh/nestjs-drizzle-mysql2'\nimport { BeforeApplicationShutdown, Global, Inject, Module, OnModuleInit } from '@nestjs/common'\nimport { MySql2Client } from 'drizzle-orm/mysql2'\nimport { Connection, Pool } from 'mysql2'\nimport { configuration } from '../../configuration/config.environment'\nimport { DB_TOKEN_PROVIDER } from './constants'\nimport { DatabaseLogger } from './database.logger'\nimport type { DBSchema } from './interfaces/database.interface'\nimport * as schema from './schema'\n\n@Global()\n@Module({\n imports: [\n DrizzleMySqlModule.registerAsync({\n tag: DB_TOKEN_PROVIDER,\n useFactory: async (): Promise<DrizzleMySqlConfig> => ({\n mysql: {\n connection: 'pool',\n config: configuration.mysql.url\n },\n config: {\n schema: { ...schema },\n mode: 'default',\n logger: configuration.mysql.logQueries ? new DatabaseLogger() : false\n }\n })\n })\n ]\n})\nexport class DatabaseModule implements OnModuleInit, BeforeApplicationShutdown {\n constructor(@Inject(DB_TOKEN_PROVIDER) private readonly db: DBSchema & { session: { client: MySql2Client } }) {}\n\n onModuleInit() {\n const pool: Pool = (this.db as any).$client\n pool.on('connection', (conn: Connection) => {\n // Force UTC timezone for every new MySQL connection.\n conn.query(`SET time_zone = '+00:00'`)\n })\n }\n\n async beforeApplicationShutdown() {\n await this.db.session.client.end()\n }\n}\n"],"names":["DatabaseModule","onModuleInit","pool","db","$client","on","conn","query","beforeApplicationShutdown","session","client","end","imports","DrizzleMySqlModule","registerAsync","tag","DB_TOKEN_PROVIDER","useFactory","mysql","connection","config","configuration","url","schema","mode","logger","logQueries","DatabaseLogger"],"mappings":";;;;+BA6BaA;;;eAAAA;;;qCA7B0C;wBACyB;mCAGlD;2BACI;gCACH;gEAEP;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqBjB,IAAA,AAAMA,iBAAN,MAAMA;IAGXC,eAAe;QACb,MAAMC,OAAa,AAAC,IAAI,CAACC,EAAE,CAASC,OAAO;QAC3CF,KAAKG,EAAE,CAAC,cAAc,CAACC;YACrB,qDAAqD;YACrDA,KAAKC,KAAK,CAAC,CAAC,wBAAwB,CAAC;QACvC;IACF;IAEA,MAAMC,4BAA4B;QAChC,MAAM,IAAI,CAACL,EAAE,CAACM,OAAO,CAACC,MAAM,CAACC,GAAG;IAClC;IAZA,YAAY,AAA4CR,EAAoD,CAAE;aAAtDA,KAAAA;IAAuD;AAajH;;;;QA/BES,SAAS;YACPC,uCAAkB,CAACC,aAAa,CAAC;gBAC/BC,KAAKC,4BAAiB;gBACtBC,YAAY,UAA0C,CAAA;wBACpDC,OAAO;4BACLC,YAAY;4BACZC,QAAQC,gCAAa,CAACH,KAAK,CAACI,GAAG;wBACjC;wBACAF,QAAQ;4BACNG,QAAQ;gCAAE,GAAGA,OAAM;4BAAC;4BACpBC,MAAM;4BACNC,QAAQJ,gCAAa,CAACH,KAAK,CAACQ,UAAU,GAAG,IAAIC,8BAAc,KAAK;wBAClE;oBACF,CAAA;YACF;SACD"}
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", {
3
+ value: true
4
+ });
5
+ const _drizzleorm = require("drizzle-orm");
6
+ const _db = require("./db");
7
+ async function checkConnection() {
8
+ try {
9
+ const db = await (0, _db.getDB)();
10
+ await db.execute((0, _drizzleorm.sql)`SELECT 1`);
11
+ console.log('Database is ready and accepting queries!');
12
+ process.exit(0);
13
+ } catch (error) {
14
+ console.error(`Database check failed: ${error.message}`);
15
+ process.exit(1);
16
+ }
17
+ }
18
+ checkConnection();
19
+
20
+ //# sourceMappingURL=check-db.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../backend/src/infrastructure/database/scripts/check-db.ts"],"sourcesContent":["import { sql } from 'drizzle-orm'\nimport { getDB } from './db'\n\nasync function checkConnection() {\n try {\n const db = await getDB()\n await db.execute(sql`SELECT 1`)\n console.log('Database is ready and accepting queries!')\n process.exit(0)\n } catch (error: any) {\n console.error(`Database check failed: ${error.message}`)\n process.exit(1)\n }\n}\n\ncheckConnection()\n"],"names":["checkConnection","db","getDB","execute","sql","console","log","process","exit","error","message"],"mappings":";;;;4BAAoB;oBACE;AAEtB,eAAeA;IACb,IAAI;QACF,MAAMC,KAAK,MAAMC,IAAAA,SAAK;QACtB,MAAMD,GAAGE,OAAO,CAACC,IAAAA,eAAG,CAAA,CAAC,QAAQ,CAAC;QAC9BC,QAAQC,GAAG,CAAC;QACZC,QAAQC,IAAI,CAAC;IACf,EAAE,OAAOC,OAAY;QACnBJ,QAAQI,KAAK,CAAC,CAAC,uBAAuB,EAAEA,MAAMC,OAAO,EAAE;QACvDH,QAAQC,IAAI,CAAC;IACf;AACF;AAEAR"}