@sync-in/server 1.9.6 → 1.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +38 -4
- package/README.md +2 -2
- package/environment/environment.dist.yaml +15 -5
- package/package.json +14 -16
- package/server/app.bootstrap.js +4 -23
- package/server/app.bootstrap.js.map +1 -1
- package/server/app.constants.js +3 -2
- package/server/app.constants.js.map +1 -1
- package/server/applications/comments/services/comments-queries.service.js +5 -9
- package/server/applications/comments/services/comments-queries.service.js.map +1 -1
- package/server/applications/files/constants/cache.js +2 -5
- package/server/applications/files/constants/cache.js.map +1 -1
- package/server/applications/files/constants/files.js +4 -0
- package/server/applications/files/constants/files.js.map +1 -1
- package/server/applications/files/constants/operations.js +4 -0
- package/server/applications/files/constants/operations.js.map +1 -1
- package/server/applications/files/constants/routes.js +1 -26
- package/server/applications/files/constants/routes.js.map +1 -1
- package/server/applications/files/files.config.js +15 -39
- package/server/applications/files/files.config.js.map +1 -1
- package/server/applications/files/files.controller.js +4 -4
- package/server/applications/files/files.controller.js.map +1 -1
- package/server/applications/files/files.module.js +12 -9
- package/server/applications/files/files.module.js.map +1 -1
- package/server/applications/files/interfaces/file-lock.interface.js.map +1 -1
- package/server/applications/files/interfaces/file-props.interface.js.map +1 -1
- package/server/applications/files/modules/collabora-online/collabora-online-environment.decorator.js +32 -0
- package/server/applications/files/modules/collabora-online/collabora-online-environment.decorator.js.map +1 -0
- package/server/applications/files/modules/collabora-online/collabora-online-manager.service.js +280 -0
- package/server/applications/files/modules/collabora-online/collabora-online-manager.service.js.map +1 -0
- package/server/applications/files/modules/collabora-online/collabora-online-manager.service.spec.js +552 -0
- package/server/applications/files/modules/collabora-online/collabora-online-manager.service.spec.js.map +1 -0
- package/server/applications/files/modules/collabora-online/collabora-online.config.js +40 -0
- package/server/applications/files/modules/collabora-online/collabora-online.config.js.map +1 -0
- package/server/applications/files/modules/collabora-online/collabora-online.constants.js +110 -0
- package/server/applications/files/modules/collabora-online/collabora-online.constants.js.map +1 -0
- package/server/applications/files/modules/collabora-online/collabora-online.controller.js +128 -0
- package/server/applications/files/modules/collabora-online/collabora-online.controller.js.map +1 -0
- package/server/applications/files/modules/collabora-online/collabora-online.controller.spec.js +47 -0
- package/server/applications/files/modules/collabora-online/collabora-online.controller.spec.js.map +1 -0
- package/server/applications/files/{interfaces/only-office-config.interface.js → modules/collabora-online/collabora-online.dtos.js} +1 -1
- package/server/applications/files/modules/collabora-online/collabora-online.dtos.js.map +1 -0
- package/server/applications/files/{guards/files-only-office.guard.js → modules/collabora-online/collabora-online.guard.js} +7 -21
- package/server/applications/files/modules/collabora-online/collabora-online.guard.js.map +1 -0
- package/server/applications/files/modules/collabora-online/collabora-online.guard.spec.js +86 -0
- package/server/applications/files/modules/collabora-online/collabora-online.guard.spec.js.map +1 -0
- package/server/applications/files/modules/collabora-online/collabora-online.interface.js +10 -0
- package/server/applications/files/modules/collabora-online/collabora-online.interface.js.map +1 -0
- package/server/applications/files/modules/collabora-online/collabora-online.module.js +41 -0
- package/server/applications/files/modules/collabora-online/collabora-online.module.js.map +1 -0
- package/server/applications/files/modules/collabora-online/collabora-online.routes.js +35 -0
- package/server/applications/files/modules/collabora-online/collabora-online.routes.js.map +1 -0
- package/server/applications/files/modules/collabora-online/collabora-online.strategy.js +59 -0
- package/server/applications/files/modules/collabora-online/collabora-online.strategy.js.map +1 -0
- package/server/applications/files/modules/collabora-online/collabora-online.utils.js +28 -0
- package/server/applications/files/modules/collabora-online/collabora-online.utils.js.map +1 -0
- package/server/applications/files/{decorators → modules/only-office}/only-office-environment.decorator.js +5 -5
- package/server/applications/files/modules/only-office/only-office-environment.decorator.js.map +1 -0
- package/server/applications/files/{services/files-only-office-manager.service.js → modules/only-office/only-office-manager.service.js} +101 -97
- package/server/applications/files/modules/only-office/only-office-manager.service.js.map +1 -0
- package/server/applications/files/modules/only-office/only-office-manager.service.spec.js +477 -0
- package/server/applications/files/modules/only-office/only-office-manager.service.spec.js.map +1 -0
- package/server/applications/files/modules/only-office/only-office.config.js +51 -0
- package/server/applications/files/modules/only-office/only-office.config.js.map +1 -0
- package/server/applications/files/modules/only-office/only-office.constants.js +417 -0
- package/server/applications/files/modules/only-office/only-office.constants.js.map +1 -0
- package/server/applications/files/{files-only-office.controller.js → modules/only-office/only-office.controller.js} +35 -52
- package/server/applications/files/modules/only-office/only-office.controller.js.map +1 -0
- package/server/applications/files/{files-only-office.controller.spec.js → modules/only-office/only-office.controller.spec.js} +24 -21
- package/server/applications/files/modules/only-office/only-office.controller.spec.js.map +1 -0
- package/server/applications/files/modules/only-office/only-office.dtos.js +10 -0
- package/server/applications/files/modules/only-office/only-office.dtos.js.map +1 -0
- package/server/applications/files/modules/only-office/only-office.guard.js +40 -0
- package/server/applications/files/modules/only-office/only-office.guard.js.map +1 -0
- package/server/applications/files/{guards/files-only-office.guard.spec.js → modules/only-office/only-office.guard.spec.js} +15 -21
- package/server/applications/files/modules/only-office/only-office.guard.spec.js.map +1 -0
- package/server/applications/files/modules/only-office/only-office.interface.js +10 -0
- package/server/applications/files/modules/only-office/only-office.interface.js.map +1 -0
- package/server/applications/files/modules/only-office/only-office.module.js +41 -0
- package/server/applications/files/modules/only-office/only-office.module.js.map +1 -0
- package/server/applications/files/modules/only-office/only-office.routes.js +45 -0
- package/server/applications/files/modules/only-office/only-office.routes.js.map +1 -0
- package/server/applications/files/{guards/files-only-office.strategy.js → modules/only-office/only-office.strategy.js} +11 -11
- package/server/applications/files/modules/only-office/only-office.strategy.js.map +1 -0
- package/server/applications/files/services/files-lock-manager.service.js +25 -33
- package/server/applications/files/services/files-lock-manager.service.js.map +1 -1
- package/server/applications/files/services/files-manager.service.js +17 -16
- package/server/applications/files/services/files-manager.service.js.map +1 -1
- package/server/applications/files/services/files-methods.service.js +2 -2
- package/server/applications/files/services/files-methods.service.js.map +1 -1
- package/server/applications/files/services/files-methods.service.spec.js +3 -1
- package/server/applications/files/services/files-methods.service.spec.js.map +1 -1
- package/server/applications/files/services/files-scheduler.service.js +2 -2
- package/server/applications/files/services/files-scheduler.service.js.map +1 -1
- package/server/applications/files/utils/files.js +10 -2
- package/server/applications/files/utils/files.js.map +1 -1
- package/server/applications/links/constants/routes.js +5 -0
- package/server/applications/links/constants/routes.js.map +1 -1
- package/server/applications/links/interfaces/link-space.interface.js.map +1 -1
- package/server/applications/links/links.controller.js +25 -5
- package/server/applications/links/links.controller.js.map +1 -1
- package/server/applications/links/services/links-manager.service.js +43 -21
- package/server/applications/links/services/links-manager.service.js.map +1 -1
- package/server/applications/links/services/links-manager.service.spec.js +4 -3
- package/server/applications/links/services/links-manager.service.spec.js.map +1 -1
- package/server/applications/links/services/links-queries.service.js +9 -2
- package/server/applications/links/services/links-queries.service.js.map +1 -1
- package/server/applications/shares/interfaces/share-link.interface.js.map +1 -1
- package/server/applications/shares/services/shares-manager.service.js +3 -0
- package/server/applications/shares/services/shares-manager.service.js.map +1 -1
- package/server/applications/shares/services/shares-manager.service.spec.js +2 -1
- package/server/applications/shares/services/shares-manager.service.spec.js.map +1 -1
- package/server/applications/shares/services/shares-queries.service.js +1 -0
- package/server/applications/shares/services/shares-queries.service.js.map +1 -1
- package/server/applications/spaces/constants/spaces.js +2 -2
- package/server/applications/spaces/constants/spaces.js.map +1 -1
- package/server/applications/spaces/decorators/space-override-permission.decorator.js +18 -0
- package/server/applications/spaces/decorators/space-override-permission.decorator.js.map +1 -0
- package/server/applications/spaces/guards/space.guard.js +40 -33
- package/server/applications/spaces/guards/space.guard.js.map +1 -1
- package/server/applications/spaces/guards/space.guard.spec.js +10 -15
- package/server/applications/spaces/guards/space.guard.spec.js.map +1 -1
- package/server/applications/users/users.e2e-spec.js +0 -1
- package/server/applications/users/users.e2e-spec.js.map +1 -1
- package/server/applications/webdav/constants/webdav.js +11 -0
- package/server/applications/webdav/constants/webdav.js.map +1 -1
- package/server/applications/webdav/guards/webdav-protocol.guard.js +9 -8
- package/server/applications/webdav/guards/webdav-protocol.guard.js.map +1 -1
- package/server/applications/webdav/guards/webdav-protocol.guard.spec.js +1 -1
- package/server/applications/webdav/guards/webdav-protocol.guard.spec.js.map +1 -1
- package/server/applications/webdav/interfaces/webdav.interface.js.map +1 -1
- package/server/applications/webdav/services/webdav-methods.service.js +40 -17
- package/server/applications/webdav/services/webdav-methods.service.js.map +1 -1
- package/server/applications/webdav/services/webdav-methods.service.spec.js +2157 -1289
- package/server/applications/webdav/services/webdav-methods.service.spec.js.map +1 -1
- package/server/applications/webdav/utils/bootstrap.js +45 -0
- package/server/applications/webdav/utils/bootstrap.js.map +1 -0
- package/server/applications/webdav/utils/webdav.js +8 -4
- package/server/applications/webdav/utils/webdav.js.map +1 -1
- package/server/applications/webdav/webdav.controller.js +5 -5
- package/server/applications/webdav/webdav.controller.js.map +1 -1
- package/server/applications/webdav/webdav.e2e-spec.js +131 -2
- package/server/applications/webdav/webdav.e2e-spec.js.map +1 -1
- package/server/authentication/auth.e2e-spec.js +12 -6
- package/server/authentication/auth.e2e-spec.js.map +1 -1
- package/server/authentication/guards/auth-basic.guard.spec.js +23 -0
- package/server/authentication/guards/auth-basic.guard.spec.js.map +1 -1
- package/server/authentication/guards/auth-basic.strategy.js +3 -3
- package/server/authentication/guards/auth-basic.strategy.js.map +1 -1
- package/server/authentication/guards/auth-digest.strategy.js +32 -11
- package/server/authentication/guards/auth-digest.strategy.js.map +1 -1
- package/server/authentication/guards/auth-token-access.guard.js +8 -3
- package/server/authentication/guards/auth-token-access.guard.js.map +1 -1
- package/server/authentication/guards/implementations/http-basic.strategy.js +76 -0
- package/server/authentication/guards/implementations/http-basic.strategy.js.map +1 -0
- package/server/authentication/guards/implementations/http-digest.strategy.js +155 -0
- package/server/authentication/guards/implementations/http-digest.strategy.js.map +1 -0
- package/server/authentication/services/auth-manager.service.js +1 -2
- package/server/authentication/services/auth-manager.service.js.map +1 -1
- package/server/authentication/services/auth-methods/auth-method-two-fa.service.js +1 -1
- package/server/authentication/services/auth-methods/auth-method-two-fa.service.js.map +1 -1
- package/server/authentication/services/auth-methods/auth-method-two-fa.service.spec.js +350 -4
- package/server/authentication/services/auth-methods/auth-method-two-fa.service.spec.js.map +1 -1
- package/server/configuration/config.environment.js +5 -1
- package/server/configuration/config.environment.js.map +1 -1
- package/server/configuration/config.interfaces.js.map +1 -1
- package/static/3rdpartylicenses.txt +507 -507
- package/static/assets/pdfjs/build/pdf.mjs +93 -33
- package/static/assets/pdfjs/build/pdf.mjs.map +1 -1
- package/static/assets/pdfjs/build/pdf.sandbox.mjs +3 -3
- package/static/assets/pdfjs/build/pdf.sandbox.mjs.map +1 -1
- package/static/assets/pdfjs/build/pdf.worker.mjs +166 -54
- package/static/assets/pdfjs/build/pdf.worker.mjs.map +1 -1
- package/static/assets/pdfjs/version +1 -1
- package/static/assets/pdfjs/web/images/checkmark.svg +5 -0
- package/static/assets/pdfjs/web/images/pages_closeButton.svg +3 -0
- package/static/assets/pdfjs/web/images/pages_selected.svg +7 -0
- package/static/assets/pdfjs/web/images/pages_viewArrow.svg +3 -0
- package/static/assets/pdfjs/web/images/pages_viewButton.svg +3 -0
- package/static/assets/pdfjs/web/locale/be/viewer.ftl +0 -2
- package/static/assets/pdfjs/web/locale/bs/viewer.ftl +0 -5
- package/static/assets/pdfjs/web/locale/cs/viewer.ftl +4 -6
- package/static/assets/pdfjs/web/locale/cy/viewer.ftl +0 -2
- package/static/assets/pdfjs/web/locale/da/viewer.ftl +0 -2
- package/static/assets/pdfjs/web/locale/de/viewer.ftl +0 -2
- package/static/assets/pdfjs/web/locale/dsb/viewer.ftl +0 -2
- package/static/assets/pdfjs/web/locale/el/viewer.ftl +0 -2
- package/static/assets/pdfjs/web/locale/en-CA/viewer.ftl +6 -2
- package/static/assets/pdfjs/web/locale/en-GB/viewer.ftl +0 -2
- package/static/assets/pdfjs/web/locale/en-US/viewer.ftl +82 -17
- package/static/assets/pdfjs/web/locale/eo/viewer.ftl +0 -2
- package/static/assets/pdfjs/web/locale/es-AR/viewer.ftl +0 -2
- package/static/assets/pdfjs/web/locale/es-CL/viewer.ftl +0 -2
- package/static/assets/pdfjs/web/locale/es-ES/viewer.ftl +0 -2
- package/static/assets/pdfjs/web/locale/es-MX/viewer.ftl +0 -2
- package/static/assets/pdfjs/web/locale/eu/viewer.ftl +0 -2
- package/static/assets/pdfjs/web/locale/fi/viewer.ftl +0 -2
- package/static/assets/pdfjs/web/locale/fr/viewer.ftl +0 -2
- package/static/assets/pdfjs/web/locale/fur/viewer.ftl +0 -5
- package/static/assets/pdfjs/web/locale/fy-NL/viewer.ftl +3 -5
- package/static/assets/pdfjs/web/locale/gn/viewer.ftl +0 -2
- package/static/assets/pdfjs/web/locale/he/viewer.ftl +0 -2
- package/static/assets/pdfjs/web/locale/hr/viewer.ftl +66 -0
- package/static/assets/pdfjs/web/locale/hsb/viewer.ftl +0 -2
- package/static/assets/pdfjs/web/locale/hu/viewer.ftl +0 -2
- package/static/assets/pdfjs/web/locale/hy-AM/viewer.ftl +3 -8
- package/static/assets/pdfjs/web/locale/ia/viewer.ftl +0 -2
- package/static/assets/pdfjs/web/locale/id/viewer.ftl +0 -5
- package/static/assets/pdfjs/web/locale/is/viewer.ftl +0 -5
- package/static/assets/pdfjs/web/locale/it/viewer.ftl +0 -2
- package/static/assets/pdfjs/web/locale/ja/viewer.ftl +0 -14
- package/static/assets/pdfjs/web/locale/ka/viewer.ftl +4 -6
- package/static/assets/pdfjs/web/locale/kab/viewer.ftl +0 -5
- package/static/assets/pdfjs/web/locale/kk/viewer.ftl +0 -2
- package/static/assets/pdfjs/web/locale/ko/viewer.ftl +0 -2
- package/static/assets/pdfjs/web/locale/nb-NO/viewer.ftl +1 -3
- package/static/assets/pdfjs/web/locale/nl/viewer.ftl +0 -2
- package/static/assets/pdfjs/web/locale/nn-NO/viewer.ftl +4 -2
- package/static/assets/pdfjs/web/locale/pa-IN/viewer.ftl +0 -2
- package/static/assets/pdfjs/web/locale/pl/viewer.ftl +0 -2
- package/static/assets/pdfjs/web/locale/pt-BR/viewer.ftl +0 -2
- package/static/assets/pdfjs/web/locale/pt-PT/viewer.ftl +35 -0
- package/static/assets/pdfjs/web/locale/rm/viewer.ftl +0 -5
- package/static/assets/pdfjs/web/locale/ro/viewer.ftl +4 -6
- package/static/assets/pdfjs/web/locale/ru/viewer.ftl +3 -5
- package/static/assets/pdfjs/web/locale/sk/viewer.ftl +0 -2
- package/static/assets/pdfjs/web/locale/sl/viewer.ftl +0 -2
- package/static/assets/pdfjs/web/locale/sq/viewer.ftl +0 -2
- package/static/assets/pdfjs/web/locale/sv-SE/viewer.ftl +0 -2
- package/static/assets/pdfjs/web/locale/tg/viewer.ftl +0 -2
- package/static/assets/pdfjs/web/locale/th/viewer.ftl +2 -2
- package/static/assets/pdfjs/web/locale/tr/viewer.ftl +0 -2
- package/static/assets/pdfjs/web/locale/vi/viewer.ftl +0 -2
- package/static/assets/pdfjs/web/locale/zh-CN/viewer.ftl +0 -2
- package/static/assets/pdfjs/web/locale/zh-TW/viewer.ftl +0 -2
- package/static/assets/pdfjs/web/viewer.css +1778 -835
- package/static/assets/pdfjs/web/viewer.html +167 -86
- package/static/assets/pdfjs/web/viewer.mjs +1106 -801
- package/static/assets/pdfjs/web/viewer.mjs.map +1 -1
- package/static/chunk-27V66YJV.js +2 -0
- package/static/{chunk-2F42MZQ5.js → chunk-2GXOVGTD.js} +1 -1
- package/static/{chunk-HLKZCMKV.js → chunk-2RWLNKZH.js} +1 -1
- package/static/chunk-2YQ4SX3A.js +13 -0
- package/static/{chunk-LFAQLJZK.js → chunk-3MVPXC3U.js} +1 -1
- package/static/chunk-3QTROEHV.js +1 -0
- package/static/{chunk-JYHTSSKW.js → chunk-3VRUIWQG.js} +1 -1
- package/static/{chunk-2CAAJBRO.js → chunk-3ZBAQTHJ.js} +1 -1
- package/static/chunk-3ZLBVUCX.js +2 -0
- package/static/chunk-46TJLPJY.js +1 -0
- package/static/chunk-4NIYCYRS.js +2 -0
- package/static/chunk-6OEOADR6.js +1 -0
- package/static/{chunk-BJARRIS6.js → chunk-76M3BMK6.js} +11 -11
- package/static/{chunk-ANH4VNOS.js → chunk-76REYAEA.js} +1 -1
- package/static/{chunk-7HL5Z6PF.js → chunk-7HJFIMNF.js} +1 -1
- package/static/{chunk-XIQXRSZ2.js → chunk-7KAYOR3A.js} +1 -1
- package/static/chunk-AALPWGPB.js +3 -0
- package/static/{chunk-Z2KBIZ5D.js → chunk-ARS47O5X.js} +1 -1
- package/static/chunk-BCN4T5DO.js +2 -0
- package/static/{chunk-DU4Q4RWJ.js → chunk-CMNMPG6Z.js} +1 -1
- package/static/{chunk-UO7ATVQG.js → chunk-CN5YVRFT.js} +1 -1
- package/static/{chunk-BX3QZ7IL.js → chunk-CSVPAZHK.js} +1 -1
- package/static/chunk-CVXLHSO5.js +1 -0
- package/static/{chunk-O233BXWK.js → chunk-D2MLAO5N.js} +1 -1
- package/static/{chunk-NHMYAVJK.js → chunk-D5FQ72R4.js} +1 -1
- package/static/{chunk-IBC7CFBQ.js → chunk-DVCN3P7Q.js} +1 -1
- package/static/{chunk-25PWAXTJ.js → chunk-EKWB5W72.js} +1 -1
- package/static/{chunk-ZCOEP4O2.js → chunk-FTFEQDWH.js} +1 -1
- package/static/{chunk-EPDWJEPD.js → chunk-FWQJ4ZCD.js} +1 -1
- package/static/chunk-G7RZN7HN.js +1 -0
- package/static/{chunk-X5UDV4ZB.js → chunk-HZAB6F4Q.js} +1 -1
- package/static/{chunk-FEQUP26G.js → chunk-IHS5LSJJ.js} +1 -1
- package/static/{chunk-DQAQUSVW.js → chunk-IMFO2MI7.js} +1 -1
- package/static/{chunk-ODAQRAPO.js → chunk-J7474P3L.js} +1 -1
- package/static/{chunk-DK2LAJEL.js → chunk-JAJ7VXMB.js} +1 -1
- package/static/{chunk-AYYJZMBE.js → chunk-JNTNMIUH.js} +1 -1
- package/static/chunk-JRXG43AA.js +2 -0
- package/static/{chunk-3AR5VNJE.js → chunk-KAUCN24H.js} +1 -1
- package/static/{chunk-3WS72A6C.js → chunk-KDUAB76O.js} +1 -1
- package/static/chunk-KEZNIIFH.js +1 -0
- package/static/chunk-LTJNLOX2.js +1 -0
- package/static/chunk-LWSCODLD.js +1 -0
- package/static/{chunk-GRV44RYI.js → chunk-LZUHREOF.js} +1 -1
- package/static/chunk-NIKNG2FX.js +1 -0
- package/static/chunk-NNZWSNAW.js +1 -0
- package/static/chunk-NWKBB7J4.js +1 -0
- package/static/{chunk-B4TDS6AQ.js → chunk-PDG7DOEF.js} +1 -1
- package/static/{chunk-NQCKX2AD.js → chunk-PPJCVBJH.js} +1 -1
- package/static/chunk-PVYVY3GD.js +1 -0
- package/static/chunk-Q5X5TPAG.js +1 -0
- package/static/chunk-QGHNJVJ6.js +1 -0
- package/static/{chunk-5HYSNQR4.js → chunk-QJ22N76V.js} +1 -1
- package/static/{chunk-TOCCCZP2.js → chunk-QTPIEEZW.js} +1 -1
- package/static/{chunk-D6QWQHWE.js → chunk-R4VMWCM5.js} +1 -1
- package/static/{chunk-7H5O4BLV.js → chunk-R4VYKZVJ.js} +1 -1
- package/static/{chunk-QKMN3S4M.js → chunk-RBTLSPYJ.js} +1 -1
- package/static/chunk-RJULB733.js +1 -0
- package/static/{chunk-E5C4QRNQ.js → chunk-RNVPQQKT.js} +5 -5
- package/static/chunk-RTNEBRKJ.js +1 -0
- package/static/{chunk-IIKL33TV.js → chunk-S44QIK3G.js} +1 -1
- package/static/{chunk-SF6Q6VRC.js → chunk-S6H2ELRY.js} +1 -1
- package/static/{chunk-4GBA6EJ4.js → chunk-SDJNZULP.js} +1 -1
- package/static/chunk-SNOOCDJD.js +1 -0
- package/static/chunk-SPQH3ATC.js +5 -0
- package/static/{chunk-5KVI243T.js → chunk-TNCKNU6I.js} +1 -1
- package/static/{chunk-7QYALK5T.js → chunk-TTWMFWEC.js} +1 -1
- package/static/{chunk-OVUMPMVM.js → chunk-U5E5H2DD.js} +1 -1
- package/static/{chunk-5NFH4E2B.js → chunk-UOK3LKSX.js} +1 -1
- package/static/{chunk-5ATJIR5S.js → chunk-VBTZDHZ3.js} +1 -1
- package/static/{chunk-CHMDM2ZW.js → chunk-VD5JHSDS.js} +1 -1
- package/static/chunk-VZFZUI6D.js +1 -0
- package/static/{chunk-GYYJ4FWN.js → chunk-WFMEUST4.js} +1 -1
- package/static/{chunk-BVKDW5XO.js → chunk-WRK2FTKU.js} +1 -1
- package/static/{chunk-GLPKRULI.js → chunk-WZPF4LS2.js} +1 -1
- package/static/chunk-X7NHX5C7.js +1 -0
- package/static/{chunk-FSGT46LM.js → chunk-XSURUW7C.js} +1 -1
- package/static/{chunk-KAAFVHYE.js → chunk-XX3JPJUM.js} +1 -1
- package/static/{chunk-C5T7RZSD.js → chunk-XZHWESIY.js} +1 -1
- package/static/chunk-ZCSHU3D7.js +1 -0
- package/static/{chunk-QUUQOBTF.js → chunk-ZEJLIGAY.js} +1 -1
- package/static/{chunk-GENTF6JM.js → chunk-ZHUBWKA2.js} +1 -1
- package/static/chunk-ZOMRIN3G.js +2 -0
- package/static/{chunk-2U5VKTML.js → chunk-ZU5MQTFN.js} +1 -1
- package/static/index.html +2 -2
- package/static/main-5O3KLGIR.js +11 -0
- package/static/{styles-S5HVK4H5.css → styles-XLLEY5Y3.css} +1 -1
- package/server/applications/files/constants/only-office.js +0 -531
- package/server/applications/files/constants/only-office.js.map +0 -1
- package/server/applications/files/decorators/only-office-environment.decorator.js.map +0 -1
- package/server/applications/files/files-only-office.controller.js.map +0 -1
- package/server/applications/files/files-only-office.controller.spec.js.map +0 -1
- package/server/applications/files/guards/files-only-office.guard.js.map +0 -1
- package/server/applications/files/guards/files-only-office.guard.spec.js.map +0 -1
- package/server/applications/files/guards/files-only-office.strategy.js.map +0 -1
- package/server/applications/files/interfaces/only-office-config.interface.js.map +0 -1
- package/server/applications/files/services/files-only-office-manager.service.js.map +0 -1
- package/server/applications/files/services/files-only-office-manager.service.spec.js +0 -58
- package/server/applications/files/services/files-only-office-manager.service.spec.js.map +0 -1
- package/static/chunk-42L6C5MT.js +0 -1
- package/static/chunk-4ZKAVMB4.js +0 -1
- package/static/chunk-5GIWZKNS.js +0 -1
- package/static/chunk-5WCQBTXW.js +0 -1
- package/static/chunk-B2A4HNDC.js +0 -1
- package/static/chunk-BSB4VROD.js +0 -2
- package/static/chunk-CUC7R6C2.js +0 -1
- package/static/chunk-DHFQIFOF.js +0 -1
- package/static/chunk-DRHPEERW.js +0 -2
- package/static/chunk-FCGTI42I.js +0 -1
- package/static/chunk-FCR5AEHR.js +0 -3
- package/static/chunk-HB5DC7RJ.js +0 -1
- package/static/chunk-ITVA26X2.js +0 -2
- package/static/chunk-KWKZN53T.js +0 -1
- package/static/chunk-LBXOAKBD.js +0 -1
- package/static/chunk-LZKI5P5T.js +0 -1
- package/static/chunk-MGMDT4VN.js +0 -1
- package/static/chunk-MWUUM2NK.js +0 -13
- package/static/chunk-MYM43ENO.js +0 -1
- package/static/chunk-NAH4V2R6.js +0 -2
- package/static/chunk-PCFH5HCI.js +0 -2
- package/static/chunk-Q6B4OVER.js +0 -5
- package/static/chunk-QV5LQKTS.js +0 -1
- package/static/chunk-S4UTSOPV.js +0 -1
- package/static/chunk-SRBOO7AO.js +0 -1
- package/static/chunk-VZPCXSRG.js +0 -2
- package/static/chunk-XKEBQNQJ.js +0 -1
- package/static/chunk-YYTDPI5S.js +0 -1
- package/static/main-ODUA232E.js +0 -11
- /package/static/assets/pdfjs/web/images/{toolbarButton-sidebarToggle.svg → toolbarButton-viewsManagerToggle.svg} +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../backend/src/authentication/guards/auth-basic.strategy.ts"],"sourcesContent":["/*\n * Copyright (C) 2012-2025 Johan Legrand <johan.legrand@sync-in.com>\n * This file is part of Sync-in | The open source file sync and share solution\n * See the LICENSE file for licensing details\n */\n\nimport { Injectable } from '@nestjs/common'\nimport { AbstractStrategy, PassportStrategy } from '@nestjs/passport'\nimport { instanceToPlain, plainToInstance } from 'class-transformer'\nimport { FastifyRequest } from 'fastify'\nimport { PinoLogger } from 'nestjs-pino'\nimport {
|
|
1
|
+
{"version":3,"sources":["../../../../backend/src/authentication/guards/auth-basic.strategy.ts"],"sourcesContent":["/*\n * Copyright (C) 2012-2025 Johan Legrand <johan.legrand@sync-in.com>\n * This file is part of Sync-in | The open source file sync and share solution\n * See the LICENSE file for licensing details\n */\n\nimport { Injectable } from '@nestjs/common'\nimport { AbstractStrategy, PassportStrategy } from '@nestjs/passport'\nimport { instanceToPlain, plainToInstance } from 'class-transformer'\nimport { FastifyRequest } from 'fastify'\nimport { PinoLogger } from 'nestjs-pino'\nimport { UserModel } from '../../applications/users/models/user.model'\nimport { SERVER_NAME } from '../../common/shared'\nimport { Cache } from '../../infrastructure/cache/services/cache.service'\nimport { AUTH_SCOPE } from '../constants/scope'\nimport { AuthMethod } from '../models/auth-method'\nimport { HttpBasicStrategy } from './implementations/http-basic.strategy'\n\n@Injectable()\nexport class AuthBasicStrategy extends PassportStrategy(HttpBasicStrategy, 'basic') implements AbstractStrategy {\n private readonly CACHE_TTL = 900\n private readonly CACHE_KEY_PREFIX = 'auth-webdav'\n\n constructor(\n private readonly authMethod: AuthMethod,\n private readonly cache: Cache,\n private readonly logger: PinoLogger\n ) {\n super({ passReqToCallback: true, realm: SERVER_NAME })\n }\n\n async validate(req: FastifyRequest, loginOrEmail: string, password: string): Promise<Omit<UserModel, 'password'> | null> {\n loginOrEmail = loginOrEmail.trim()\n password = password.trim()\n this.logger.assign({ user: loginOrEmail })\n const authBasicUser = `${this.CACHE_KEY_PREFIX}-${req.headers['authorization'].split(' ').at(-1).toLowerCase()}`\n const userFromCache: any = await this.cache.get(authBasicUser)\n if (userFromCache === null) {\n // not authorized\n return null\n }\n if (userFromCache !== undefined) {\n // cached\n // warning: plainToInstance do not use constructor to instantiate the class\n return plainToInstance(UserModel, userFromCache)\n }\n const userFromDB: UserModel = await this.authMethod.validateUser(loginOrEmail, password, req.ip, AUTH_SCOPE.WEBDAV)\n if (userFromDB !== null) {\n userFromDB.removePassword()\n }\n const userToCache: Record<string, any> | null = userFromDB ? instanceToPlain(userFromDB, { excludePrefixes: ['_'] }) : null\n this.cache.set(authBasicUser, userToCache, this.CACHE_TTL).catch((e: Error) => this.logger.error(`${this.validate.name} - ${e}`))\n return userFromDB\n }\n}\n"],"names":["AuthBasicStrategy","PassportStrategy","HttpBasicStrategy","validate","req","loginOrEmail","password","trim","logger","assign","user","authBasicUser","CACHE_KEY_PREFIX","headers","split","at","toLowerCase","userFromCache","cache","get","undefined","plainToInstance","UserModel","userFromDB","authMethod","validateUser","ip","AUTH_SCOPE","WEBDAV","removePassword","userToCache","instanceToPlain","excludePrefixes","set","CACHE_TTL","catch","e","error","name","passReqToCallback","realm","SERVER_NAME"],"mappings":"AAAA;;;;CAIC;;;;+BAeYA;;;eAAAA;;;wBAbc;0BACwB;kCACF;4BAEtB;2BACD;wBACE;8BACN;uBACK;4BACA;mCACO;;;;;;;;;;AAG3B,IAAA,AAAMA,oBAAN,MAAMA,0BAA0BC,IAAAA,0BAAgB,EAACC,oCAAiB,EAAE;IAYzE,MAAMC,SAASC,GAAmB,EAAEC,YAAoB,EAAEC,QAAgB,EAA+C;QACvHD,eAAeA,aAAaE,IAAI;QAChCD,WAAWA,SAASC,IAAI;QACxB,IAAI,CAACC,MAAM,CAACC,MAAM,CAAC;YAAEC,MAAML;QAAa;QACxC,MAAMM,gBAAgB,GAAG,IAAI,CAACC,gBAAgB,CAAC,CAAC,EAAER,IAAIS,OAAO,CAAC,gBAAgB,CAACC,KAAK,CAAC,KAAKC,EAAE,CAAC,CAAC,GAAGC,WAAW,IAAI;QAChH,MAAMC,gBAAqB,MAAM,IAAI,CAACC,KAAK,CAACC,GAAG,CAACR;QAChD,IAAIM,kBAAkB,MAAM;YAC1B,iBAAiB;YACjB,OAAO;QACT;QACA,IAAIA,kBAAkBG,WAAW;YAC/B,SAAS;YACT,2EAA2E;YAC3E,OAAOC,IAAAA,iCAAe,EAACC,oBAAS,EAAEL;QACpC;QACA,MAAMM,aAAwB,MAAM,IAAI,CAACC,UAAU,CAACC,YAAY,CAACpB,cAAcC,UAAUF,IAAIsB,EAAE,EAAEC,iBAAU,CAACC,MAAM;QAClH,IAAIL,eAAe,MAAM;YACvBA,WAAWM,cAAc;QAC3B;QACA,MAAMC,cAA0CP,aAAaQ,IAAAA,iCAAe,EAACR,YAAY;YAAES,iBAAiB;gBAAC;aAAI;QAAC,KAAK;QACvH,IAAI,CAACd,KAAK,CAACe,GAAG,CAACtB,eAAemB,aAAa,IAAI,CAACI,SAAS,EAAEC,KAAK,CAAC,CAACC,IAAa,IAAI,CAAC5B,MAAM,CAAC6B,KAAK,CAAC,GAAG,IAAI,CAAClC,QAAQ,CAACmC,IAAI,CAAC,GAAG,EAAEF,GAAG;QAC/H,OAAOb;IACT;IA9BA,YACE,AAAiBC,UAAsB,EACvC,AAAiBN,KAAY,EAC7B,AAAiBV,MAAkB,CACnC;QACA,KAAK,CAAC;YAAE+B,mBAAmB;YAAMC,OAAOC,mBAAW;QAAC,SAJnCjB,aAAAA,iBACAN,QAAAA,YACAV,SAAAA,aANF0B,YAAY,UACZtB,mBAAmB;IAQpC;AAyBF"}
|
|
@@ -5,22 +5,43 @@
|
|
|
5
5
|
*/ // import { Injectable } from '@nestjs/common'
|
|
6
6
|
// import { PassportStrategy } from '@nestjs/passport'
|
|
7
7
|
// import { PinoLogger } from 'nestjs-pino'
|
|
8
|
-
// import {
|
|
9
|
-
//
|
|
10
|
-
// import {
|
|
8
|
+
// import { SERVER_NAME } from '../../common/shared'
|
|
9
|
+
//
|
|
10
|
+
// import { HttpDigestStrategy } from './implementations/http-digest.strategy'
|
|
11
11
|
//
|
|
12
12
|
// @Injectable()
|
|
13
|
-
// export class AuthDigestStrategy extends PassportStrategy(
|
|
14
|
-
// constructor(
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
//
|
|
13
|
+
// export class AuthDigestStrategy extends PassportStrategy(HttpDigestStrategy, 'digest') {
|
|
14
|
+
// constructor(private readonly logger: PinoLogger) {
|
|
15
|
+
// super({
|
|
16
|
+
// realm: SERVER_NAME,
|
|
17
|
+
// // Recommended options for RFC-compliant Digest (required for security)
|
|
18
|
+
// qop: 'auth',
|
|
19
|
+
// algorithm: 'MD5'
|
|
20
|
+
// // Optional anti-replay validation hook
|
|
21
|
+
// // validate: (params, done) => done(null, true),
|
|
22
|
+
// })
|
|
19
23
|
// }
|
|
20
24
|
//
|
|
21
|
-
// validate(loginOrEmail: string) {
|
|
25
|
+
// async validate(loginOrEmail: string) {
|
|
26
|
+
// loginOrEmail = loginOrEmail.trim()
|
|
27
|
+
// this.logger.assign({ user: loginOrEmail })
|
|
28
|
+
//
|
|
29
|
+
// // ⚠️ TO ADAPT: Digest authentication requires a server-side secret:
|
|
30
|
+
// // - ideally a stored { ha1 } value (HA1 = MD5(username:realm:password))
|
|
31
|
+
// // - otherwise the clear-text "password" (less secure, but possible)
|
|
32
|
+
// //
|
|
22
33
|
// // return [loginOrEmail, { ha1: '4befe40c6af915eca11de84be07a1f21' }]
|
|
23
|
-
// return [loginOrEmail, 'password']
|
|
34
|
+
// // return [loginOrEmail, 'password']
|
|
35
|
+
//
|
|
36
|
+
// // Method to get digest secret
|
|
37
|
+
// const { user, ha1, password } = getDigestSecret(loginOrEmail, SERVER_NAME)
|
|
38
|
+
//
|
|
39
|
+
// if (!user) return null
|
|
40
|
+
//
|
|
41
|
+
// if (ha1) return [user, { ha1 }]
|
|
42
|
+
// if (password) return [user, password]
|
|
43
|
+
//
|
|
44
|
+
// return null
|
|
24
45
|
// }
|
|
25
46
|
// }
|
|
26
47
|
"use strict";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../backend/src/authentication/guards/auth-digest.strategy.ts"],"sourcesContent":["/*\n * Copyright (C) 2012-2025 Johan Legrand <johan.legrand@sync-in.com>\n * This file is part of Sync-in | The open source file sync and share solution\n * See the LICENSE file for licensing details\n */\n\n// import { Injectable } from '@nestjs/common'\n// import { PassportStrategy } from '@nestjs/passport'\n// import { PinoLogger } from 'nestjs-pino'\n// import {
|
|
1
|
+
{"version":3,"sources":["../../../../backend/src/authentication/guards/auth-digest.strategy.ts"],"sourcesContent":["/*\n * Copyright (C) 2012-2025 Johan Legrand <johan.legrand@sync-in.com>\n * This file is part of Sync-in | The open source file sync and share solution\n * See the LICENSE file for licensing details\n */\n\n// import { Injectable } from '@nestjs/common'\n// import { PassportStrategy } from '@nestjs/passport'\n// import { PinoLogger } from 'nestjs-pino'\n// import { SERVER_NAME } from '../../common/shared'\n//\n// import { HttpDigestStrategy } from './implementations/http-digest.strategy'\n//\n// @Injectable()\n// export class AuthDigestStrategy extends PassportStrategy(HttpDigestStrategy, 'digest') {\n// constructor(private readonly logger: PinoLogger) {\n// super({\n// realm: SERVER_NAME,\n// // Recommended options for RFC-compliant Digest (required for security)\n// qop: 'auth',\n// algorithm: 'MD5'\n// // Optional anti-replay validation hook\n// // validate: (params, done) => done(null, true),\n// })\n// }\n//\n// async validate(loginOrEmail: string) {\n// loginOrEmail = loginOrEmail.trim()\n// this.logger.assign({ user: loginOrEmail })\n//\n// // ⚠️ TO ADAPT: Digest authentication requires a server-side secret:\n// // - ideally a stored { ha1 } value (HA1 = MD5(username:realm:password))\n// // - otherwise the clear-text \"password\" (less secure, but possible)\n// //\n// // return [loginOrEmail, { ha1: '4befe40c6af915eca11de84be07a1f21' }]\n// // return [loginOrEmail, 'password']\n//\n// // Method to get digest secret\n// const { user, ha1, password } = getDigestSecret(loginOrEmail, SERVER_NAME)\n//\n// if (!user) return null\n//\n// if (ha1) return [user, { ha1 }]\n// if (password) return [user, password]\n//\n// return null\n// }\n// }\n"],"names":[],"mappings":"AAAA;;;;CAIC,GAED,8CAA8C;AAC9C,sDAAsD;AACtD,2CAA2C;AAC3C,oDAAoD;AACpD,EAAE;AACF,8EAA8E;AAC9E,EAAE;AACF,gBAAgB;AAChB,2FAA2F;AAC3F,uDAAuD;AACvD,cAAc;AACd,4BAA4B;AAC5B,gFAAgF;AAChF,qBAAqB;AACrB,yBAAyB;AACzB,gDAAgD;AAChD,yDAAyD;AACzD,SAAS;AACT,MAAM;AACN,EAAE;AACF,2CAA2C;AAC3C,yCAAyC;AACzC,iDAAiD;AACjD,EAAE;AACF,2EAA2E;AAC3E,+EAA+E;AAC/E,2EAA2E;AAC3E,SAAS;AACT,4EAA4E;AAC5E,2CAA2C;AAC3C,EAAE;AACF,qCAAqC;AACrC,iFAAiF;AACjF,EAAE;AACF,6BAA6B;AAC7B,EAAE;AACF,sCAAsC;AACtC,4CAA4C;AAC5C,EAAE;AACF,kBAAkB;AAClB,MAAM;AACN,IAAI"}
|
|
@@ -15,7 +15,8 @@ Object.defineProperty(exports, "AuthTokenAccessGuard", {
|
|
|
15
15
|
const _common = require("@nestjs/common");
|
|
16
16
|
const _core = require("@nestjs/core");
|
|
17
17
|
const _passport = require("@nestjs/passport");
|
|
18
|
-
const
|
|
18
|
+
const _collaboraonlineconstants = require("../../applications/files/modules/collabora-online/collabora-online.constants");
|
|
19
|
+
const _onlyofficeconstants = require("../../applications/files/modules/only-office/only-office.constants");
|
|
19
20
|
const _webdavcontextdecorator = require("../../applications/webdav/decorators/webdav-context.decorator");
|
|
20
21
|
const _authtokenskipdecorator = require("../decorators/auth-token-skip.decorator");
|
|
21
22
|
function _ts_decorate(decorators, target, key, desc) {
|
|
@@ -37,11 +38,15 @@ let AuthTokenAccessGuard = class AuthTokenAccessGuard extends (0, _passport.Auth
|
|
|
37
38
|
ctx.getHandler(),
|
|
38
39
|
ctx.getClass()
|
|
39
40
|
]);
|
|
40
|
-
const onlyOfficeContext = this.reflector.getAllAndOverride(
|
|
41
|
+
const onlyOfficeContext = this.reflector.getAllAndOverride(_onlyofficeconstants.ONLY_OFFICE_CONTEXT, [
|
|
41
42
|
ctx.getHandler(),
|
|
42
43
|
ctx.getClass()
|
|
43
44
|
]);
|
|
44
|
-
|
|
45
|
+
const collaboraOnlineContext = this.reflector.getAllAndOverride(_collaboraonlineconstants.COLLABORA_CONTEXT, [
|
|
46
|
+
ctx.getHandler(),
|
|
47
|
+
ctx.getClass()
|
|
48
|
+
]);
|
|
49
|
+
return authTokenSkip || webDAVContext || onlyOfficeContext || collaboraOnlineContext || super.canActivate(ctx);
|
|
45
50
|
}
|
|
46
51
|
handleRequest(err, user, info, ctx, status) {
|
|
47
52
|
const req = this.getRequest(ctx);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../backend/src/authentication/guards/auth-token-access.guard.ts"],"sourcesContent":["/*\n * Copyright (C) 2012-2025 Johan Legrand <johan.legrand@sync-in.com>\n * This file is part of Sync-in | The open source file sync and share solution\n * See the LICENSE file for licensing details\n */\n\nimport { ExecutionContext, Injectable, Logger } from '@nestjs/common'\nimport { Reflector } from '@nestjs/core'\nimport { AuthGuard, IAuthGuard } from '@nestjs/passport'\nimport {
|
|
1
|
+
{"version":3,"sources":["../../../../backend/src/authentication/guards/auth-token-access.guard.ts"],"sourcesContent":["/*\n * Copyright (C) 2012-2025 Johan Legrand <johan.legrand@sync-in.com>\n * This file is part of Sync-in | The open source file sync and share solution\n * See the LICENSE file for licensing details\n */\n\nimport { ExecutionContext, Injectable, Logger } from '@nestjs/common'\nimport { Reflector } from '@nestjs/core'\nimport { AuthGuard, IAuthGuard } from '@nestjs/passport'\nimport { COLLABORA_CONTEXT } from '../../applications/files/modules/collabora-online/collabora-online.constants'\nimport { ONLY_OFFICE_CONTEXT } from '../../applications/files/modules/only-office/only-office.constants'\nimport { WEB_DAV_CONTEXT } from '../../applications/webdav/decorators/webdav-context.decorator'\nimport { AUTH_TOKEN_SKIP } from '../decorators/auth-token-skip.decorator'\n\n@Injectable()\nexport class AuthTokenAccessGuard extends AuthGuard('tokenAccess') implements IAuthGuard {\n private readonly logger = new Logger(AuthTokenAccessGuard.name)\n\n constructor(private readonly reflector: Reflector) {\n super()\n }\n\n canActivate(ctx: ExecutionContext) {\n const authTokenSkip: boolean = this.reflector.getAllAndOverride<boolean>(AUTH_TOKEN_SKIP, [ctx.getHandler(), ctx.getClass()])\n const webDAVContext: boolean = this.reflector.getAllAndOverride<boolean>(WEB_DAV_CONTEXT, [ctx.getHandler(), ctx.getClass()])\n const onlyOfficeContext: boolean = this.reflector.getAllAndOverride<boolean>(ONLY_OFFICE_CONTEXT, [ctx.getHandler(), ctx.getClass()])\n const collaboraOnlineContext: boolean = this.reflector.getAllAndOverride<boolean>(COLLABORA_CONTEXT, [ctx.getHandler(), ctx.getClass()])\n return authTokenSkip || webDAVContext || onlyOfficeContext || collaboraOnlineContext || super.canActivate(ctx)\n }\n\n handleRequest<TUser = any>(err: any, user: any, info: Error, ctx: ExecutionContext, status?: any): TUser {\n const req = this.getRequest(ctx)\n req.raw.user = user?.login || 'unauthorized'\n if (info) {\n this.logger.warn(`<${req.raw.user}> <${req.ip}> ${info}`)\n }\n return super.handleRequest(err, user, info, ctx, status)\n }\n}\n"],"names":["AuthTokenAccessGuard","AuthGuard","canActivate","ctx","authTokenSkip","reflector","getAllAndOverride","AUTH_TOKEN_SKIP","getHandler","getClass","webDAVContext","WEB_DAV_CONTEXT","onlyOfficeContext","ONLY_OFFICE_CONTEXT","collaboraOnlineContext","COLLABORA_CONTEXT","handleRequest","err","user","info","status","req","getRequest","raw","login","logger","warn","ip","Logger","name"],"mappings":"AAAA;;;;CAIC;;;;+BAWYA;;;eAAAA;;;wBATwC;sBAC3B;0BACY;0CACJ;qCACE;wCACJ;wCACA;;;;;;;;;;AAGzB,IAAA,AAAMA,uBAAN,MAAMA,6BAA6BC,IAAAA,mBAAS,EAAC;IAOlDC,YAAYC,GAAqB,EAAE;QACjC,MAAMC,gBAAyB,IAAI,CAACC,SAAS,CAACC,iBAAiB,CAAUC,uCAAe,EAAE;YAACJ,IAAIK,UAAU;YAAIL,IAAIM,QAAQ;SAAG;QAC5H,MAAMC,gBAAyB,IAAI,CAACL,SAAS,CAACC,iBAAiB,CAAUK,uCAAe,EAAE;YAACR,IAAIK,UAAU;YAAIL,IAAIM,QAAQ;SAAG;QAC5H,MAAMG,oBAA6B,IAAI,CAACP,SAAS,CAACC,iBAAiB,CAAUO,wCAAmB,EAAE;YAACV,IAAIK,UAAU;YAAIL,IAAIM,QAAQ;SAAG;QACpI,MAAMK,yBAAkC,IAAI,CAACT,SAAS,CAACC,iBAAiB,CAAUS,2CAAiB,EAAE;YAACZ,IAAIK,UAAU;YAAIL,IAAIM,QAAQ;SAAG;QACvI,OAAOL,iBAAiBM,iBAAiBE,qBAAqBE,0BAA0B,KAAK,CAACZ,YAAYC;IAC5G;IAEAa,cAA2BC,GAAQ,EAAEC,IAAS,EAAEC,IAAW,EAAEhB,GAAqB,EAAEiB,MAAY,EAAS;QACvG,MAAMC,MAAM,IAAI,CAACC,UAAU,CAACnB;QAC5BkB,IAAIE,GAAG,CAACL,IAAI,GAAGA,MAAMM,SAAS;QAC9B,IAAIL,MAAM;YACR,IAAI,CAACM,MAAM,CAACC,IAAI,CAAC,CAAC,CAAC,EAAEL,IAAIE,GAAG,CAACL,IAAI,CAAC,GAAG,EAAEG,IAAIM,EAAE,CAAC,EAAE,EAAER,MAAM;QAC1D;QACA,OAAO,KAAK,CAACH,cAAcC,KAAKC,MAAMC,MAAMhB,KAAKiB;IACnD;IAnBA,YAAY,AAAiBf,SAAoB,CAAE;QACjD,KAAK,SADsBA,YAAAA,gBAFZoB,SAAS,IAAIG,cAAM,CAAC5B,qBAAqB6B,IAAI;IAI9D;AAkBF"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (C) 2012-2026 Johan Legrand <johan.legrand@sync-in.com>
|
|
3
|
+
* This file is part of Sync-in | The open source file sync and share solution
|
|
4
|
+
* See the LICENSE file for licensing details
|
|
5
|
+
*/ "use strict";
|
|
6
|
+
Object.defineProperty(exports, "__esModule", {
|
|
7
|
+
value: true
|
|
8
|
+
});
|
|
9
|
+
Object.defineProperty(exports, "HttpBasicStrategy", {
|
|
10
|
+
enumerable: true,
|
|
11
|
+
get: function() {
|
|
12
|
+
return HttpBasicStrategy;
|
|
13
|
+
}
|
|
14
|
+
});
|
|
15
|
+
const _passportstrategy = require("passport-strategy");
|
|
16
|
+
function splitFirst(str, sep) {
|
|
17
|
+
const i = str.indexOf(sep);
|
|
18
|
+
if (i < 0) return [
|
|
19
|
+
str
|
|
20
|
+
];
|
|
21
|
+
return [
|
|
22
|
+
str.substring(0, i),
|
|
23
|
+
str.substring(i + 1)
|
|
24
|
+
];
|
|
25
|
+
}
|
|
26
|
+
let HttpBasicStrategy = class HttpBasicStrategy extends _passportstrategy.Strategy {
|
|
27
|
+
authenticate(req) {
|
|
28
|
+
const authorization = req?.headers?.authorization;
|
|
29
|
+
if (!authorization) {
|
|
30
|
+
return this.fail(this.challenge(), 401);
|
|
31
|
+
}
|
|
32
|
+
const parts = authorization.split(' ');
|
|
33
|
+
if (parts.length !== 2) {
|
|
34
|
+
return this.fail(400);
|
|
35
|
+
}
|
|
36
|
+
if (!/^Basic$/i.test(parts[0])) {
|
|
37
|
+
return this.fail(this.challenge(), 401);
|
|
38
|
+
}
|
|
39
|
+
let decoded;
|
|
40
|
+
try {
|
|
41
|
+
decoded = Buffer.from(parts[1], 'base64').toString();
|
|
42
|
+
} catch {
|
|
43
|
+
return this.fail(400);
|
|
44
|
+
}
|
|
45
|
+
const [userid, password] = splitFirst(decoded, ':');
|
|
46
|
+
if (password === undefined) {
|
|
47
|
+
return this.fail(400);
|
|
48
|
+
}
|
|
49
|
+
const verified = (err, user)=>{
|
|
50
|
+
if (err) return this.error(err);
|
|
51
|
+
if (!user) return this.fail(this.challenge(), 401);
|
|
52
|
+
return this.success(user);
|
|
53
|
+
};
|
|
54
|
+
if (this.passReqToCallback) {
|
|
55
|
+
;
|
|
56
|
+
this.verify(req, userid, password, verified);
|
|
57
|
+
} else {
|
|
58
|
+
;
|
|
59
|
+
this.verify(userid, password, verified);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
challenge() {
|
|
63
|
+
return `Basic realm="${this.realm}"`;
|
|
64
|
+
}
|
|
65
|
+
constructor(options = {}, verify){
|
|
66
|
+
super(), this.name = 'basic';
|
|
67
|
+
if (!verify) {
|
|
68
|
+
throw new TypeError('HttpBasicStrategy requires a verify callback');
|
|
69
|
+
}
|
|
70
|
+
this.verify = verify;
|
|
71
|
+
this.realm = options.realm ?? 'Sync-in';
|
|
72
|
+
this.passReqToCallback = !!options.passReqToCallback;
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
//# sourceMappingURL=http-basic.strategy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../backend/src/authentication/guards/implementations/http-basic.strategy.ts"],"sourcesContent":["/*\n * Copyright (C) 2012-2026 Johan Legrand <johan.legrand@sync-in.com>\n * This file is part of Sync-in | The open source file sync and share solution\n * See the LICENSE file for licensing details\n */\n\nimport { Strategy as PassportStrategy } from 'passport-strategy'\n\nexport type BasicVerifyCallback = (err?: any, user?: any) => void\n\nexport type BasicVerifyFunction = (userid: string, password: string, done: BasicVerifyCallback) => void\n\nexport type BasicVerifyFunctionWithRequest = (req: any, userid: string, password: string, done: BasicVerifyCallback) => void\n\nfunction splitFirst(str: string, sep: string): [string] | [string, string] {\n const i = str.indexOf(sep)\n if (i < 0) return [str]\n return [str.substring(0, i), str.substring(i + 1)]\n}\n\nexport interface HttpBasicStrategyOptions {\n realm?: string\n passReqToCallback?: boolean\n}\n\n/**\n * Pure HTTP Basic authentication strategy\n **/\nexport class HttpBasicStrategy extends PassportStrategy {\n name = 'basic'\n\n private readonly verify: BasicVerifyFunction | BasicVerifyFunctionWithRequest\n private readonly realm: string\n private readonly passReqToCallback: boolean\n\n constructor(options: HttpBasicStrategyOptions = {}, verify: BasicVerifyFunction | BasicVerifyFunctionWithRequest) {\n super()\n\n if (!verify) {\n throw new TypeError('HttpBasicStrategy requires a verify callback')\n }\n\n this.verify = verify\n this.realm = options.realm ?? 'Sync-in'\n this.passReqToCallback = !!options.passReqToCallback\n }\n\n authenticate(req: any): void {\n const authorization: string | undefined = req?.headers?.authorization\n if (!authorization) {\n return this.fail(this.challenge(), 401)\n }\n\n const parts = authorization.split(' ')\n if (parts.length !== 2) {\n return this.fail(400)\n }\n\n if (!/^Basic$/i.test(parts[0])) {\n return this.fail(this.challenge(), 401)\n }\n\n let decoded: string\n try {\n decoded = Buffer.from(parts[1], 'base64').toString()\n } catch {\n return this.fail(400)\n }\n\n const [userid, password] = splitFirst(decoded, ':')\n if (password === undefined) {\n return this.fail(400)\n }\n\n const verified: BasicVerifyCallback = (err, user) => {\n if (err) return this.error(err)\n if (!user) return this.fail(this.challenge(), 401)\n return this.success(user)\n }\n\n if (this.passReqToCallback) {\n ;(this.verify as BasicVerifyFunctionWithRequest)(req, userid, password, verified)\n } else {\n ;(this.verify as BasicVerifyFunction)(userid, password, verified)\n }\n }\n\n private challenge(): string {\n return `Basic realm=\"${this.realm}\"`\n }\n}\n"],"names":["HttpBasicStrategy","splitFirst","str","sep","i","indexOf","substring","PassportStrategy","authenticate","req","authorization","headers","fail","challenge","parts","split","length","test","decoded","Buffer","from","toString","userid","password","undefined","verified","err","user","error","success","passReqToCallback","verify","realm","options","name","TypeError"],"mappings":"AAAA;;;;CAIC;;;;+BAwBYA;;;eAAAA;;;kCAtBgC;AAQ7C,SAASC,WAAWC,GAAW,EAAEC,GAAW;IAC1C,MAAMC,IAAIF,IAAIG,OAAO,CAACF;IACtB,IAAIC,IAAI,GAAG,OAAO;QAACF;KAAI;IACvB,OAAO;QAACA,IAAII,SAAS,CAAC,GAAGF;QAAIF,IAAII,SAAS,CAACF,IAAI;KAAG;AACpD;AAUO,IAAA,AAAMJ,oBAAN,MAAMA,0BAA0BO,0BAAgB;IAmBrDC,aAAaC,GAAQ,EAAQ;QAC3B,MAAMC,gBAAoCD,KAAKE,SAASD;QACxD,IAAI,CAACA,eAAe;YAClB,OAAO,IAAI,CAACE,IAAI,CAAC,IAAI,CAACC,SAAS,IAAI;QACrC;QAEA,MAAMC,QAAQJ,cAAcK,KAAK,CAAC;QAClC,IAAID,MAAME,MAAM,KAAK,GAAG;YACtB,OAAO,IAAI,CAACJ,IAAI,CAAC;QACnB;QAEA,IAAI,CAAC,WAAWK,IAAI,CAACH,KAAK,CAAC,EAAE,GAAG;YAC9B,OAAO,IAAI,CAACF,IAAI,CAAC,IAAI,CAACC,SAAS,IAAI;QACrC;QAEA,IAAIK;QACJ,IAAI;YACFA,UAAUC,OAAOC,IAAI,CAACN,KAAK,CAAC,EAAE,EAAE,UAAUO,QAAQ;QACpD,EAAE,OAAM;YACN,OAAO,IAAI,CAACT,IAAI,CAAC;QACnB;QAEA,MAAM,CAACU,QAAQC,SAAS,GAAGtB,WAAWiB,SAAS;QAC/C,IAAIK,aAAaC,WAAW;YAC1B,OAAO,IAAI,CAACZ,IAAI,CAAC;QACnB;QAEA,MAAMa,WAAgC,CAACC,KAAKC;YAC1C,IAAID,KAAK,OAAO,IAAI,CAACE,KAAK,CAACF;YAC3B,IAAI,CAACC,MAAM,OAAO,IAAI,CAACf,IAAI,CAAC,IAAI,CAACC,SAAS,IAAI;YAC9C,OAAO,IAAI,CAACgB,OAAO,CAACF;QACtB;QAEA,IAAI,IAAI,CAACG,iBAAiB,EAAE;;YACxB,IAAI,CAACC,MAAM,CAAoCtB,KAAKa,QAAQC,UAAUE;QAC1E,OAAO;;YACH,IAAI,CAACM,MAAM,CAAyBT,QAAQC,UAAUE;QAC1D;IACF;IAEQZ,YAAoB;QAC1B,OAAO,CAAC,aAAa,EAAE,IAAI,CAACmB,KAAK,CAAC,CAAC,CAAC;IACtC;IAtDA,YAAYC,UAAoC,CAAC,CAAC,EAAEF,MAA4D,CAAE;QAChH,KAAK,SAPPG,OAAO;QASL,IAAI,CAACH,QAAQ;YACX,MAAM,IAAII,UAAU;QACtB;QAEA,IAAI,CAACJ,MAAM,GAAGA;QACd,IAAI,CAACC,KAAK,GAAGC,QAAQD,KAAK,IAAI;QAC9B,IAAI,CAACF,iBAAiB,GAAG,CAAC,CAACG,QAAQH,iBAAiB;IACtD;AA6CF"}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (C) 2012-2026 Johan Legrand <johan.legrand@sync-in.com>
|
|
3
|
+
* This file is part of Sync-in | The open source file sync and share solution
|
|
4
|
+
* See the LICENSE file for licensing details
|
|
5
|
+
*/ "use strict";
|
|
6
|
+
Object.defineProperty(exports, "__esModule", {
|
|
7
|
+
value: true
|
|
8
|
+
});
|
|
9
|
+
Object.defineProperty(exports, "HttpDigestStrategy", {
|
|
10
|
+
enumerable: true,
|
|
11
|
+
get: function() {
|
|
12
|
+
return HttpDigestStrategy;
|
|
13
|
+
}
|
|
14
|
+
});
|
|
15
|
+
const _nodecrypto = /*#__PURE__*/ _interop_require_default(require("node:crypto"));
|
|
16
|
+
const _passportstrategy = require("passport-strategy");
|
|
17
|
+
function _interop_require_default(obj) {
|
|
18
|
+
return obj && obj.__esModule ? obj : {
|
|
19
|
+
default: obj
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
function md5(str, encoding = 'hex') {
|
|
23
|
+
return _nodecrypto.default.createHash('md5').update(str).digest(encoding);
|
|
24
|
+
}
|
|
25
|
+
function nonce(len) {
|
|
26
|
+
const buf = [];
|
|
27
|
+
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
|
28
|
+
const charlen = chars.length;
|
|
29
|
+
for(let i = 0; i < len; ++i){
|
|
30
|
+
buf.push(chars[Math.random() * charlen | 0]);
|
|
31
|
+
}
|
|
32
|
+
return buf.join('');
|
|
33
|
+
}
|
|
34
|
+
// parsing identique à digest.js
|
|
35
|
+
function parse(params) {
|
|
36
|
+
const opts = {};
|
|
37
|
+
const tokens = params.split(/,(?=(?:[^"]|"[^"]*")*$)/);
|
|
38
|
+
for (const token of tokens){
|
|
39
|
+
const m = /(\w+)=["]?([^"]+)["]?$/.exec(token);
|
|
40
|
+
if (m) opts[m[1]] = m[2];
|
|
41
|
+
}
|
|
42
|
+
return opts;
|
|
43
|
+
}
|
|
44
|
+
let HttpDigestStrategy = class HttpDigestStrategy extends _passportstrategy.Strategy {
|
|
45
|
+
authenticate(req) {
|
|
46
|
+
const authorization = req?.headers?.authorization;
|
|
47
|
+
if (!authorization) {
|
|
48
|
+
return this.fail(this.challenge(), 401);
|
|
49
|
+
}
|
|
50
|
+
const parts = authorization.split(' ');
|
|
51
|
+
if (parts.length < 2) return this.fail(400);
|
|
52
|
+
const scheme = parts[0];
|
|
53
|
+
const params = parts.slice(1).join(' ');
|
|
54
|
+
if (!/Digest/i.test(scheme)) {
|
|
55
|
+
return this.fail(this.challenge(), 401);
|
|
56
|
+
}
|
|
57
|
+
const creds = parse(params);
|
|
58
|
+
if (Object.keys(creds).length === 0) return this.fail(400);
|
|
59
|
+
if (!creds.username) return this.fail(this.challenge(), 401);
|
|
60
|
+
// Même check que digest.js : req.url doit matcher creds.uri
|
|
61
|
+
// (en Fastify, req.url inclut la querystring, ce qui est généralement OK)
|
|
62
|
+
if (req.url !== creds.uri) return this.fail(400);
|
|
63
|
+
const verified = (err, result)=>{
|
|
64
|
+
if (err) return this.error(err);
|
|
65
|
+
if (!result || !result.user) return this.fail(this.challenge(), 401);
|
|
66
|
+
const { user, secret } = result;
|
|
67
|
+
if (!secret) return this.fail(400);
|
|
68
|
+
// compute HA1
|
|
69
|
+
let ha1;
|
|
70
|
+
const algo = creds.algorithm || 'MD5';
|
|
71
|
+
if (algo === 'MD5') {
|
|
72
|
+
if (typeof secret === 'object' && secret.ha1) {
|
|
73
|
+
ha1 = secret.ha1;
|
|
74
|
+
} else {
|
|
75
|
+
// password en clair (peu probable)
|
|
76
|
+
ha1 = md5(`${creds.username}:${creds.realm}:${String(secret)}`);
|
|
77
|
+
}
|
|
78
|
+
} else if (algo === 'MD5-sess') {
|
|
79
|
+
// idem digest.js (note: nonce/cnonce init non gérés)
|
|
80
|
+
const base = typeof secret === 'object' && secret.ha1 ? secret.ha1 : md5(`${creds.username}:${creds.realm}:${String(secret)}`);
|
|
81
|
+
ha1 = md5(`${base}:${creds.nonce}:${creds.cnonce}`);
|
|
82
|
+
} else {
|
|
83
|
+
return this.fail(400);
|
|
84
|
+
}
|
|
85
|
+
// compute HA2
|
|
86
|
+
let ha2;
|
|
87
|
+
if (!creds.qop || creds.qop === 'auth') {
|
|
88
|
+
ha2 = md5(`${req.method}:${creds.uri}`);
|
|
89
|
+
} else if (creds.qop === 'auth-int') {
|
|
90
|
+
return this.error(new Error('auth-int not implemented'));
|
|
91
|
+
} else {
|
|
92
|
+
return this.fail(400);
|
|
93
|
+
}
|
|
94
|
+
// compute expected digest
|
|
95
|
+
let digest;
|
|
96
|
+
if (!creds.qop) {
|
|
97
|
+
digest = md5(`${ha1}:${creds.nonce}:${ha2}`);
|
|
98
|
+
} else if (creds.qop === 'auth' || creds.qop === 'auth-int') {
|
|
99
|
+
digest = md5(`${ha1}:${creds.nonce}:${creds.nc}:${creds.cnonce}:${creds.qop}:${ha2}`);
|
|
100
|
+
} else {
|
|
101
|
+
return this.fail(400);
|
|
102
|
+
}
|
|
103
|
+
if (creds.response !== digest) {
|
|
104
|
+
return this.fail(this.challenge(), 401);
|
|
105
|
+
}
|
|
106
|
+
if (this.validateCb) {
|
|
107
|
+
return this.validateCb({
|
|
108
|
+
nonce: creds.nonce,
|
|
109
|
+
cnonce: creds.cnonce,
|
|
110
|
+
nc: creds.nc,
|
|
111
|
+
opaque: creds.opaque
|
|
112
|
+
}, (e, ok)=>{
|
|
113
|
+
if (e) return this.error(e);
|
|
114
|
+
if (!ok) return this.fail(this.challenge(), 401);
|
|
115
|
+
return this.success(user);
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
return this.success(user);
|
|
119
|
+
};
|
|
120
|
+
if (this.passReqToCallback) {
|
|
121
|
+
;
|
|
122
|
+
this.verify(req, creds.username, verified);
|
|
123
|
+
} else {
|
|
124
|
+
;
|
|
125
|
+
this.verify(creds.username, verified);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
challenge() {
|
|
129
|
+
let challenge = `Digest realm="${this.realm}"`;
|
|
130
|
+
if (this.domain) challenge += `, domain="${this.domain.join(' ')}"`;
|
|
131
|
+
challenge += `, nonce="${nonce(32)}"`;
|
|
132
|
+
if (this.opaque) challenge += `, opaque="${this.opaque}"`;
|
|
133
|
+
if (this.algorithm) challenge += `, algorithm=${this.algorithm}`;
|
|
134
|
+
if (this.qop) challenge += `, qop="${this.qop.join(',')}"`;
|
|
135
|
+
return challenge;
|
|
136
|
+
}
|
|
137
|
+
constructor(options = {}, verify){
|
|
138
|
+
super(), this.name = 'digest';
|
|
139
|
+
if (!verify) throw new Error('HTTP Digest authentication strategy requires a secret function');
|
|
140
|
+
this.verify = verify;
|
|
141
|
+
this.realm = options.realm ?? 'Users';
|
|
142
|
+
if (options.domain) this.domain = Array.isArray(options.domain) ? options.domain : [
|
|
143
|
+
options.domain
|
|
144
|
+
];
|
|
145
|
+
this.opaque = options.opaque;
|
|
146
|
+
this.algorithm = options.algorithm;
|
|
147
|
+
this.qop = options.qop ? Array.isArray(options.qop) ? options.qop : [
|
|
148
|
+
options.qop
|
|
149
|
+
] : undefined;
|
|
150
|
+
this.validateCb = options.validate;
|
|
151
|
+
this.passReqToCallback = options.passReqToCallback === true;
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
//# sourceMappingURL=http-digest.strategy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../backend/src/authentication/guards/implementations/http-digest.strategy.ts"],"sourcesContent":["/*\n * Copyright (C) 2012-2026 Johan Legrand <johan.legrand@sync-in.com>\n * This file is part of Sync-in | The open source file sync and share solution\n * See the LICENSE file for licensing details\n */\n\nimport crypto from 'node:crypto'\nimport { Strategy as PassportStrategy } from 'passport-strategy'\n\nexport interface DigestValidateParams {\n nonce?: string\n cnonce?: string\n nc?: string\n opaque?: string\n}\n\nexport type DigestValidateCallback = (err?: any, valid?: boolean) => void\n\n// ⚠️ verify (secret callback) : on attend un \"user container\" qui contient aussi le secret\nexport type DigestVerifyCallback = (err?: any, result?: any) => void\n\nexport type DigestVerifyFunction = (username: string, done: DigestVerifyCallback) => void\nexport type DigestVerifyFunctionWithRequest = (req: any, username: string, done: DigestVerifyCallback) => void\n\nexport type DigestSecret =\n | string // password en clair (rare)\n | { ha1: string } // recommandé : HA1 = MD5(username:realm:password)\n\nexport interface DigestVerifyResult {\n user: any\n secret: DigestSecret\n}\n\nexport interface HttpDigestStrategyOptionsBase {\n realm?: string\n domain?: string | string[]\n opaque?: string\n algorithm?: 'MD5' | 'MD5-sess'\n qop?: ('auth' | 'auth-int')[] | 'auth' | 'auth-int'\n // optionnel : anti-replay\n validate?: (params: DigestValidateParams, done: DigestValidateCallback) => void\n}\n\nexport type HttpDigestStrategyOptionsWithReq = HttpDigestStrategyOptionsBase & {\n passReqToCallback: true\n}\n\nexport type HttpDigestStrategyOptionsNoReq = HttpDigestStrategyOptionsBase & {\n passReqToCallback?: false | undefined\n}\n\nfunction md5(str: string, encoding: crypto.BinaryToTextEncoding = 'hex'): string {\n return crypto.createHash('md5').update(str).digest(encoding)\n}\n\nfunction nonce(len: number): string {\n const buf: string[] = []\n const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'\n const charlen = chars.length\n for (let i = 0; i < len; ++i) {\n buf.push(chars[(Math.random() * charlen) | 0])\n }\n return buf.join('')\n}\n\n// parsing identique à digest.js\nfunction parse(params: string): Record<string, string> {\n const opts: Record<string, string> = {}\n const tokens = params.split(/,(?=(?:[^\"]|\"[^\"]*\")*$)/)\n for (const token of tokens) {\n const m = /(\\w+)=[\"]?([^\"]+)[\"]?$/.exec(token)\n if (m) opts[m[1]] = m[2]\n }\n return opts\n}\n\nexport class HttpDigestStrategy extends PassportStrategy {\n name = 'digest'\n\n private readonly realm: string\n private readonly domain?: string[]\n private readonly opaque?: string\n private readonly algorithm?: 'MD5' | 'MD5-sess'\n private readonly qop?: ('auth' | 'auth-int')[]\n private readonly passReqToCallback: boolean\n private readonly validateCb?: (params: DigestValidateParams, done: DigestValidateCallback) => void\n\n private readonly verify: DigestVerifyFunction | DigestVerifyFunctionWithRequest\n\n // overloads typés selon passReqToCallback\n constructor(options: HttpDigestStrategyOptionsWithReq, verify: DigestVerifyFunctionWithRequest)\n constructor(options?: HttpDigestStrategyOptionsNoReq, verify?: DigestVerifyFunction)\n constructor(\n options: HttpDigestStrategyOptionsWithReq | HttpDigestStrategyOptionsNoReq = {},\n verify?: DigestVerifyFunction | DigestVerifyFunctionWithRequest\n ) {\n super()\n if (!verify) throw new Error('HTTP Digest authentication strategy requires a secret function')\n\n this.verify = verify\n this.realm = options.realm ?? 'Users'\n\n if (options.domain) this.domain = Array.isArray(options.domain) ? options.domain : [options.domain]\n this.opaque = options.opaque\n this.algorithm = options.algorithm\n this.qop = options.qop ? (Array.isArray(options.qop) ? options.qop : [options.qop]) : undefined\n\n this.validateCb = options.validate\n this.passReqToCallback = (options as HttpDigestStrategyOptionsWithReq).passReqToCallback === true\n }\n\n authenticate(req: any): void {\n const authorization: string | undefined = req?.headers?.authorization\n if (!authorization) {\n return this.fail(this.challenge(), 401)\n }\n\n const parts = authorization.split(' ')\n if (parts.length < 2) return this.fail(400 as any)\n\n const scheme = parts[0]\n const params = parts.slice(1).join(' ')\n if (!/Digest/i.test(scheme)) {\n return this.fail(this.challenge(), 401)\n }\n\n const creds = parse(params)\n if (Object.keys(creds).length === 0) return this.fail(400 as any)\n if (!creds.username) return this.fail(this.challenge(), 401)\n\n // Même check que digest.js : req.url doit matcher creds.uri\n // (en Fastify, req.url inclut la querystring, ce qui est généralement OK)\n if (req.url !== creds.uri) return this.fail(400 as any)\n\n const verified: DigestVerifyCallback = (err, result) => {\n if (err) return this.error(err)\n if (!result || !result.user) return this.fail(this.challenge(), 401)\n\n const { user, secret } = result as DigestVerifyResult\n if (!secret) return this.fail(400 as any)\n\n // compute HA1\n let ha1: string\n const algo = creds.algorithm || 'MD5'\n if (algo === 'MD5') {\n if (typeof secret === 'object' && (secret as any).ha1) {\n ha1 = (secret as any).ha1\n } else {\n // password en clair (peu probable)\n ha1 = md5(`${creds.username}:${creds.realm}:${String(secret)}`)\n }\n } else if (algo === 'MD5-sess') {\n // idem digest.js (note: nonce/cnonce init non gérés)\n const base =\n typeof secret === 'object' && (secret as any).ha1 ? (secret as any).ha1 : md5(`${creds.username}:${creds.realm}:${String(secret)}`)\n ha1 = md5(`${base}:${creds.nonce}:${creds.cnonce}`)\n } else {\n return this.fail(400 as any)\n }\n\n // compute HA2\n let ha2: string\n if (!creds.qop || creds.qop === 'auth') {\n ha2 = md5(`${req.method}:${creds.uri}`)\n } else if (creds.qop === 'auth-int') {\n return this.error(new Error('auth-int not implemented'))\n } else {\n return this.fail(400 as any)\n }\n\n // compute expected digest\n let digest: string\n if (!creds.qop) {\n digest = md5(`${ha1}:${creds.nonce}:${ha2}`)\n } else if (creds.qop === 'auth' || creds.qop === 'auth-int') {\n digest = md5(`${ha1}:${creds.nonce}:${creds.nc}:${creds.cnonce}:${creds.qop}:${ha2}`)\n } else {\n return this.fail(400 as any)\n }\n\n if (creds.response !== digest) {\n return this.fail(this.challenge(), 401)\n }\n\n if (this.validateCb) {\n return this.validateCb({ nonce: creds.nonce, cnonce: creds.cnonce, nc: creds.nc, opaque: creds.opaque }, (e, ok) => {\n if (e) return this.error(e)\n if (!ok) return this.fail(this.challenge(), 401)\n return this.success(user)\n })\n }\n\n return this.success(user)\n }\n\n if (this.passReqToCallback) {\n ;(this.verify as DigestVerifyFunctionWithRequest)(req, creds.username, verified)\n } else {\n ;(this.verify as DigestVerifyFunction)(creds.username, verified)\n }\n }\n\n private challenge(): string {\n let challenge = `Digest realm=\"${this.realm}\"`\n if (this.domain) challenge += `, domain=\"${this.domain.join(' ')}\"`\n challenge += `, nonce=\"${nonce(32)}\"`\n if (this.opaque) challenge += `, opaque=\"${this.opaque}\"`\n if (this.algorithm) challenge += `, algorithm=${this.algorithm}`\n if (this.qop) challenge += `, qop=\"${this.qop.join(',')}\"`\n return challenge\n }\n}\n"],"names":["HttpDigestStrategy","md5","str","encoding","crypto","createHash","update","digest","nonce","len","buf","chars","charlen","length","i","push","Math","random","join","parse","params","opts","tokens","split","token","m","exec","PassportStrategy","authenticate","req","authorization","headers","fail","challenge","parts","scheme","slice","test","creds","Object","keys","username","url","uri","verified","err","result","error","user","secret","ha1","algo","algorithm","realm","String","base","cnonce","ha2","qop","method","Error","nc","response","validateCb","opaque","e","ok","success","passReqToCallback","verify","domain","options","name","Array","isArray","undefined","validate"],"mappings":"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"}
|
|
@@ -114,8 +114,7 @@ let AuthManager = class AuthManager {
|
|
|
114
114
|
async clearCookies(res) {
|
|
115
115
|
for (const [type, path] of Object.entries(_auth.TOKEN_PATHS)){
|
|
116
116
|
res.clearCookie(_configenvironment.configuration.auth.token[type].name, {
|
|
117
|
-
path: path
|
|
118
|
-
httpOnly: type !== _tokeninterface.TOKEN_TYPE.CSRF
|
|
117
|
+
path: path
|
|
119
118
|
});
|
|
120
119
|
}
|
|
121
120
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../backend/src/authentication/services/auth-manager.service.ts"],"sourcesContent":["/*\n * Copyright (C) 2012-2025 Johan Legrand <johan.legrand@sync-in.com>\n * This file is part of Sync-in | The open source file sync and share solution\n * See the LICENSE file for licensing details\n */\nimport { unsign, UnsignResult } from '@fastify/cookie'\nimport { HttpException, HttpStatus, Injectable, Logger } from '@nestjs/common'\nimport { JwtService } from '@nestjs/jwt'\nimport { FastifyReply, FastifyRequest } from 'fastify'\nimport crypto from 'node:crypto'\nimport { HTTP_CSRF_IGNORED_METHODS } from '../../applications/applications.constants'\nimport { UserModel } from '../../applications/users/models/user.model'\nimport { convertHumanTimeToSeconds } from '../../common/functions'\nimport { currentTimeStamp } from '../../common/shared'\nimport { configuration, serverConfig } from '../../configuration/config.environment'\nimport { CSRF_ERROR, CSRF_KEY, TOKEN_2FA_TYPES, TOKEN_PATHS, TOKEN_TYPES } from '../constants/auth'\nimport { LoginResponseDto, LoginVerify2FaDto } from '../dto/login-response.dto'\nimport { TokenResponseDto } from '../dto/token-response.dto'\nimport { JwtIdentity2FaPayload, JwtIdentityPayload, JwtPayload } from '../interfaces/jwt-payload.interface'\nimport { TOKEN_TYPE } from '../interfaces/token.interface'\n\n@Injectable()\nexport class AuthManager {\n private readonly logger = new Logger(AuthManager.name)\n\n constructor(private readonly jwt: JwtService) {}\n\n async getTokens(user: UserModel, refresh = false): Promise<TokenResponseDto> {\n const currentTime = currentTimeStamp()\n if (refresh && user.exp < currentTime) {\n this.logger.error(`${this.getTokens.name} - token refresh has incorrect expiration : *${user.login}*`)\n throw new HttpException('Token has expired', HttpStatus.FORBIDDEN)\n }\n const accessExpiration = convertHumanTimeToSeconds(configuration.auth.token.access.expiration)\n const refreshExpiration = refresh ? user.exp - currentTime : convertHumanTimeToSeconds(configuration.auth.token.refresh.expiration)\n return {\n [TOKEN_TYPE.ACCESS]: await this.jwtSign(user, TOKEN_TYPE.ACCESS, accessExpiration),\n [TOKEN_TYPE.REFRESH]: await this.jwtSign(user, TOKEN_TYPE.REFRESH, refreshExpiration),\n [`${TOKEN_TYPE.ACCESS}_expiration`]: accessExpiration + currentTime,\n [`${TOKEN_TYPE.REFRESH}_expiration`]: refreshExpiration + currentTime\n }\n }\n\n async setCookies(user: UserModel, res: FastifyReply, init2FaVerify: true): Promise<LoginVerify2FaDto>\n async setCookies(user: UserModel, res: FastifyReply, init2FaVerify?: false): Promise<LoginResponseDto>\n async setCookies(user: UserModel, res: FastifyReply, init2FaVerify = false): Promise<LoginResponseDto | LoginVerify2FaDto> {\n // If `verify2Fa` is true, it sets the cookies and response required for valid 2FA authentication.\n const verify2Fa = init2FaVerify && configuration.auth.mfa.totp.enabled && user.twoFaEnabled\n const response = verify2Fa ? new LoginVerify2FaDto(serverConfig) : new LoginResponseDto(user, serverConfig)\n const currentTime = currentTimeStamp()\n const csrfToken: string = crypto.randomUUID()\n const tokenTypes: TOKEN_TYPE[] = verify2Fa ? TOKEN_2FA_TYPES : TOKEN_TYPES\n for (const type of tokenTypes) {\n const isCSRFToken = type === TOKEN_TYPE.CSRF || type === TOKEN_TYPE.CSRF_2FA\n const tokenExpiration = convertHumanTimeToSeconds(configuration.auth.token[type].expiration)\n let cookieValue: string\n if (isCSRFToken) {\n cookieValue = csrfToken\n } else if (verify2Fa) {\n cookieValue = await this.jwtSign2Fa(user, type, tokenExpiration, csrfToken)\n } else {\n cookieValue = await this.jwtSign(user, type, tokenExpiration, csrfToken)\n }\n res.setCookie(configuration.auth.token[type].name, cookieValue, {\n signed: isCSRFToken,\n path: TOKEN_PATHS[type],\n maxAge: tokenExpiration,\n httpOnly: !isCSRFToken\n })\n if (type === TOKEN_TYPE.ACCESS || type === TOKEN_TYPE.REFRESH || type === TOKEN_TYPE.ACCESS_2FA) {\n response.token[`${type}_expiration`] = tokenExpiration + currentTime\n }\n }\n return response\n }\n\n async refreshCookies(user: UserModel, res: FastifyReply): Promise<TokenResponseDto> {\n const response = {} as TokenResponseDto\n const currentTime = currentTimeStamp()\n let refreshTokenExpiration: number\n // refresh cookie must have the `exp` attribute\n // reuse token expiration to make it final\n if (user.exp && user.exp > currentTime) {\n refreshTokenExpiration = user.exp - currentTime\n } else {\n this.logger.error(`${this.refreshCookies.name} - token ${TOKEN_TYPE.REFRESH} has incorrect expiration : *${user.login}*`)\n throw new HttpException('Token has expired', HttpStatus.FORBIDDEN)\n }\n const csrfToken: string = crypto.randomUUID()\n for (const type of TOKEN_TYPES) {\n const tokenExpiration =\n type === TOKEN_TYPE.ACCESS ? convertHumanTimeToSeconds(configuration.auth.token[TOKEN_TYPE.ACCESS].expiration) : refreshTokenExpiration\n const cookieValue: string = type === TOKEN_TYPE.CSRF ? csrfToken : await this.jwtSign(user, type, tokenExpiration, csrfToken)\n res.setCookie(configuration.auth.token[type].name, cookieValue, {\n signed: type === TOKEN_TYPE.CSRF,\n path: TOKEN_PATHS[type],\n maxAge: tokenExpiration,\n httpOnly: type !== TOKEN_TYPE.CSRF\n })\n if (type === TOKEN_TYPE.ACCESS || type === TOKEN_TYPE.REFRESH) {\n response[`${type}_expiration`] = tokenExpiration + currentTime\n }\n }\n return response\n }\n\n async clearCookies(res: FastifyReply) {\n for (const [type, path] of Object.entries(TOKEN_PATHS)) {\n res.clearCookie(configuration.auth.token[type].name, { path: path, httpOnly: type !== TOKEN_TYPE.CSRF })\n }\n }\n\n csrfValidation(req: FastifyRequest, jwtPayload: JwtPayload, type: TOKEN_TYPE.ACCESS | TOKEN_TYPE.REFRESH): void {\n // ignore safe methods\n if (HTTP_CSRF_IGNORED_METHODS.has(req.method)) {\n return\n }\n\n // check csrf only for access and refresh cookies\n if (typeof req.cookies !== 'object' || req.cookies[configuration.auth.token[type].name] === undefined) {\n return\n }\n\n if (!jwtPayload.csrf) {\n this.logger.warn(`${this.csrfValidation.name} - ${CSRF_ERROR.MISSING_JWT}`)\n throw new HttpException(CSRF_ERROR.MISSING_JWT, HttpStatus.FORBIDDEN)\n }\n\n if (!req.headers[CSRF_KEY]) {\n this.logger.warn(`${this.csrfValidation.name} - ${CSRF_ERROR.MISSING_HEADERS}`)\n throw new HttpException(CSRF_ERROR.MISSING_HEADERS, HttpStatus.FORBIDDEN)\n }\n\n const csrfHeader: UnsignResult = unsign(req.headers[CSRF_KEY] as string, configuration.auth.token.csrf.secret)\n if (jwtPayload.csrf !== csrfHeader.value) {\n this.logger.warn(`${this.csrfValidation.name} - ${CSRF_ERROR.MISMATCH}`)\n throw new HttpException(CSRF_ERROR.MISMATCH, HttpStatus.FORBIDDEN)\n }\n }\n\n private jwtSign(user: UserModel, type: TOKEN_TYPE, expiration: number, csrfToken?: string): Promise<string> {\n return this.jwt.signAsync(\n {\n identity: {\n id: user.id,\n login: user.login,\n email: user.email,\n fullName: user.fullName,\n language: user.language,\n role: user.role,\n applications: user.applications,\n impersonatedFromId: user.impersonatedFromId || undefined,\n impersonatedClientId: user.impersonatedClientId || undefined,\n clientId: user.clientId || undefined,\n twoFaEnabled: user.twoFaEnabled || undefined\n } satisfies JwtIdentityPayload,\n ...((type === TOKEN_TYPE.ACCESS || type === TOKEN_TYPE.REFRESH) && { csrf: csrfToken })\n },\n {\n secret: configuration.auth.token[type].secret,\n expiresIn: expiration\n }\n )\n }\n\n private jwtSign2Fa(user: UserModel, type: TOKEN_TYPE, expiration: number, csrfToken: string): Promise<string> {\n // Restrict the temporary token to the minimum required information\n return this.jwt.signAsync(\n {\n identity: {\n id: user.id,\n login: user.login,\n language: user.language,\n role: user.role,\n twoFaEnabled: true\n } satisfies JwtIdentity2FaPayload,\n csrf: csrfToken\n },\n {\n secret: configuration.auth.token[type].secret,\n expiresIn: expiration\n }\n )\n }\n}\n"],"names":["AuthManager","getTokens","user","refresh","currentTime","currentTimeStamp","exp","logger","error","name","login","HttpException","HttpStatus","FORBIDDEN","accessExpiration","convertHumanTimeToSeconds","configuration","auth","token","access","expiration","refreshExpiration","TOKEN_TYPE","ACCESS","jwtSign","REFRESH","setCookies","res","init2FaVerify","verify2Fa","mfa","totp","enabled","twoFaEnabled","response","LoginVerify2FaDto","serverConfig","LoginResponseDto","csrfToken","crypto","randomUUID","tokenTypes","TOKEN_2FA_TYPES","TOKEN_TYPES","type","isCSRFToken","CSRF","CSRF_2FA","tokenExpiration","cookieValue","jwtSign2Fa","setCookie","signed","path","TOKEN_PATHS","maxAge","httpOnly","ACCESS_2FA","refreshCookies","refreshTokenExpiration","clearCookies","Object","entries","clearCookie","csrfValidation","req","jwtPayload","HTTP_CSRF_IGNORED_METHODS","has","method","cookies","undefined","csrf","warn","CSRF_ERROR","MISSING_JWT","headers","CSRF_KEY","MISSING_HEADERS","csrfHeader","unsign","secret","value","MISMATCH","jwt","signAsync","identity","id","email","fullName","language","role","applications","impersonatedFromId","impersonatedClientId","clientId","expiresIn","Logger"],"mappings":"AAAA;;;;CAIC;;;;+BAkBYA;;;eAAAA;;;wBAjBwB;wBACyB;qBACnC;mEAER;uCACuB;2BAEA;wBACT;mCACW;sBACoC;kCAC5B;gCAGzB;;;;;;;;;;;;;;;AAGpB,IAAA,AAAMA,cAAN,MAAMA;IAKX,MAAMC,UAAUC,IAAe,EAAEC,UAAU,KAAK,EAA6B;QAC3E,MAAMC,cAAcC,IAAAA,wBAAgB;QACpC,IAAIF,WAAWD,KAAKI,GAAG,GAAGF,aAAa;YACrC,IAAI,CAACG,MAAM,CAACC,KAAK,CAAC,GAAG,IAAI,CAACP,SAAS,CAACQ,IAAI,CAAC,6CAA6C,EAAEP,KAAKQ,KAAK,CAAC,CAAC,CAAC;YACrG,MAAM,IAAIC,qBAAa,CAAC,qBAAqBC,kBAAU,CAACC,SAAS;QACnE;QACA,MAAMC,mBAAmBC,IAAAA,oCAAyB,EAACC,gCAAa,CAACC,IAAI,CAACC,KAAK,CAACC,MAAM,CAACC,UAAU;QAC7F,MAAMC,oBAAoBlB,UAAUD,KAAKI,GAAG,GAAGF,cAAcW,IAAAA,oCAAyB,EAACC,gCAAa,CAACC,IAAI,CAACC,KAAK,CAACf,OAAO,CAACiB,UAAU;QAClI,OAAO;YACL,CAACE,0BAAU,CAACC,MAAM,CAAC,EAAE,MAAM,IAAI,CAACC,OAAO,CAACtB,MAAMoB,0BAAU,CAACC,MAAM,EAAET;YACjE,CAACQ,0BAAU,CAACG,OAAO,CAAC,EAAE,MAAM,IAAI,CAACD,OAAO,CAACtB,MAAMoB,0BAAU,CAACG,OAAO,EAAEJ;YACnE,CAAC,GAAGC,0BAAU,CAACC,MAAM,CAAC,WAAW,CAAC,CAAC,EAAET,mBAAmBV;YACxD,CAAC,GAAGkB,0BAAU,CAACG,OAAO,CAAC,WAAW,CAAC,CAAC,EAAEJ,oBAAoBjB;QAC5D;IACF;IAIA,MAAMsB,WAAWxB,IAAe,EAAEyB,GAAiB,EAAEC,gBAAgB,KAAK,EAAiD;QACzH,kGAAkG;QAClG,MAAMC,YAAYD,iBAAiBZ,gCAAa,CAACC,IAAI,CAACa,GAAG,CAACC,IAAI,CAACC,OAAO,IAAI9B,KAAK+B,YAAY;QAC3F,MAAMC,WAAWL,YAAY,IAAIM,mCAAiB,CAACC,+BAAY,IAAI,IAAIC,kCAAgB,CAACnC,MAAMkC,+BAAY;QAC1G,MAAMhC,cAAcC,IAAAA,wBAAgB;QACpC,MAAMiC,YAAoBC,mBAAM,CAACC,UAAU;QAC3C,MAAMC,aAA2BZ,YAAYa,qBAAe,GAAGC,iBAAW;QAC1E,KAAK,MAAMC,QAAQH,WAAY;YAC7B,MAAMI,cAAcD,SAAStB,0BAAU,CAACwB,IAAI,IAAIF,SAAStB,0BAAU,CAACyB,QAAQ;YAC5E,MAAMC,kBAAkBjC,IAAAA,oCAAyB,EAACC,gCAAa,CAACC,IAAI,CAACC,KAAK,CAAC0B,KAAK,CAACxB,UAAU;YAC3F,IAAI6B;YACJ,IAAIJ,aAAa;gBACfI,cAAcX;YAChB,OAAO,IAAIT,WAAW;gBACpBoB,cAAc,MAAM,IAAI,CAACC,UAAU,CAAChD,MAAM0C,MAAMI,iBAAiBV;YACnE,OAAO;gBACLW,cAAc,MAAM,IAAI,CAACzB,OAAO,CAACtB,MAAM0C,MAAMI,iBAAiBV;YAChE;YACAX,IAAIwB,SAAS,CAACnC,gCAAa,CAACC,IAAI,CAACC,KAAK,CAAC0B,KAAK,CAACnC,IAAI,EAAEwC,aAAa;gBAC9DG,QAAQP;gBACRQ,MAAMC,iBAAW,CAACV,KAAK;gBACvBW,QAAQP;gBACRQ,UAAU,CAACX;YACb;YACA,IAAID,SAAStB,0BAAU,CAACC,MAAM,IAAIqB,SAAStB,0BAAU,CAACG,OAAO,IAAImB,SAAStB,0BAAU,CAACmC,UAAU,EAAE;gBAC/FvB,SAAShB,KAAK,CAAC,GAAG0B,KAAK,WAAW,CAAC,CAAC,GAAGI,kBAAkB5C;YAC3D;QACF;QACA,OAAO8B;IACT;IAEA,MAAMwB,eAAexD,IAAe,EAAEyB,GAAiB,EAA6B;QAClF,MAAMO,WAAW,CAAC;QAClB,MAAM9B,cAAcC,IAAAA,wBAAgB;QACpC,IAAIsD;QACJ,+CAA+C;QAC/C,0CAA0C;QAC1C,IAAIzD,KAAKI,GAAG,IAAIJ,KAAKI,GAAG,GAAGF,aAAa;YACtCuD,yBAAyBzD,KAAKI,GAAG,GAAGF;QACtC,OAAO;YACL,IAAI,CAACG,MAAM,CAACC,KAAK,CAAC,GAAG,IAAI,CAACkD,cAAc,CAACjD,IAAI,CAAC,SAAS,EAAEa,0BAAU,CAACG,OAAO,CAAC,6BAA6B,EAAEvB,KAAKQ,KAAK,CAAC,CAAC,CAAC;YACxH,MAAM,IAAIC,qBAAa,CAAC,qBAAqBC,kBAAU,CAACC,SAAS;QACnE;QACA,MAAMyB,YAAoBC,mBAAM,CAACC,UAAU;QAC3C,KAAK,MAAMI,QAAQD,iBAAW,CAAE;YAC9B,MAAMK,kBACJJ,SAAStB,0BAAU,CAACC,MAAM,GAAGR,IAAAA,oCAAyB,EAACC,gCAAa,CAACC,IAAI,CAACC,KAAK,CAACI,0BAAU,CAACC,MAAM,CAAC,CAACH,UAAU,IAAIuC;YACnH,MAAMV,cAAsBL,SAAStB,0BAAU,CAACwB,IAAI,GAAGR,YAAY,MAAM,IAAI,CAACd,OAAO,CAACtB,MAAM0C,MAAMI,iBAAiBV;YACnHX,IAAIwB,SAAS,CAACnC,gCAAa,CAACC,IAAI,CAACC,KAAK,CAAC0B,KAAK,CAACnC,IAAI,EAAEwC,aAAa;gBAC9DG,QAAQR,SAAStB,0BAAU,CAACwB,IAAI;gBAChCO,MAAMC,iBAAW,CAACV,KAAK;gBACvBW,QAAQP;gBACRQ,UAAUZ,SAAStB,0BAAU,CAACwB,IAAI;YACpC;YACA,IAAIF,SAAStB,0BAAU,CAACC,MAAM,IAAIqB,SAAStB,0BAAU,CAACG,OAAO,EAAE;gBAC7DS,QAAQ,CAAC,GAAGU,KAAK,WAAW,CAAC,CAAC,GAAGI,kBAAkB5C;YACrD;QACF;QACA,OAAO8B;IACT;IAEA,MAAM0B,aAAajC,GAAiB,EAAE;QACpC,KAAK,MAAM,CAACiB,MAAMS,KAAK,IAAIQ,OAAOC,OAAO,CAACR,iBAAW,EAAG;YACtD3B,IAAIoC,WAAW,CAAC/C,gCAAa,CAACC,IAAI,CAACC,KAAK,CAAC0B,KAAK,CAACnC,IAAI,EAAE;gBAAE4C,MAAMA;gBAAMG,UAAUZ,SAAStB,0BAAU,CAACwB,IAAI;YAAC;QACxG;IACF;IAEAkB,eAAeC,GAAmB,EAAEC,UAAsB,EAAEtB,IAA4C,EAAQ;QAC9G,sBAAsB;QACtB,IAAIuB,gDAAyB,CAACC,GAAG,CAACH,IAAII,MAAM,GAAG;YAC7C;QACF;QAEA,iDAAiD;QACjD,IAAI,OAAOJ,IAAIK,OAAO,KAAK,YAAYL,IAAIK,OAAO,CAACtD,gCAAa,CAACC,IAAI,CAACC,KAAK,CAAC0B,KAAK,CAACnC,IAAI,CAAC,KAAK8D,WAAW;YACrG;QACF;QAEA,IAAI,CAACL,WAAWM,IAAI,EAAE;YACpB,IAAI,CAACjE,MAAM,CAACkE,IAAI,CAAC,GAAG,IAAI,CAACT,cAAc,CAACvD,IAAI,CAAC,GAAG,EAAEiE,gBAAU,CAACC,WAAW,EAAE;YAC1E,MAAM,IAAIhE,qBAAa,CAAC+D,gBAAU,CAACC,WAAW,EAAE/D,kBAAU,CAACC,SAAS;QACtE;QAEA,IAAI,CAACoD,IAAIW,OAAO,CAACC,cAAQ,CAAC,EAAE;YAC1B,IAAI,CAACtE,MAAM,CAACkE,IAAI,CAAC,GAAG,IAAI,CAACT,cAAc,CAACvD,IAAI,CAAC,GAAG,EAAEiE,gBAAU,CAACI,eAAe,EAAE;YAC9E,MAAM,IAAInE,qBAAa,CAAC+D,gBAAU,CAACI,eAAe,EAAElE,kBAAU,CAACC,SAAS;QAC1E;QAEA,MAAMkE,aAA2BC,IAAAA,cAAM,EAACf,IAAIW,OAAO,CAACC,cAAQ,CAAC,EAAY7D,gCAAa,CAACC,IAAI,CAACC,KAAK,CAACsD,IAAI,CAACS,MAAM;QAC7G,IAAIf,WAAWM,IAAI,KAAKO,WAAWG,KAAK,EAAE;YACxC,IAAI,CAAC3E,MAAM,CAACkE,IAAI,CAAC,GAAG,IAAI,CAACT,cAAc,CAACvD,IAAI,CAAC,GAAG,EAAEiE,gBAAU,CAACS,QAAQ,EAAE;YACvE,MAAM,IAAIxE,qBAAa,CAAC+D,gBAAU,CAACS,QAAQ,EAAEvE,kBAAU,CAACC,SAAS;QACnE;IACF;IAEQW,QAAQtB,IAAe,EAAE0C,IAAgB,EAAExB,UAAkB,EAAEkB,SAAkB,EAAmB;QAC1G,OAAO,IAAI,CAAC8C,GAAG,CAACC,SAAS,CACvB;YACEC,UAAU;gBACRC,IAAIrF,KAAKqF,EAAE;gBACX7E,OAAOR,KAAKQ,KAAK;gBACjB8E,OAAOtF,KAAKsF,KAAK;gBACjBC,UAAUvF,KAAKuF,QAAQ;gBACvBC,UAAUxF,KAAKwF,QAAQ;gBACvBC,MAAMzF,KAAKyF,IAAI;gBACfC,cAAc1F,KAAK0F,YAAY;gBAC/BC,oBAAoB3F,KAAK2F,kBAAkB,IAAItB;gBAC/CuB,sBAAsB5F,KAAK4F,oBAAoB,IAAIvB;gBACnDwB,UAAU7F,KAAK6F,QAAQ,IAAIxB;gBAC3BtC,cAAc/B,KAAK+B,YAAY,IAAIsC;YACrC;YACA,GAAI,AAAC3B,CAAAA,SAAStB,0BAAU,CAACC,MAAM,IAAIqB,SAAStB,0BAAU,CAACG,OAAO,AAAD,KAAM;gBAAE+C,MAAMlC;YAAU,CAAC;QACxF,GACA;YACE2C,QAAQjE,gCAAa,CAACC,IAAI,CAACC,KAAK,CAAC0B,KAAK,CAACqC,MAAM;YAC7Ce,WAAW5E;QACb;IAEJ;IAEQ8B,WAAWhD,IAAe,EAAE0C,IAAgB,EAAExB,UAAkB,EAAEkB,SAAiB,EAAmB;QAC5G,mEAAmE;QACnE,OAAO,IAAI,CAAC8C,GAAG,CAACC,SAAS,CACvB;YACEC,UAAU;gBACRC,IAAIrF,KAAKqF,EAAE;gBACX7E,OAAOR,KAAKQ,KAAK;gBACjBgF,UAAUxF,KAAKwF,QAAQ;gBACvBC,MAAMzF,KAAKyF,IAAI;gBACf1D,cAAc;YAChB;YACAuC,MAAMlC;QACR,GACA;YACE2C,QAAQjE,gCAAa,CAACC,IAAI,CAACC,KAAK,CAAC0B,KAAK,CAACqC,MAAM;YAC7Ce,WAAW5E;QACb;IAEJ;IA9JA,YAAY,AAAiBgE,GAAe,CAAE;aAAjBA,MAAAA;aAFZ7E,SAAS,IAAI0F,cAAM,CAACjG,YAAYS,IAAI;IAEN;AA+JjD"}
|
|
1
|
+
{"version":3,"sources":["../../../../backend/src/authentication/services/auth-manager.service.ts"],"sourcesContent":["/*\n * Copyright (C) 2012-2025 Johan Legrand <johan.legrand@sync-in.com>\n * This file is part of Sync-in | The open source file sync and share solution\n * See the LICENSE file for licensing details\n */\nimport { unsign, UnsignResult } from '@fastify/cookie'\nimport { HttpException, HttpStatus, Injectable, Logger } from '@nestjs/common'\nimport { JwtService } from '@nestjs/jwt'\nimport { FastifyReply, FastifyRequest } from 'fastify'\nimport crypto from 'node:crypto'\nimport { HTTP_CSRF_IGNORED_METHODS } from '../../applications/applications.constants'\nimport { UserModel } from '../../applications/users/models/user.model'\nimport { convertHumanTimeToSeconds } from '../../common/functions'\nimport { currentTimeStamp } from '../../common/shared'\nimport { configuration, serverConfig } from '../../configuration/config.environment'\nimport { CSRF_ERROR, CSRF_KEY, TOKEN_2FA_TYPES, TOKEN_PATHS, TOKEN_TYPES } from '../constants/auth'\nimport { LoginResponseDto, LoginVerify2FaDto } from '../dto/login-response.dto'\nimport { TokenResponseDto } from '../dto/token-response.dto'\nimport { JwtIdentity2FaPayload, JwtIdentityPayload, JwtPayload } from '../interfaces/jwt-payload.interface'\nimport { TOKEN_TYPE } from '../interfaces/token.interface'\n\n@Injectable()\nexport class AuthManager {\n private readonly logger = new Logger(AuthManager.name)\n\n constructor(private readonly jwt: JwtService) {}\n\n async getTokens(user: UserModel, refresh = false): Promise<TokenResponseDto> {\n const currentTime = currentTimeStamp()\n if (refresh && user.exp < currentTime) {\n this.logger.error(`${this.getTokens.name} - token refresh has incorrect expiration : *${user.login}*`)\n throw new HttpException('Token has expired', HttpStatus.FORBIDDEN)\n }\n const accessExpiration = convertHumanTimeToSeconds(configuration.auth.token.access.expiration)\n const refreshExpiration = refresh ? user.exp - currentTime : convertHumanTimeToSeconds(configuration.auth.token.refresh.expiration)\n return {\n [TOKEN_TYPE.ACCESS]: await this.jwtSign(user, TOKEN_TYPE.ACCESS, accessExpiration),\n [TOKEN_TYPE.REFRESH]: await this.jwtSign(user, TOKEN_TYPE.REFRESH, refreshExpiration),\n [`${TOKEN_TYPE.ACCESS}_expiration`]: accessExpiration + currentTime,\n [`${TOKEN_TYPE.REFRESH}_expiration`]: refreshExpiration + currentTime\n }\n }\n\n async setCookies(user: UserModel, res: FastifyReply, init2FaVerify: true): Promise<LoginVerify2FaDto>\n async setCookies(user: UserModel, res: FastifyReply, init2FaVerify?: false): Promise<LoginResponseDto>\n async setCookies(user: UserModel, res: FastifyReply, init2FaVerify = false): Promise<LoginResponseDto | LoginVerify2FaDto> {\n // If `verify2Fa` is true, it sets the cookies and response required for valid 2FA authentication.\n const verify2Fa = init2FaVerify && configuration.auth.mfa.totp.enabled && user.twoFaEnabled\n const response = verify2Fa ? new LoginVerify2FaDto(serverConfig) : new LoginResponseDto(user, serverConfig)\n const currentTime = currentTimeStamp()\n const csrfToken: string = crypto.randomUUID()\n const tokenTypes: TOKEN_TYPE[] = verify2Fa ? TOKEN_2FA_TYPES : TOKEN_TYPES\n for (const type of tokenTypes) {\n const isCSRFToken = type === TOKEN_TYPE.CSRF || type === TOKEN_TYPE.CSRF_2FA\n const tokenExpiration = convertHumanTimeToSeconds(configuration.auth.token[type].expiration)\n let cookieValue: string\n if (isCSRFToken) {\n cookieValue = csrfToken\n } else if (verify2Fa) {\n cookieValue = await this.jwtSign2Fa(user, type, tokenExpiration, csrfToken)\n } else {\n cookieValue = await this.jwtSign(user, type, tokenExpiration, csrfToken)\n }\n res.setCookie(configuration.auth.token[type].name, cookieValue, {\n signed: isCSRFToken,\n path: TOKEN_PATHS[type],\n maxAge: tokenExpiration,\n httpOnly: !isCSRFToken\n })\n if (type === TOKEN_TYPE.ACCESS || type === TOKEN_TYPE.REFRESH || type === TOKEN_TYPE.ACCESS_2FA) {\n response.token[`${type}_expiration`] = tokenExpiration + currentTime\n }\n }\n return response\n }\n\n async refreshCookies(user: UserModel, res: FastifyReply): Promise<TokenResponseDto> {\n const response = {} as TokenResponseDto\n const currentTime = currentTimeStamp()\n let refreshTokenExpiration: number\n // refresh cookie must have the `exp` attribute\n // reuse token expiration to make it final\n if (user.exp && user.exp > currentTime) {\n refreshTokenExpiration = user.exp - currentTime\n } else {\n this.logger.error(`${this.refreshCookies.name} - token ${TOKEN_TYPE.REFRESH} has incorrect expiration : *${user.login}*`)\n throw new HttpException('Token has expired', HttpStatus.FORBIDDEN)\n }\n const csrfToken: string = crypto.randomUUID()\n for (const type of TOKEN_TYPES) {\n const tokenExpiration =\n type === TOKEN_TYPE.ACCESS ? convertHumanTimeToSeconds(configuration.auth.token[TOKEN_TYPE.ACCESS].expiration) : refreshTokenExpiration\n const cookieValue: string = type === TOKEN_TYPE.CSRF ? csrfToken : await this.jwtSign(user, type, tokenExpiration, csrfToken)\n res.setCookie(configuration.auth.token[type].name, cookieValue, {\n signed: type === TOKEN_TYPE.CSRF,\n path: TOKEN_PATHS[type],\n maxAge: tokenExpiration,\n httpOnly: type !== TOKEN_TYPE.CSRF\n })\n if (type === TOKEN_TYPE.ACCESS || type === TOKEN_TYPE.REFRESH) {\n response[`${type}_expiration`] = tokenExpiration + currentTime\n }\n }\n return response\n }\n\n async clearCookies(res: FastifyReply) {\n for (const [type, path] of Object.entries(TOKEN_PATHS)) {\n res.clearCookie(configuration.auth.token[type].name, { path: path })\n }\n }\n\n csrfValidation(req: FastifyRequest, jwtPayload: JwtPayload, type: TOKEN_TYPE.ACCESS | TOKEN_TYPE.REFRESH): void {\n // ignore safe methods\n if (HTTP_CSRF_IGNORED_METHODS.has(req.method)) {\n return\n }\n\n // check csrf only for access and refresh cookies\n if (typeof req.cookies !== 'object' || req.cookies[configuration.auth.token[type].name] === undefined) {\n return\n }\n\n if (!jwtPayload.csrf) {\n this.logger.warn(`${this.csrfValidation.name} - ${CSRF_ERROR.MISSING_JWT}`)\n throw new HttpException(CSRF_ERROR.MISSING_JWT, HttpStatus.FORBIDDEN)\n }\n\n if (!req.headers[CSRF_KEY]) {\n this.logger.warn(`${this.csrfValidation.name} - ${CSRF_ERROR.MISSING_HEADERS}`)\n throw new HttpException(CSRF_ERROR.MISSING_HEADERS, HttpStatus.FORBIDDEN)\n }\n\n const csrfHeader: UnsignResult = unsign(req.headers[CSRF_KEY] as string, configuration.auth.token.csrf.secret)\n if (jwtPayload.csrf !== csrfHeader.value) {\n this.logger.warn(`${this.csrfValidation.name} - ${CSRF_ERROR.MISMATCH}`)\n throw new HttpException(CSRF_ERROR.MISMATCH, HttpStatus.FORBIDDEN)\n }\n }\n\n private jwtSign(user: UserModel, type: TOKEN_TYPE, expiration: number, csrfToken?: string): Promise<string> {\n return this.jwt.signAsync(\n {\n identity: {\n id: user.id,\n login: user.login,\n email: user.email,\n fullName: user.fullName,\n language: user.language,\n role: user.role,\n applications: user.applications,\n impersonatedFromId: user.impersonatedFromId || undefined,\n impersonatedClientId: user.impersonatedClientId || undefined,\n clientId: user.clientId || undefined,\n twoFaEnabled: user.twoFaEnabled || undefined\n } satisfies JwtIdentityPayload,\n ...((type === TOKEN_TYPE.ACCESS || type === TOKEN_TYPE.REFRESH) && { csrf: csrfToken })\n },\n {\n secret: configuration.auth.token[type].secret,\n expiresIn: expiration\n }\n )\n }\n\n private jwtSign2Fa(user: UserModel, type: TOKEN_TYPE, expiration: number, csrfToken: string): Promise<string> {\n // Restrict the temporary token to the minimum required information\n return this.jwt.signAsync(\n {\n identity: {\n id: user.id,\n login: user.login,\n language: user.language,\n role: user.role,\n twoFaEnabled: true\n } satisfies JwtIdentity2FaPayload,\n csrf: csrfToken\n },\n {\n secret: configuration.auth.token[type].secret,\n expiresIn: expiration\n }\n )\n }\n}\n"],"names":["AuthManager","getTokens","user","refresh","currentTime","currentTimeStamp","exp","logger","error","name","login","HttpException","HttpStatus","FORBIDDEN","accessExpiration","convertHumanTimeToSeconds","configuration","auth","token","access","expiration","refreshExpiration","TOKEN_TYPE","ACCESS","jwtSign","REFRESH","setCookies","res","init2FaVerify","verify2Fa","mfa","totp","enabled","twoFaEnabled","response","LoginVerify2FaDto","serverConfig","LoginResponseDto","csrfToken","crypto","randomUUID","tokenTypes","TOKEN_2FA_TYPES","TOKEN_TYPES","type","isCSRFToken","CSRF","CSRF_2FA","tokenExpiration","cookieValue","jwtSign2Fa","setCookie","signed","path","TOKEN_PATHS","maxAge","httpOnly","ACCESS_2FA","refreshCookies","refreshTokenExpiration","clearCookies","Object","entries","clearCookie","csrfValidation","req","jwtPayload","HTTP_CSRF_IGNORED_METHODS","has","method","cookies","undefined","csrf","warn","CSRF_ERROR","MISSING_JWT","headers","CSRF_KEY","MISSING_HEADERS","csrfHeader","unsign","secret","value","MISMATCH","jwt","signAsync","identity","id","email","fullName","language","role","applications","impersonatedFromId","impersonatedClientId","clientId","expiresIn","Logger"],"mappings":"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"}
|
|
@@ -116,7 +116,7 @@ let AuthMethod2FA = class AuthMethod2FA {
|
|
|
116
116
|
async loadUser(userId, ip) {
|
|
117
117
|
const user = await this.usersManager.fromUserId(userId);
|
|
118
118
|
if (!user) {
|
|
119
|
-
this.logger.warn(`User
|
|
119
|
+
this.logger.warn(`User ${userId} (${ip}) not found`);
|
|
120
120
|
throw new _common.HttpException(`User not found`, _common.HttpStatus.NOT_FOUND);
|
|
121
121
|
}
|
|
122
122
|
this.usersManager.validateUserAccess(user, ip);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../backend/src/authentication/services/auth-methods/auth-method-two-fa.service.ts"],"sourcesContent":["/*\n * Copyright (C) 2012-2025 Johan Legrand <johan.legrand@sync-in.com>\n * This file is part of Sync-in | The open source file sync and share solution\n * See the LICENSE file for licensing details\n */\n\nimport { HttpException, HttpStatus, Injectable, Logger } from '@nestjs/common'\nimport { Totp } from 'time2fa'\nimport { NOTIFICATION_APP, NOTIFICATION_APP_EVENT } from '../../../applications/notifications/constants/notifications'\nimport { NotificationContent } from '../../../applications/notifications/interfaces/notification-properties.interface'\nimport { NotificationsManager } from '../../../applications/notifications/services/notifications-manager.service'\nimport { UserModel } from '../../../applications/users/models/user.model'\nimport { UsersManager } from '../../../applications/users/services/users-manager.service'\nimport { ACTION } from '../../../common/constants'\nimport { generateShortUUID } from '../../../common/functions'\nimport { qrcodeToDataURL } from '../../../common/qrcode'\nimport { configuration } from '../../../configuration/config.environment'\nimport { Cache } from '../../../infrastructure/cache/services/cache.service'\nimport { TWO_FA_CODE_LENGTH } from '../../constants/auth'\nimport { TwoFaVerifyDto, TwoFaVerifyWithPasswordDto } from '../../dto/two-fa-verify.dto'\nimport { FastifyAuthenticatedRequest } from '../../interfaces/auth-request.interface'\nimport { TwoFaEnableResult, TwoFaSetup, TwoFaVerifyResult } from '../../interfaces/two-fa-setup.interface'\nimport { decryptSecret, encryptSecret } from '../../utils/crypt-secret'\n\n@Injectable()\nexport class AuthMethod2FA {\n private readonly logger = new Logger(AuthMethod2FA.name)\n private readonly cacheKeyPrefix = 'auth-2fa-pending-user-'\n\n constructor(\n private readonly cache: Cache,\n private readonly usersManager: UsersManager,\n private readonly notificationsManager: NotificationsManager\n ) {}\n\n async initTwoFactor(user: UserModel): Promise<TwoFaSetup> {\n const { secret, qrDataUrl } = this.generateSecretAndQr(user.email)\n // store encrypted secret in cache for 5 minutes\n await this.cache.set(this.getCacheKey(user.id), this.encryptSecret(secret), 300)\n return { secret, qrDataUrl }\n }\n\n async enableTwoFactor(body: TwoFaVerifyWithPasswordDto, req: FastifyAuthenticatedRequest): Promise<TwoFaEnableResult> {\n // retrieve encrypted secret from cache\n const secret: string = await this.cache.get(this.getCacheKey(req.user.id))\n if (!secret) {\n throw new HttpException('The secret has expired', HttpStatus.BAD_REQUEST)\n }\n // load user\n const [auth, user] = await this.verify(body, req, true, secret)\n if (!auth.success) {\n throw new HttpException(auth.message, HttpStatus.FORBIDDEN)\n }\n // verify user password\n await this.verifyUserPassword(user, body.password, req.ip)\n // generate recovery codes\n const recoveryCodes = this.generateRecoveryCodes()\n // store and enable TwoFA & recovery codes\n await this.usersManager.updateSecrets(user.id, {\n twoFaSecret: secret,\n recoveryCodes: recoveryCodes.map((code) => this.encryptSecret(code))\n })\n this.sendEmailNotification(req, ACTION.ADD)\n return { ...auth, recoveryCodes: recoveryCodes }\n }\n\n async disableTwoFactor(body: TwoFaVerifyWithPasswordDto, req: FastifyAuthenticatedRequest): Promise<TwoFaVerifyResult> {\n // load user\n const [auth, user] = await this.verify(body, req, true)\n if (!auth.success) {\n throw new HttpException(auth.message, HttpStatus.FORBIDDEN)\n }\n // verify user password\n await this.verifyUserPassword(user, body.password, req.ip)\n // store and disable TwoFA & recovery codes\n await this.usersManager.updateSecrets(user.id, { twoFaSecret: undefined, recoveryCodes: undefined })\n this.sendEmailNotification(req, ACTION.DELETE)\n return auth\n }\n\n async verify(verifyDto: TwoFaVerifyDto, req: FastifyAuthenticatedRequest, fromLogin?: false, secret?: string): Promise<TwoFaVerifyResult>\n async verify(verifyDto: TwoFaVerifyDto, req: FastifyAuthenticatedRequest, fromLogin: true, secret?: string): Promise<[TwoFaVerifyResult, UserModel]>\n async verify(\n verifyDto: TwoFaVerifyDto,\n req: FastifyAuthenticatedRequest,\n fromLogin = false,\n secret?: string\n ): Promise<TwoFaVerifyResult | [TwoFaVerifyResult, UserModel]> {\n const user = await this.loadUser(req.user.id, req.ip)\n secret = secret || user.secrets.twoFaSecret\n const auth = verifyDto.isRecoveryCode\n ? await this.validateRecoveryCode(req.user.id, verifyDto.code, user.secrets.recoveryCodes)\n : this.validateTwoFactorCode(verifyDto.code, secret)\n this.usersManager.updateAccesses(user, req.ip, auth.success, true).catch((e: Error) => this.logger.error(`${this.verify.name} - ${e}`))\n return fromLogin ? [auth, user] : auth\n }\n\n async adminResetUserTwoFa(userId: number) {\n const auth: TwoFaVerifyResult = { success: false, message: '' }\n try {\n await this.usersManager.updateSecrets(userId, { twoFaSecret: undefined, recoveryCodes: undefined })\n auth.success = true\n } catch (e) {\n auth.success = false\n auth.message = e.message\n this.logger.error(`${this.adminResetUserTwoFa.name} - ${e}`)\n }\n return auth\n }\n\n async loadUser(userId: number, ip: string) {\n const user: UserModel = await this.usersManager.fromUserId(userId)\n if (!user) {\n this.logger.warn(`User *${user.login}* (${user.id}) not found`)\n throw new HttpException(`User not found`, HttpStatus.NOT_FOUND)\n }\n this.usersManager.validateUserAccess(user, ip)\n return user\n }\n\n async verifyUserPassword(user: UserModel, password: string, ip: string) {\n // This function works with any authentication method, provided that\n // the authentication service implements proper user password updates in the database.\n if (!(await this.usersManager.compareUserPassword(user.id, password))) {\n this.usersManager.updateAccesses(user, ip, false, true).catch((e: Error) => this.logger.error(`${this.enableTwoFactor.name} - ${e}`))\n throw new HttpException('Incorrect code or password', HttpStatus.BAD_REQUEST)\n }\n }\n\n validateTwoFactorCode(code: string, encryptedSecret: string): TwoFaVerifyResult {\n const auth: TwoFaVerifyResult = { success: false, message: '' }\n if (!encryptedSecret) {\n auth.message = 'Incorrect code or password'\n return auth\n }\n try {\n auth.success = Totp.validate({ passcode: code, secret: this.decryptSecret(encryptedSecret), drift: 1 })\n if (!auth.success) auth.message = 'Incorrect code or password'\n } catch (e) {\n this.logger.error(`${this.validateTwoFactorCode.name} - ${e}`)\n auth.message = e.message\n }\n return auth\n }\n\n private async validateRecoveryCode(userId: number, code: string, encryptedCodes: string[]): Promise<TwoFaVerifyResult> {\n const auth: TwoFaVerifyResult = { success: false, message: '' }\n if (!encryptedCodes || encryptedCodes.length === 0) {\n auth.message = 'Invalid code'\n } else {\n try {\n for (const encCode of encryptedCodes) {\n if (code === this.decryptSecret(encCode)) {\n auth.success = true\n // removed used code\n encryptedCodes.splice(encryptedCodes.indexOf(encCode), 1)\n break\n }\n }\n if (auth.success) {\n // update recovery codes\n await this.usersManager.updateSecrets(userId, { recoveryCodes: encryptedCodes })\n } else {\n auth.message = 'Invalid code'\n }\n } catch (e) {\n this.logger.error(`${this.validateRecoveryCode.name} - ${e}`)\n auth.message = e.message\n }\n }\n return auth\n }\n\n private generateSecretAndQr(userEmail: string): TwoFaSetup {\n // Generate secret + otpauth URL + QR (DataURL)\n // Totp.generateKey returns { issuer, user, config, secret, url }\n const key = Totp.generateKey({ issuer: configuration.auth.mfa.totp.issuer, user: userEmail }, { digits: TWO_FA_CODE_LENGTH })\n const qrDataUrl = qrcodeToDataURL(key.url)\n return { secret: key.secret, qrDataUrl: qrDataUrl }\n }\n\n private getCacheKey(userId: number): string {\n return `${this.cacheKeyPrefix}${userId}`\n }\n\n private encryptSecret(secret: string): string {\n if (configuration.auth.encryptionKey) {\n return encryptSecret(secret, configuration.auth.encryptionKey)\n }\n return secret\n }\n\n private decryptSecret(secret: string): string {\n if (configuration.auth.encryptionKey) {\n return decryptSecret(secret, configuration.auth.encryptionKey)\n }\n return secret\n }\n\n private generateRecoveryCodes(count = 5): string[] {\n return Array.from({ length: count }, () => generateShortUUID())\n }\n\n private sendEmailNotification(req: FastifyAuthenticatedRequest, action: ACTION) {\n const notification: NotificationContent = {\n app: NOTIFICATION_APP.AUTH_2FA,\n event: NOTIFICATION_APP_EVENT.AUTH_2FA[action],\n element: req.headers['user-agent'],\n url: req.ip\n }\n this.notificationsManager\n .sendEmailNotification([req.user], notification)\n .catch((e: Error) => this.logger.error(`${this.sendEmailNotification.name} - ${e}`))\n }\n}\n"],"names":["AuthMethod2FA","initTwoFactor","user","secret","qrDataUrl","generateSecretAndQr","email","cache","set","getCacheKey","id","encryptSecret","enableTwoFactor","body","req","get","HttpException","HttpStatus","BAD_REQUEST","auth","verify","success","message","FORBIDDEN","verifyUserPassword","password","ip","recoveryCodes","generateRecoveryCodes","usersManager","updateSecrets","twoFaSecret","map","code","sendEmailNotification","ACTION","ADD","disableTwoFactor","undefined","DELETE","verifyDto","fromLogin","loadUser","secrets","isRecoveryCode","validateRecoveryCode","validateTwoFactorCode","updateAccesses","catch","e","logger","error","name","adminResetUserTwoFa","userId","fromUserId","warn","login","NOT_FOUND","validateUserAccess","compareUserPassword","encryptedSecret","Totp","validate","passcode","decryptSecret","drift","encryptedCodes","length","encCode","splice","indexOf","userEmail","key","generateKey","issuer","configuration","mfa","totp","digits","TWO_FA_CODE_LENGTH","qrcodeToDataURL","url","cacheKeyPrefix","encryptionKey","count","Array","from","generateShortUUID","action","notification","app","NOTIFICATION_APP","AUTH_2FA","event","NOTIFICATION_APP_EVENT","element","headers","notificationsManager","Logger"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../backend/src/authentication/services/auth-methods/auth-method-two-fa.service.ts"],"sourcesContent":["/*\n * Copyright (C) 2012-2025 Johan Legrand <johan.legrand@sync-in.com>\n * This file is part of Sync-in | The open source file sync and share solution\n * See the LICENSE file for licensing details\n */\n\nimport { HttpException, HttpStatus, Injectable, Logger } from '@nestjs/common'\nimport { Totp } from 'time2fa'\nimport { NOTIFICATION_APP, NOTIFICATION_APP_EVENT } from '../../../applications/notifications/constants/notifications'\nimport { NotificationContent } from '../../../applications/notifications/interfaces/notification-properties.interface'\nimport { NotificationsManager } from '../../../applications/notifications/services/notifications-manager.service'\nimport { UserModel } from '../../../applications/users/models/user.model'\nimport { UsersManager } from '../../../applications/users/services/users-manager.service'\nimport { ACTION } from '../../../common/constants'\nimport { generateShortUUID } from '../../../common/functions'\nimport { qrcodeToDataURL } from '../../../common/qrcode'\nimport { configuration } from '../../../configuration/config.environment'\nimport { Cache } from '../../../infrastructure/cache/services/cache.service'\nimport { TWO_FA_CODE_LENGTH } from '../../constants/auth'\nimport { TwoFaVerifyDto, TwoFaVerifyWithPasswordDto } from '../../dto/two-fa-verify.dto'\nimport { FastifyAuthenticatedRequest } from '../../interfaces/auth-request.interface'\nimport { TwoFaEnableResult, TwoFaSetup, TwoFaVerifyResult } from '../../interfaces/two-fa-setup.interface'\nimport { decryptSecret, encryptSecret } from '../../utils/crypt-secret'\n\n@Injectable()\nexport class AuthMethod2FA {\n private readonly logger = new Logger(AuthMethod2FA.name)\n private readonly cacheKeyPrefix = 'auth-2fa-pending-user-'\n\n constructor(\n private readonly cache: Cache,\n private readonly usersManager: UsersManager,\n private readonly notificationsManager: NotificationsManager\n ) {}\n\n async initTwoFactor(user: UserModel): Promise<TwoFaSetup> {\n const { secret, qrDataUrl } = this.generateSecretAndQr(user.email)\n // store encrypted secret in cache for 5 minutes\n await this.cache.set(this.getCacheKey(user.id), this.encryptSecret(secret), 300)\n return { secret, qrDataUrl }\n }\n\n async enableTwoFactor(body: TwoFaVerifyWithPasswordDto, req: FastifyAuthenticatedRequest): Promise<TwoFaEnableResult> {\n // retrieve encrypted secret from cache\n const secret: string = await this.cache.get(this.getCacheKey(req.user.id))\n if (!secret) {\n throw new HttpException('The secret has expired', HttpStatus.BAD_REQUEST)\n }\n // load user\n const [auth, user] = await this.verify(body, req, true, secret)\n if (!auth.success) {\n throw new HttpException(auth.message, HttpStatus.FORBIDDEN)\n }\n // verify user password\n await this.verifyUserPassword(user, body.password, req.ip)\n // generate recovery codes\n const recoveryCodes = this.generateRecoveryCodes()\n // store and enable TwoFA & recovery codes\n await this.usersManager.updateSecrets(user.id, {\n twoFaSecret: secret,\n recoveryCodes: recoveryCodes.map((code) => this.encryptSecret(code))\n })\n this.sendEmailNotification(req, ACTION.ADD)\n return { ...auth, recoveryCodes: recoveryCodes }\n }\n\n async disableTwoFactor(body: TwoFaVerifyWithPasswordDto, req: FastifyAuthenticatedRequest): Promise<TwoFaVerifyResult> {\n // load user\n const [auth, user] = await this.verify(body, req, true)\n if (!auth.success) {\n throw new HttpException(auth.message, HttpStatus.FORBIDDEN)\n }\n // verify user password\n await this.verifyUserPassword(user, body.password, req.ip)\n // store and disable TwoFA & recovery codes\n await this.usersManager.updateSecrets(user.id, { twoFaSecret: undefined, recoveryCodes: undefined })\n this.sendEmailNotification(req, ACTION.DELETE)\n return auth\n }\n\n async verify(verifyDto: TwoFaVerifyDto, req: FastifyAuthenticatedRequest, fromLogin?: false, secret?: string): Promise<TwoFaVerifyResult>\n async verify(verifyDto: TwoFaVerifyDto, req: FastifyAuthenticatedRequest, fromLogin: true, secret?: string): Promise<[TwoFaVerifyResult, UserModel]>\n async verify(\n verifyDto: TwoFaVerifyDto,\n req: FastifyAuthenticatedRequest,\n fromLogin = false,\n secret?: string\n ): Promise<TwoFaVerifyResult | [TwoFaVerifyResult, UserModel]> {\n const user = await this.loadUser(req.user.id, req.ip)\n secret = secret || user.secrets.twoFaSecret\n const auth = verifyDto.isRecoveryCode\n ? await this.validateRecoveryCode(req.user.id, verifyDto.code, user.secrets.recoveryCodes)\n : this.validateTwoFactorCode(verifyDto.code, secret)\n this.usersManager.updateAccesses(user, req.ip, auth.success, true).catch((e: Error) => this.logger.error(`${this.verify.name} - ${e}`))\n return fromLogin ? [auth, user] : auth\n }\n\n async adminResetUserTwoFa(userId: number) {\n const auth: TwoFaVerifyResult = { success: false, message: '' }\n try {\n await this.usersManager.updateSecrets(userId, { twoFaSecret: undefined, recoveryCodes: undefined })\n auth.success = true\n } catch (e) {\n auth.success = false\n auth.message = e.message\n this.logger.error(`${this.adminResetUserTwoFa.name} - ${e}`)\n }\n return auth\n }\n\n async loadUser(userId: number, ip: string) {\n const user: UserModel = await this.usersManager.fromUserId(userId)\n if (!user) {\n this.logger.warn(`User ${userId} (${ip}) not found`)\n throw new HttpException(`User not found`, HttpStatus.NOT_FOUND)\n }\n this.usersManager.validateUserAccess(user, ip)\n return user\n }\n\n async verifyUserPassword(user: UserModel, password: string, ip: string) {\n // This function works with any authentication method, provided that\n // the authentication service implements proper user password updates in the database.\n if (!(await this.usersManager.compareUserPassword(user.id, password))) {\n this.usersManager.updateAccesses(user, ip, false, true).catch((e: Error) => this.logger.error(`${this.enableTwoFactor.name} - ${e}`))\n throw new HttpException('Incorrect code or password', HttpStatus.BAD_REQUEST)\n }\n }\n\n validateTwoFactorCode(code: string, encryptedSecret: string): TwoFaVerifyResult {\n const auth: TwoFaVerifyResult = { success: false, message: '' }\n if (!encryptedSecret) {\n auth.message = 'Incorrect code or password'\n return auth\n }\n try {\n auth.success = Totp.validate({ passcode: code, secret: this.decryptSecret(encryptedSecret), drift: 1 })\n if (!auth.success) auth.message = 'Incorrect code or password'\n } catch (e) {\n this.logger.error(`${this.validateTwoFactorCode.name} - ${e}`)\n auth.message = e.message\n }\n return auth\n }\n\n private async validateRecoveryCode(userId: number, code: string, encryptedCodes: string[]): Promise<TwoFaVerifyResult> {\n const auth: TwoFaVerifyResult = { success: false, message: '' }\n if (!encryptedCodes || encryptedCodes.length === 0) {\n auth.message = 'Invalid code'\n } else {\n try {\n for (const encCode of encryptedCodes) {\n if (code === this.decryptSecret(encCode)) {\n auth.success = true\n // removed used code\n encryptedCodes.splice(encryptedCodes.indexOf(encCode), 1)\n break\n }\n }\n if (auth.success) {\n // update recovery codes\n await this.usersManager.updateSecrets(userId, { recoveryCodes: encryptedCodes })\n } else {\n auth.message = 'Invalid code'\n }\n } catch (e) {\n this.logger.error(`${this.validateRecoveryCode.name} - ${e}`)\n auth.message = e.message\n }\n }\n return auth\n }\n\n private generateSecretAndQr(userEmail: string): TwoFaSetup {\n // Generate secret + otpauth URL + QR (DataURL)\n // Totp.generateKey returns { issuer, user, config, secret, url }\n const key = Totp.generateKey({ issuer: configuration.auth.mfa.totp.issuer, user: userEmail }, { digits: TWO_FA_CODE_LENGTH })\n const qrDataUrl = qrcodeToDataURL(key.url)\n return { secret: key.secret, qrDataUrl: qrDataUrl }\n }\n\n private getCacheKey(userId: number): string {\n return `${this.cacheKeyPrefix}${userId}`\n }\n\n private encryptSecret(secret: string): string {\n if (configuration.auth.encryptionKey) {\n return encryptSecret(secret, configuration.auth.encryptionKey)\n }\n return secret\n }\n\n private decryptSecret(secret: string): string {\n if (configuration.auth.encryptionKey) {\n return decryptSecret(secret, configuration.auth.encryptionKey)\n }\n return secret\n }\n\n private generateRecoveryCodes(count = 5): string[] {\n return Array.from({ length: count }, () => generateShortUUID())\n }\n\n private sendEmailNotification(req: FastifyAuthenticatedRequest, action: ACTION) {\n const notification: NotificationContent = {\n app: NOTIFICATION_APP.AUTH_2FA,\n event: NOTIFICATION_APP_EVENT.AUTH_2FA[action],\n element: req.headers['user-agent'],\n url: req.ip\n }\n this.notificationsManager\n .sendEmailNotification([req.user], notification)\n .catch((e: Error) => this.logger.error(`${this.sendEmailNotification.name} - ${e}`))\n }\n}\n"],"names":["AuthMethod2FA","initTwoFactor","user","secret","qrDataUrl","generateSecretAndQr","email","cache","set","getCacheKey","id","encryptSecret","enableTwoFactor","body","req","get","HttpException","HttpStatus","BAD_REQUEST","auth","verify","success","message","FORBIDDEN","verifyUserPassword","password","ip","recoveryCodes","generateRecoveryCodes","usersManager","updateSecrets","twoFaSecret","map","code","sendEmailNotification","ACTION","ADD","disableTwoFactor","undefined","DELETE","verifyDto","fromLogin","loadUser","secrets","isRecoveryCode","validateRecoveryCode","validateTwoFactorCode","updateAccesses","catch","e","logger","error","name","adminResetUserTwoFa","userId","fromUserId","warn","NOT_FOUND","validateUserAccess","compareUserPassword","encryptedSecret","Totp","validate","passcode","decryptSecret","drift","encryptedCodes","length","encCode","splice","indexOf","userEmail","key","generateKey","issuer","configuration","mfa","totp","digits","TWO_FA_CODE_LENGTH","qrcodeToDataURL","url","cacheKeyPrefix","encryptionKey","count","Array","from","generateShortUUID","action","notification","app","NOTIFICATION_APP","AUTH_2FA","event","NOTIFICATION_APP_EVENT","element","headers","notificationsManager","Logger"],"mappings":"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"}
|