@sync-in/server 1.3.8 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +28 -0
- package/README.md +3 -2
- package/environment/environment.dist.yaml +2 -0
- package/migrations/0001_square_mauler.sql +4 -0
- package/migrations/meta/0001_snapshot.json +2417 -0
- package/migrations/meta/_journal.json +7 -0
- package/package.json +20 -2
- package/server/app.bootstrap.js +9 -0
- package/server/app.bootstrap.js.map +1 -1
- package/server/app.service.spec.js +48 -23
- package/server/app.service.spec.js.map +1 -1
- package/server/applications/files/constants/files.js +0 -23
- package/server/applications/files/constants/files.js.map +1 -1
- package/server/applications/files/constants/only-office.js +8 -0
- package/server/applications/files/constants/only-office.js.map +1 -1
- package/server/applications/files/files.config.js +5 -0
- package/server/applications/files/files.config.js.map +1 -1
- package/server/applications/files/guards/files-only-office.strategy.js +0 -1
- package/server/applications/files/guards/files-only-office.strategy.js.map +1 -1
- package/server/applications/spaces/guards/space.guard.spec.js +153 -18
- package/server/applications/spaces/guards/space.guard.spec.js.map +1 -1
- package/server/applications/spaces/services/spaces-browser.service.js +7 -7
- package/server/applications/spaces/services/spaces-browser.service.js.map +1 -1
- package/server/applications/spaces/services/spaces-manager.service.js +17 -17
- package/server/applications/spaces/services/spaces-manager.service.js.map +1 -1
- package/server/applications/sync/schemas/sync-clients.schema.js +4 -4
- package/server/applications/sync/schemas/sync-clients.schema.js.map +1 -1
- package/server/applications/sync/utils/routes.js +1 -1
- package/server/applications/sync/utils/routes.js.map +1 -1
- package/server/applications/users/guards/permissions.guard.js +4 -4
- package/server/applications/users/guards/permissions.guard.js.map +1 -1
- package/server/applications/users/guards/permissions.guard.spec.js +6 -6
- package/server/applications/users/guards/permissions.guard.spec.js.map +1 -1
- package/server/applications/users/guards/roles.guard.js +1 -1
- package/server/applications/users/guards/roles.guard.js.map +1 -1
- package/server/applications/users/models/user.model.js +1 -1
- package/server/applications/users/models/user.model.js.map +1 -1
- package/server/applications/users/schemas/users.schema.js +4 -4
- package/server/applications/users/schemas/users.schema.js.map +1 -1
- package/server/authentication/guards/auth-basic.guard.spec.js +38 -2
- package/server/authentication/guards/auth-basic.guard.spec.js.map +1 -1
- package/server/authentication/guards/auth-basic.strategy.js +0 -1
- package/server/authentication/guards/auth-basic.strategy.js.map +1 -1
- package/server/authentication/guards/auth-local.guard.spec.js +7 -5
- package/server/authentication/guards/auth-local.guard.spec.js.map +1 -1
- package/server/authentication/guards/auth-local.strategy.js +0 -1
- package/server/authentication/guards/auth-local.strategy.js.map +1 -1
- package/server/authentication/guards/auth-token-access.guard.spec.js +30 -0
- package/server/authentication/guards/auth-token-access.guard.spec.js.map +1 -1
- package/server/authentication/guards/auth-token-access.strategy.js +0 -1
- package/server/authentication/guards/auth-token-access.strategy.js.map +1 -1
- package/server/authentication/guards/auth-token-refresh.strategy.js +0 -1
- package/server/authentication/guards/auth-token-refresh.strategy.js.map +1 -1
- package/server/authentication/services/auth-methods/auth-method-database.service.js +1 -1
- package/server/authentication/services/auth-methods/auth-method-database.service.js.map +1 -1
- package/server/authentication/services/auth-methods/auth-method-database.service.spec.js +8 -6
- package/server/authentication/services/auth-methods/auth-method-database.service.spec.js.map +1 -1
- package/server/authentication/services/auth-methods/auth-method-ldap.service.js +2 -2
- package/server/authentication/services/auth-methods/auth-method-ldap.service.js.map +1 -1
- package/server/authentication/services/auth-methods/auth-method-ldap.service.spec.js +500 -5
- package/server/authentication/services/auth-methods/auth-method-ldap.service.spec.js.map +1 -1
- package/server/configuration/config.constants.js +0 -4
- package/server/configuration/config.constants.js.map +1 -1
- package/server/configuration/config.loader.js +2 -5
- package/server/configuration/config.loader.js.map +1 -1
- package/server/infrastructure/context/services/context-manager.service.spec.js +98 -0
- package/server/infrastructure/context/services/context-manager.service.spec.js.map +1 -0
- package/server/infrastructure/database/configuration.js +1 -1
- package/server/infrastructure/database/configuration.js.map +1 -1
- package/server/infrastructure/database/constants.js +18 -5
- package/server/infrastructure/database/constants.js.map +1 -1
- package/server/infrastructure/database/scripts/seed/usersgroups.js +3 -3
- package/server/infrastructure/database/scripts/seed/usersgroups.js.map +1 -1
- package/server/infrastructure/mailer/mailer.service.js +20 -19
- package/server/infrastructure/mailer/mailer.service.js.map +1 -1
- package/server/infrastructure/mailer/mailer.service.spec.js +176 -0
- package/server/infrastructure/mailer/mailer.service.spec.js.map +1 -0
- package/static/{chunk-EVYNK2XG.js → chunk-3GC2BQZD.js} +1 -1
- package/static/{chunk-XN4TXQKE.js → chunk-5YKWZT33.js} +1 -1
- package/static/{chunk-VSBD6246.js → chunk-6F55D74O.js} +1 -1
- package/static/{chunk-MWPC5XQW.js → chunk-B2Y2RNFP.js} +1 -1
- package/static/{chunk-YTROXGRM.js → chunk-C23BPTJZ.js} +1 -1
- package/static/chunk-DGVNNICG.js +1 -0
- package/static/{chunk-WSVE6EWB.js → chunk-FQ4AFNGE.js} +1 -1
- package/static/{chunk-4YWOE6HM.js → chunk-GBCYYDCI.js} +1 -1
- package/static/{chunk-Z6S7RFGN.js → chunk-HZA7R43P.js} +1 -1
- package/static/{chunk-VO5W2VKC.js → chunk-KZQCFEPT.js} +1 -1
- package/static/{chunk-BLRDUWOI.js → chunk-LJIGRUEF.js} +1 -1
- package/static/chunk-MOVWEZ7J.js +1 -0
- package/static/{chunk-3UFSZ24R.js → chunk-MTRNPGS4.js} +1 -1
- package/static/{chunk-JM5PB2NQ.js → chunk-N2LYWNTC.js} +1 -1
- package/static/{chunk-L4F2K2VQ.js → chunk-PF4K7MVG.js} +1 -1
- package/static/{chunk-F2KKCIDO.js → chunk-PY3BGNJN.js} +1 -1
- package/static/chunk-RSNLYAN6.js +560 -0
- package/static/{chunk-VCXIQZT4.js → chunk-SH5EVL4E.js} +4 -4
- package/static/{chunk-BEJ62KA7.js → chunk-T55FAU2O.js} +1 -1
- package/static/{chunk-BEC2WQVF.js → chunk-TCFKH6K6.js} +1 -1
- package/static/{chunk-LATG3ZVP.js → chunk-TDQAEVZN.js} +1 -1
- package/static/{chunk-FNO4KAYG.js → chunk-TNW2CGK6.js} +1 -1
- package/static/{chunk-OWYD7F64.js → chunk-TTQ37MUV.js} +1 -1
- package/static/chunk-TXPODW5Q.js +7 -0
- package/static/chunk-VHYIXL7R.js +4 -0
- package/static/{chunk-UEN7RH33.js → chunk-VMQMD36Z.js} +1 -1
- package/static/{chunk-6E5YHOWA.js → chunk-VMUOUCEI.js} +1 -1
- package/static/{chunk-I6ACCGIM.js → chunk-W3QXNDI5.js} +1 -1
- package/static/chunk-WHMS3PJ3.js +24 -0
- package/static/{chunk-3PB73RWL.js → chunk-WWIC7UW3.js} +1 -1
- package/static/{chunk-6A5R4OPV.js → chunk-X43VWRFP.js} +1 -1
- package/static/chunk-XE5YHU5J.js +1 -0
- package/static/chunk-Y2CDUS4J.js +4 -0
- package/static/chunk-YCINY2YI.js +1 -0
- package/static/index.html +2 -2
- package/static/main-7LDKYVXO.js +10 -0
- package/static/{polyfills-GNKGQMRJ.js → polyfills-IA5XWQYN.js} +1 -1
- package/static/{scripts-263WP5VB.js → scripts-VZVAP2P4.js} +4 -4
- package/static/styles-FYUSO6OJ.css +1 -0
- package/sync-in-server.js +1 -1
- package/static/chunk-CIGVDQPA.js +0 -1
- package/static/chunk-CY3GGCEX.js +0 -7
- package/static/chunk-DJRILQTU.js +0 -1
- package/static/chunk-EDLO4Y5E.js +0 -4
- package/static/chunk-ENDRPUCI.js +0 -4
- package/static/chunk-HXRRMHEX.js +0 -1
- package/static/chunk-IJDFKCZM.js +0 -24
- package/static/chunk-J6YSVODR.js +0 -1
- package/static/chunk-JQOMI5VI.js +0 -560
- package/static/main-7LVJ2JSP.js +0 -10
- package/static/styles-3DONJ2Z4.css +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../backend/src/authentication/guards/auth-token-access.guard.spec.ts"],"sourcesContent":["/*\n * Copyright (C) 2012-2025 Johan Legrand <johan.legrand@sync-in.com>\n * This file is part of Sync-in | The open source file sync and share solution\n * See the LICENSE file for licensing details\n */\n\nimport { sign } from '@fastify/cookie'\nimport { createMock, DeepMocked } from '@golevelup/ts-jest'\nimport { ExecutionContext } from '@nestjs/common'\nimport { ConfigModule, ConfigService } from '@nestjs/config'\nimport { Reflector } from '@nestjs/core'\nimport { JwtModule, JwtService } from '@nestjs/jwt'\nimport { PassportModule } from '@nestjs/passport'\nimport { Test, TestingModule } from '@nestjs/testing'\nimport { PinoLogger } from 'nestjs-pino'\nimport crypto from 'node:crypto'\nimport { UsersManager } from '../../applications/users/services/users-manager.service'\nimport { WEB_DAV_CONTEXT, WebDAVContext } from '../../applications/webdav/decorators/webdav-context.decorator'\nimport { exportConfiguration } from '../../configuration/config.environment'\nimport { AuthConfig } from '../auth.config'\nimport { CSRF_ERROR } from '../constants/auth'\nimport { AuthTokenOptional } from '../decorators/auth-token-optional.decorator'\nimport { AUTH_TOKEN_SKIP, AuthTokenSkip } from '../decorators/auth-token-skip.decorator'\nimport { JwtPayload } from '../interfaces/jwt-payload.interface'\nimport { TOKEN_TYPE } from '../interfaces/token.interface'\nimport { AuthManager } from '../services/auth-manager.service'\nimport { AuthAnonymousGuard } from './auth-anonymous.guard'\nimport { AuthAnonymousStrategy } from './auth-anonymous.strategy'\nimport { AuthTokenAccessGuard } from './auth-token-access.guard'\nimport { AuthTokenAccessStrategy } from './auth-token-access.strategy'\n\ndescribe(AuthTokenAccessGuard.name, () => {\n const csrfToken: string = crypto.randomUUID()\n let authConfig: AuthConfig\n let jwtService: JwtService\n let authAccessGuard: AuthTokenAccessGuard\n let authAnonymousGuard: AuthAnonymousGuard\n let authAnonymousStrategy: AuthAnonymousStrategy\n let reflector: Reflector\n let accessTokenWithoutCSRF: string\n let accessToken: string\n let context: DeepMocked<ExecutionContext>\n\n beforeAll(async () => {\n const module: TestingModule = await Test.createTestingModule({\n imports: [\n await ConfigModule.forRoot({\n load: [exportConfiguration],\n isGlobal: true\n }),\n JwtModule.register({ global: true }),\n PassportModule\n ],\n providers: [\n AuthTokenAccessStrategy,\n AuthAnonymousStrategy,\n AuthAnonymousGuard,\n AuthManager,\n { provide: UsersManager, useValue: {} },\n {\n provide: PinoLogger,\n useValue: {\n assign: () => undefined\n }\n }\n ]\n }).compile()\n\n authConfig = module.get<ConfigService>(ConfigService).get<AuthConfig>('auth')\n jwtService = module.get<JwtService>(JwtService)\n reflector = new Reflector()\n authAccessGuard = new AuthTokenAccessGuard(reflector)\n authAnonymousStrategy = module.get<AuthAnonymousStrategy>(AuthAnonymousStrategy)\n authAnonymousGuard = module.get<AuthAnonymousGuard>(AuthAnonymousGuard)\n accessToken = await jwtService.signAsync({ identity: { id: 1, login: 'foo' }, [TOKEN_TYPE.CSRF]: csrfToken } as JwtPayload, {\n secret: authConfig.token.access.secret,\n expiresIn: 30\n })\n accessTokenWithoutCSRF = await jwtService.signAsync({ identity: { id: 1, login: 'foo' } } as JwtPayload, {\n secret: authConfig.token.access.secret,\n expiresIn: 30\n })\n context = createMock<ExecutionContext>()\n })\n\n it('should be defined', () => {\n expect(authConfig).toBeDefined()\n expect(jwtService).toBeDefined()\n expect(authAccessGuard).toBeDefined()\n expect(authAnonymousGuard).toBeDefined()\n expect(authAnonymousStrategy).toBeDefined()\n expect(accessToken).toBeDefined()\n expect(accessTokenWithoutCSRF).toBeDefined()\n expect(csrfToken).toBeDefined()\n })\n\n it('should pass with a valid access token in cookies with CSRF', async () => {\n context.switchToHttp().getRequest.mockReturnValue({\n raw: { user: '' },\n headers: { [authConfig.token.csrf.name]: sign(csrfToken, authConfig.token.csrf.secret) },\n cookies: {\n [authConfig.token.access.name]: accessToken\n }\n })\n expect(await authAccessGuard.canActivate(context)).toBe(true)\n })\n\n it('should pass with a valid access token in request header with no CSRF', async () => {\n context.switchToHttp().getRequest.mockReturnValue({\n raw: { user: '' },\n headers: {\n authorization: `Bearer ${accessToken}`\n }\n })\n expect(await authAccessGuard.canActivate(context)).toBe(true)\n })\n\n it('should throw an error with an invalid access token in cookies', async () => {\n // Cookies test\n context.switchToHttp().getRequest.mockReturnValue({\n raw: { user: '' },\n headers: {},\n cookies: {\n [authConfig.token.access.name]: 'bar'\n }\n })\n await expect(authAccessGuard.canActivate(context)).rejects.toThrow('Unauthorized')\n })\n\n it('should throw an error with an invalid access token in request header', async () => {\n context.switchToHttp().getRequest.mockReturnValue({\n raw: { user: '' },\n headers: {\n authorization: `Bearer bar`\n }\n })\n await expect(authAccessGuard.canActivate(context)).rejects.toThrow('Unauthorized')\n })\n\n it('should throw an error with a valid access token in cookies and a missing CSRF in request header', async () => {\n context.switchToHttp().getRequest.mockReturnValue({\n raw: { user: '' },\n headers: {},\n cookies: {\n [authConfig.token.access.name]: accessToken\n }\n })\n await expect(authAccessGuard.canActivate(context)).rejects.toThrow(new RegExp(CSRF_ERROR.MISSING_HEADERS))\n })\n\n it('should throw an error with a valid access token in cookies and a missing CSRF claim in the access token', async () => {\n context.switchToHttp().getRequest.mockReturnValue({\n raw: { user: '' },\n headers: { [authConfig.token.csrf.name]: csrfToken },\n cookies: {\n [authConfig.token.access.name]: accessTokenWithoutCSRF\n }\n })\n await expect(authAccessGuard.canActivate(context)).rejects.toThrow(new RegExp(CSRF_ERROR.MISSING_JWT))\n })\n\n it('should throw an error with a valid access token in cookies and a mismatch CSRF', async () => {\n context.switchToHttp().getRequest.mockReturnValue({\n raw: { user: '' },\n headers: { [authConfig.token.csrf.name]: csrfToken + '*' },\n cookies: {\n [authConfig.token.access.name]: accessToken\n }\n })\n await expect(authAccessGuard.canActivate(context)).rejects.toThrow(new RegExp(CSRF_ERROR.MISMATCH))\n })\n\n it('should bypass access token when AuthTokenSkip decorator is applied to context', () => {\n context = createMock<ExecutionContext>()\n AuthTokenSkip()(context.getHandler())\n expect(reflector.getAllAndOverride<boolean>(AUTH_TOKEN_SKIP, [context.getHandler(), context.getClass()])).toBe(true)\n context.switchToHttp().getRequest.mockReturnValue({\n raw: { user: '' },\n headers: {\n authorization: `Bearer bar`\n }\n })\n expect(authAccessGuard.canActivate(context)).toBe(true)\n })\n\n it('should bypass access token with WebDAVContext decorator is applied to context', () => {\n context = createMock<ExecutionContext>()\n WebDAVContext()(context.getHandler())\n expect(reflector.getAllAndOverride<boolean>(AUTH_TOKEN_SKIP, [context.getHandler(), context.getClass()])).toBe(undefined)\n expect(reflector.getAllAndOverride<boolean>(WEB_DAV_CONTEXT, [context.getHandler(), context.getClass()])).toBe(true)\n context.switchToHttp().getRequest.mockReturnValue({\n raw: { user: '' },\n headers: {\n authorization: `Bearer bar`\n }\n })\n expect(authAccessGuard.canActivate(context)).toBe(true)\n })\n\n it('should pass without a valid access token when AuthTokenOptional is applied to context', async () => {\n const spyAuthenticate = jest.spyOn(authAnonymousStrategy, 'authenticate')\n context = createMock<ExecutionContext>()\n AuthTokenOptional()(context.getHandler())\n expect(reflector.getAllAndOverride<boolean>(AUTH_TOKEN_SKIP, [context.getHandler(), context.getClass()])).toBe(true)\n context.switchToHttp().getRequest.mockReturnValue({\n raw: { user: '' },\n authorization: `Bearer bar`\n })\n expect(authAccessGuard.canActivate(context)).toBe(true)\n expect(await authAnonymousGuard.canActivate(context)).toBe(true)\n expect(spyAuthenticate).toHaveBeenCalledTimes(1)\n spyAuthenticate.mockClear()\n })\n})\n"],"names":["describe","AuthTokenAccessGuard","name","csrfToken","crypto","randomUUID","authConfig","jwtService","authAccessGuard","authAnonymousGuard","authAnonymousStrategy","reflector","accessTokenWithoutCSRF","accessToken","context","beforeAll","module","Test","createTestingModule","imports","ConfigModule","forRoot","load","exportConfiguration","isGlobal","JwtModule","register","global","PassportModule","providers","AuthTokenAccessStrategy","AuthAnonymousStrategy","AuthAnonymousGuard","AuthManager","provide","UsersManager","useValue","PinoLogger","assign","undefined","compile","get","ConfigService","JwtService","Reflector","signAsync","identity","id","login","TOKEN_TYPE","CSRF","secret","token","access","expiresIn","createMock","it","expect","toBeDefined","switchToHttp","getRequest","mockReturnValue","raw","user","headers","csrf","sign","cookies","canActivate","toBe","authorization","rejects","toThrow","RegExp","CSRF_ERROR","MISSING_HEADERS","MISSING_JWT","MISMATCH","AuthTokenSkip","getHandler","getAllAndOverride","AUTH_TOKEN_SKIP","getClass","WebDAVContext","WEB_DAV_CONTEXT","spyAuthenticate","jest","spyOn","AuthTokenOptional","toHaveBeenCalledTimes","mockClear"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../backend/src/authentication/guards/auth-token-access.guard.spec.ts"],"sourcesContent":["/*\n * Copyright (C) 2012-2025 Johan Legrand <johan.legrand@sync-in.com>\n * This file is part of Sync-in | The open source file sync and share solution\n * See the LICENSE file for licensing details\n */\n\nimport { sign } from '@fastify/cookie'\nimport { createMock, DeepMocked } from '@golevelup/ts-jest'\nimport { ExecutionContext } from '@nestjs/common'\nimport { ConfigModule, ConfigService } from '@nestjs/config'\nimport { Reflector } from '@nestjs/core'\nimport { JwtModule, JwtService } from '@nestjs/jwt'\nimport { PassportModule } from '@nestjs/passport'\nimport { Test, TestingModule } from '@nestjs/testing'\nimport { PinoLogger } from 'nestjs-pino'\nimport crypto from 'node:crypto'\nimport { UsersManager } from '../../applications/users/services/users-manager.service'\nimport { WEB_DAV_CONTEXT, WebDAVContext } from '../../applications/webdav/decorators/webdav-context.decorator'\nimport { exportConfiguration } from '../../configuration/config.environment'\nimport { AuthConfig } from '../auth.config'\nimport { CSRF_ERROR } from '../constants/auth'\nimport { AuthTokenOptional } from '../decorators/auth-token-optional.decorator'\nimport { AUTH_TOKEN_SKIP, AuthTokenSkip } from '../decorators/auth-token-skip.decorator'\nimport { JwtPayload } from '../interfaces/jwt-payload.interface'\nimport { TOKEN_TYPE } from '../interfaces/token.interface'\nimport { AuthManager } from '../services/auth-manager.service'\nimport { AuthAnonymousGuard } from './auth-anonymous.guard'\nimport { AuthAnonymousStrategy } from './auth-anonymous.strategy'\nimport { AuthTokenAccessGuard } from './auth-token-access.guard'\nimport { AuthTokenAccessStrategy } from './auth-token-access.strategy'\n\ndescribe(AuthTokenAccessGuard.name, () => {\n const csrfToken: string = crypto.randomUUID()\n let authConfig: AuthConfig\n let jwtService: JwtService\n let authAccessGuard: AuthTokenAccessGuard\n let authAnonymousGuard: AuthAnonymousGuard\n let authAnonymousStrategy: AuthAnonymousStrategy\n let reflector: Reflector\n let accessTokenWithoutCSRF: string\n let accessToken: string\n let context: DeepMocked<ExecutionContext>\n\n beforeAll(async () => {\n const module: TestingModule = await Test.createTestingModule({\n imports: [\n await ConfigModule.forRoot({\n load: [exportConfiguration],\n isGlobal: true\n }),\n JwtModule.register({ global: true }),\n PassportModule\n ],\n providers: [\n AuthTokenAccessStrategy,\n AuthAnonymousStrategy,\n AuthAnonymousGuard,\n AuthManager,\n { provide: UsersManager, useValue: {} },\n {\n provide: PinoLogger,\n useValue: {\n assign: () => undefined\n }\n }\n ]\n }).compile()\n\n authConfig = module.get<ConfigService>(ConfigService).get<AuthConfig>('auth')\n jwtService = module.get<JwtService>(JwtService)\n reflector = new Reflector()\n authAccessGuard = new AuthTokenAccessGuard(reflector)\n authAnonymousStrategy = module.get<AuthAnonymousStrategy>(AuthAnonymousStrategy)\n authAnonymousGuard = module.get<AuthAnonymousGuard>(AuthAnonymousGuard)\n accessToken = await jwtService.signAsync({ identity: { id: 1, login: 'foo' }, [TOKEN_TYPE.CSRF]: csrfToken } as JwtPayload, {\n secret: authConfig.token.access.secret,\n expiresIn: 30\n })\n accessTokenWithoutCSRF = await jwtService.signAsync({ identity: { id: 1, login: 'foo' } } as JwtPayload, {\n secret: authConfig.token.access.secret,\n expiresIn: 30\n })\n context = createMock<ExecutionContext>()\n })\n\n it('should be defined', () => {\n expect(authConfig).toBeDefined()\n expect(jwtService).toBeDefined()\n expect(authAccessGuard).toBeDefined()\n expect(authAnonymousGuard).toBeDefined()\n expect(authAnonymousStrategy).toBeDefined()\n expect(accessToken).toBeDefined()\n expect(accessTokenWithoutCSRF).toBeDefined()\n expect(csrfToken).toBeDefined()\n })\n\n it('should pass with a valid access token in cookies with CSRF', async () => {\n context.switchToHttp().getRequest.mockReturnValue({\n raw: { user: '' },\n headers: { [authConfig.token.csrf.name]: sign(csrfToken, authConfig.token.csrf.secret) },\n cookies: {\n [authConfig.token.access.name]: accessToken\n }\n })\n expect(await authAccessGuard.canActivate(context)).toBe(true)\n })\n\n it('should pass with a valid access token in request header with no CSRF', async () => {\n context.switchToHttp().getRequest.mockReturnValue({\n raw: { user: '' },\n headers: {\n authorization: `Bearer ${accessToken}`\n }\n })\n expect(await authAccessGuard.canActivate(context)).toBe(true)\n })\n\n it('should throw an error with an invalid access token in cookies', async () => {\n // Cookies test\n context.switchToHttp().getRequest.mockReturnValue({\n raw: { user: '' },\n headers: {},\n cookies: {\n [authConfig.token.access.name]: 'bar'\n }\n })\n await expect(authAccessGuard.canActivate(context)).rejects.toThrow('Unauthorized')\n })\n\n it('should throw an error with an invalid access token in request header', async () => {\n context.switchToHttp().getRequest.mockReturnValue({\n raw: { user: '' },\n headers: {\n authorization: `Bearer bar`\n }\n })\n await expect(authAccessGuard.canActivate(context)).rejects.toThrow('Unauthorized')\n })\n\n it('should throw an error with a valid access token in cookies and a missing CSRF in request header', async () => {\n context.switchToHttp().getRequest.mockReturnValue({\n raw: { user: '' },\n headers: {},\n cookies: {\n [authConfig.token.access.name]: accessToken\n }\n })\n await expect(authAccessGuard.canActivate(context)).rejects.toThrow(new RegExp(CSRF_ERROR.MISSING_HEADERS))\n })\n\n it('should throw an error with a valid access token in cookies and a missing CSRF claim in the access token', async () => {\n context.switchToHttp().getRequest.mockReturnValue({\n raw: { user: '' },\n headers: { [authConfig.token.csrf.name]: csrfToken },\n cookies: {\n [authConfig.token.access.name]: accessTokenWithoutCSRF\n }\n })\n await expect(authAccessGuard.canActivate(context)).rejects.toThrow(new RegExp(CSRF_ERROR.MISSING_JWT))\n })\n\n it('should throw an error with a valid access token in cookies and a mismatch CSRF', async () => {\n context.switchToHttp().getRequest.mockReturnValue({\n raw: { user: '' },\n headers: { [authConfig.token.csrf.name]: csrfToken + '*' },\n cookies: {\n [authConfig.token.access.name]: accessToken\n }\n })\n await expect(authAccessGuard.canActivate(context)).rejects.toThrow(new RegExp(CSRF_ERROR.MISMATCH))\n })\n\n it('should pass with method GET and a valid access token in cookies and a mismatch CSRF', async () => {\n context.switchToHttp().getRequest.mockReturnValue({\n method: 'GET',\n raw: { user: '' },\n headers: { [authConfig.token.csrf.name]: csrfToken + '*' },\n cookies: {\n [authConfig.token.access.name]: accessToken\n }\n })\n await expect(authAccessGuard.canActivate(context)).resolves.not.toThrow()\n })\n\n it('should throw an error with method POST and a valid access token in cookies and a mismatch CSRF', async () => {\n context.switchToHttp().getRequest.mockReturnValue({\n method: 'POST',\n raw: { user: '' },\n headers: { [authConfig.token.csrf.name]: csrfToken + '*' },\n cookies: {\n [authConfig.token.access.name]: accessToken\n }\n })\n await expect(authAccessGuard.canActivate(context)).rejects.toThrow(new RegExp(CSRF_ERROR.MISMATCH))\n })\n\n it('should bypass access token when AuthTokenSkip decorator is applied to context', () => {\n context = createMock<ExecutionContext>()\n AuthTokenSkip()(context.getHandler())\n expect(reflector.getAllAndOverride<boolean>(AUTH_TOKEN_SKIP, [context.getHandler(), context.getClass()])).toBe(true)\n context.switchToHttp().getRequest.mockReturnValue({\n raw: { user: '' },\n headers: {\n authorization: `Bearer bar`\n }\n })\n expect(authAccessGuard.canActivate(context)).toBe(true)\n })\n\n it('should bypass access token with WebDAVContext decorator is applied to context', () => {\n context = createMock<ExecutionContext>()\n WebDAVContext()(context.getHandler())\n expect(reflector.getAllAndOverride<boolean>(AUTH_TOKEN_SKIP, [context.getHandler(), context.getClass()])).toBe(undefined)\n expect(reflector.getAllAndOverride<boolean>(WEB_DAV_CONTEXT, [context.getHandler(), context.getClass()])).toBe(true)\n context.switchToHttp().getRequest.mockReturnValue({\n raw: { user: '' },\n headers: {\n authorization: `Bearer bar`\n }\n })\n expect(authAccessGuard.canActivate(context)).toBe(true)\n })\n\n it('should pass without a valid access token when AuthTokenOptional is applied to context', async () => {\n const spyAuthenticate = jest.spyOn(authAnonymousStrategy, 'authenticate')\n context = createMock<ExecutionContext>()\n AuthTokenOptional()(context.getHandler())\n expect(reflector.getAllAndOverride<boolean>(AUTH_TOKEN_SKIP, [context.getHandler(), context.getClass()])).toBe(true)\n context.switchToHttp().getRequest.mockReturnValue({\n raw: { user: '' },\n authorization: `Bearer bar`\n })\n expect(authAccessGuard.canActivate(context)).toBe(true)\n expect(await authAnonymousGuard.canActivate(context)).toBe(true)\n expect(spyAuthenticate).toHaveBeenCalledTimes(1)\n spyAuthenticate.mockClear()\n })\n})\n"],"names":["describe","AuthTokenAccessGuard","name","csrfToken","crypto","randomUUID","authConfig","jwtService","authAccessGuard","authAnonymousGuard","authAnonymousStrategy","reflector","accessTokenWithoutCSRF","accessToken","context","beforeAll","module","Test","createTestingModule","imports","ConfigModule","forRoot","load","exportConfiguration","isGlobal","JwtModule","register","global","PassportModule","providers","AuthTokenAccessStrategy","AuthAnonymousStrategy","AuthAnonymousGuard","AuthManager","provide","UsersManager","useValue","PinoLogger","assign","undefined","compile","get","ConfigService","JwtService","Reflector","signAsync","identity","id","login","TOKEN_TYPE","CSRF","secret","token","access","expiresIn","createMock","it","expect","toBeDefined","switchToHttp","getRequest","mockReturnValue","raw","user","headers","csrf","sign","cookies","canActivate","toBe","authorization","rejects","toThrow","RegExp","CSRF_ERROR","MISSING_HEADERS","MISSING_JWT","MISMATCH","method","resolves","not","AuthTokenSkip","getHandler","getAllAndOverride","AUTH_TOKEN_SKIP","getClass","WebDAVContext","WEB_DAV_CONTEXT","spyAuthenticate","jest","spyOn","AuthTokenOptional","toHaveBeenCalledTimes","mockClear"],"mappings":"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"}
|
|
@@ -29,7 +29,6 @@ function _ts_metadata(k, v) {
|
|
|
29
29
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
30
30
|
}
|
|
31
31
|
let AuthTokenAccessStrategy = class AuthTokenAccessStrategy extends (0, _passport.PassportStrategy)(_passportjwt.Strategy, 'tokenAccess') {
|
|
32
|
-
// not declared properly: https://github.com/nestjs/passport/issues/929
|
|
33
32
|
validate(req, jwtPayload) {
|
|
34
33
|
this.logger.assign({
|
|
35
34
|
user: jwtPayload.identity.login
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../backend/src/authentication/guards/auth-token-access.strategy.ts"],"sourcesContent":["/*\n * Copyright (C) 2012-2025 Johan Legrand <johan.legrand@sync-in.com>\n * This file is part of Sync-in | The open source file sync and share solution\n * See the LICENSE file for licensing details\n */\n\nimport { Injectable } from '@nestjs/common'\nimport { PassportStrategy } from '@nestjs/passport'\nimport { FastifyRequest } from 'fastify'\nimport { PinoLogger } from 'nestjs-pino'\nimport { ExtractJwt, Strategy } from 'passport-jwt'\nimport { UserModel } from '../../applications/users/models/user.model'\nimport { JwtPayload } from '../interfaces/jwt-payload.interface'\nimport { TOKEN_TYPE } from '../interfaces/token.interface'\nimport { AuthManager } from '../services/auth-manager.service'\n\n@Injectable()\nexport class AuthTokenAccessStrategy extends PassportStrategy(Strategy, 'tokenAccess') {\n private static accessCookieName: string\n\n constructor(\n private readonly authManager: AuthManager,\n private readonly logger: PinoLogger\n ) {\n super({\n jwtFromRequest: ExtractJwt.fromExtractors([AuthTokenAccessStrategy.extractJWTFromCookie, ExtractJwt.fromAuthHeaderAsBearerToken()]),\n secretOrKey: authManager.authConfig.token.access.secret,\n ignoreExpiration: false,\n passReqToCallback: true\n })\n AuthTokenAccessStrategy.accessCookieName = authManager.authConfig.token.access.name\n }\n\n
|
|
1
|
+
{"version":3,"sources":["../../../../backend/src/authentication/guards/auth-token-access.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 { FastifyRequest } from 'fastify'\nimport { PinoLogger } from 'nestjs-pino'\nimport { ExtractJwt, Strategy } from 'passport-jwt'\nimport { UserModel } from '../../applications/users/models/user.model'\nimport { JwtPayload } from '../interfaces/jwt-payload.interface'\nimport { TOKEN_TYPE } from '../interfaces/token.interface'\nimport { AuthManager } from '../services/auth-manager.service'\n\n@Injectable()\nexport class AuthTokenAccessStrategy extends PassportStrategy(Strategy, 'tokenAccess') implements AbstractStrategy {\n private static accessCookieName: string\n\n constructor(\n private readonly authManager: AuthManager,\n private readonly logger: PinoLogger\n ) {\n super({\n jwtFromRequest: ExtractJwt.fromExtractors([AuthTokenAccessStrategy.extractJWTFromCookie, ExtractJwt.fromAuthHeaderAsBearerToken()]),\n secretOrKey: authManager.authConfig.token.access.secret,\n ignoreExpiration: false,\n passReqToCallback: true\n })\n AuthTokenAccessStrategy.accessCookieName = authManager.authConfig.token.access.name\n }\n\n validate(req: FastifyRequest, jwtPayload: JwtPayload): UserModel {\n this.logger.assign({ user: jwtPayload.identity.login })\n this.authManager.csrfValidation(req, jwtPayload, TOKEN_TYPE.ACCESS)\n return new UserModel(jwtPayload.identity)\n }\n\n static extractJWTFromCookie(req: FastifyRequest): string | null {\n if (typeof req.cookies === 'object' && req.cookies[AuthTokenAccessStrategy.accessCookieName] !== undefined) {\n return req.cookies[AuthTokenAccessStrategy.accessCookieName]\n }\n return null\n }\n}\n"],"names":["AuthTokenAccessStrategy","PassportStrategy","Strategy","validate","req","jwtPayload","logger","assign","user","identity","login","authManager","csrfValidation","TOKEN_TYPE","ACCESS","UserModel","extractJWTFromCookie","cookies","accessCookieName","undefined","jwtFromRequest","ExtractJwt","fromExtractors","fromAuthHeaderAsBearerToken","secretOrKey","authConfig","token","access","secret","ignoreExpiration","passReqToCallback","name"],"mappings":"AAAA;;;;CAIC;;;;+BAaYA;;;eAAAA;;;wBAXc;0BACwB;4BAExB;6BACU;2BACX;gCAEC;oCACC;;;;;;;;;;AAGrB,IAAA,AAAMA,0BAAN,MAAMA,gCAAgCC,IAAAA,0BAAgB,EAACC,qBAAQ,EAAE;IAgBtEC,SAASC,GAAmB,EAAEC,UAAsB,EAAa;QAC/D,IAAI,CAACC,MAAM,CAACC,MAAM,CAAC;YAAEC,MAAMH,WAAWI,QAAQ,CAACC,KAAK;QAAC;QACrD,IAAI,CAACC,WAAW,CAACC,cAAc,CAACR,KAAKC,YAAYQ,0BAAU,CAACC,MAAM;QAClE,OAAO,IAAIC,oBAAS,CAACV,WAAWI,QAAQ;IAC1C;IAEA,OAAOO,qBAAqBZ,GAAmB,EAAiB;QAC9D,IAAI,OAAOA,IAAIa,OAAO,KAAK,YAAYb,IAAIa,OAAO,CAACjB,wBAAwBkB,gBAAgB,CAAC,KAAKC,WAAW;YAC1G,OAAOf,IAAIa,OAAO,CAACjB,wBAAwBkB,gBAAgB,CAAC;QAC9D;QACA,OAAO;IACT;IAxBA,YACE,AAAiBP,WAAwB,EACzC,AAAiBL,MAAkB,CACnC;QACA,KAAK,CAAC;YACJc,gBAAgBC,uBAAU,CAACC,cAAc,CAAC;gBAACtB,wBAAwBgB,oBAAoB;gBAAEK,uBAAU,CAACE,2BAA2B;aAAG;YAClIC,aAAab,YAAYc,UAAU,CAACC,KAAK,CAACC,MAAM,CAACC,MAAM;YACvDC,kBAAkB;YAClBC,mBAAmB;QACrB,SARiBnB,cAAAA,kBACAL,SAAAA;QAQjBN,wBAAwBkB,gBAAgB,GAAGP,YAAYc,UAAU,CAACC,KAAK,CAACC,MAAM,CAACI,IAAI;IACrF;AAcF"}
|
|
@@ -29,7 +29,6 @@ function _ts_metadata(k, v) {
|
|
|
29
29
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
30
30
|
}
|
|
31
31
|
let AuthTokenRefreshStrategy = class AuthTokenRefreshStrategy extends (0, _passport.PassportStrategy)(_passportjwt.Strategy, 'tokenRefresh') {
|
|
32
|
-
// not declared properly: https://github.com/nestjs/passport/issues/929
|
|
33
32
|
validate(req, jwtPayload) {
|
|
34
33
|
this.logger.assign({
|
|
35
34
|
user: jwtPayload.identity.login
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../backend/src/authentication/guards/auth-token-refresh.strategy.ts"],"sourcesContent":["/*\n * Copyright (C) 2012-2025 Johan Legrand <johan.legrand@sync-in.com>\n * This file is part of Sync-in | The open source file sync and share solution\n * See the LICENSE file for licensing details\n */\n\nimport { Injectable } from '@nestjs/common'\nimport { PassportStrategy } from '@nestjs/passport'\nimport { FastifyRequest } from 'fastify'\nimport { PinoLogger } from 'nestjs-pino'\nimport { ExtractJwt, Strategy } from 'passport-jwt'\nimport { UserModel } from '../../applications/users/models/user.model'\nimport { JwtPayload } from '../interfaces/jwt-payload.interface'\nimport { TOKEN_TYPE } from '../interfaces/token.interface'\nimport { AuthManager } from '../services/auth-manager.service'\n\n@Injectable()\nexport class AuthTokenRefreshStrategy extends PassportStrategy(Strategy, 'tokenRefresh') {\n private static refreshCookieName: string\n\n constructor(\n private readonly authManager: AuthManager,\n private readonly logger: PinoLogger\n ) {\n super({\n jwtFromRequest: ExtractJwt.fromExtractors([AuthTokenRefreshStrategy.extractJWTFromCookie, ExtractJwt.fromAuthHeaderAsBearerToken()]),\n secretOrKey: authManager.authConfig.token.refresh.secret,\n ignoreExpiration: false,\n passReqToCallback: true\n })\n AuthTokenRefreshStrategy.refreshCookieName = authManager.authConfig.token.refresh.name\n }\n\n
|
|
1
|
+
{"version":3,"sources":["../../../../backend/src/authentication/guards/auth-token-refresh.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 { FastifyRequest } from 'fastify'\nimport { PinoLogger } from 'nestjs-pino'\nimport { ExtractJwt, Strategy } from 'passport-jwt'\nimport { UserModel } from '../../applications/users/models/user.model'\nimport { JwtPayload } from '../interfaces/jwt-payload.interface'\nimport { TOKEN_TYPE } from '../interfaces/token.interface'\nimport { AuthManager } from '../services/auth-manager.service'\n\n@Injectable()\nexport class AuthTokenRefreshStrategy extends PassportStrategy(Strategy, 'tokenRefresh') implements AbstractStrategy {\n private static refreshCookieName: string\n\n constructor(\n private readonly authManager: AuthManager,\n private readonly logger: PinoLogger\n ) {\n super({\n jwtFromRequest: ExtractJwt.fromExtractors([AuthTokenRefreshStrategy.extractJWTFromCookie, ExtractJwt.fromAuthHeaderAsBearerToken()]),\n secretOrKey: authManager.authConfig.token.refresh.secret,\n ignoreExpiration: false,\n passReqToCallback: true\n })\n AuthTokenRefreshStrategy.refreshCookieName = authManager.authConfig.token.refresh.name\n }\n\n validate(req: FastifyRequest, jwtPayload: JwtPayload): UserModel {\n this.logger.assign({ user: jwtPayload.identity.login })\n this.authManager.csrfValidation(req, jwtPayload, TOKEN_TYPE.REFRESH)\n // jwt expiration is used later to refresh cookies\n return new UserModel({ ...jwtPayload.identity, exp: jwtPayload.exp })\n }\n\n private static extractJWTFromCookie(req: FastifyRequest): string | null {\n if (typeof req.cookies === 'object' && req.cookies[AuthTokenRefreshStrategy.refreshCookieName] !== undefined) {\n return req.cookies[AuthTokenRefreshStrategy.refreshCookieName]\n }\n return null\n }\n}\n"],"names":["AuthTokenRefreshStrategy","PassportStrategy","Strategy","validate","req","jwtPayload","logger","assign","user","identity","login","authManager","csrfValidation","TOKEN_TYPE","REFRESH","UserModel","exp","extractJWTFromCookie","cookies","refreshCookieName","undefined","jwtFromRequest","ExtractJwt","fromExtractors","fromAuthHeaderAsBearerToken","secretOrKey","authConfig","token","refresh","secret","ignoreExpiration","passReqToCallback","name"],"mappings":"AAAA;;;;CAIC;;;;+BAaYA;;;eAAAA;;;wBAXc;0BACwB;4BAExB;6BACU;2BACX;gCAEC;oCACC;;;;;;;;;;AAGrB,IAAA,AAAMA,2BAAN,MAAMA,iCAAiCC,IAAAA,0BAAgB,EAACC,qBAAQ,EAAE;IAgBvEC,SAASC,GAAmB,EAAEC,UAAsB,EAAa;QAC/D,IAAI,CAACC,MAAM,CAACC,MAAM,CAAC;YAAEC,MAAMH,WAAWI,QAAQ,CAACC,KAAK;QAAC;QACrD,IAAI,CAACC,WAAW,CAACC,cAAc,CAACR,KAAKC,YAAYQ,0BAAU,CAACC,OAAO;QACnE,kDAAkD;QAClD,OAAO,IAAIC,oBAAS,CAAC;YAAE,GAAGV,WAAWI,QAAQ;YAAEO,KAAKX,WAAWW,GAAG;QAAC;IACrE;IAEA,OAAeC,qBAAqBb,GAAmB,EAAiB;QACtE,IAAI,OAAOA,IAAIc,OAAO,KAAK,YAAYd,IAAIc,OAAO,CAAClB,yBAAyBmB,iBAAiB,CAAC,KAAKC,WAAW;YAC5G,OAAOhB,IAAIc,OAAO,CAAClB,yBAAyBmB,iBAAiB,CAAC;QAChE;QACA,OAAO;IACT;IAzBA,YACE,AAAiBR,WAAwB,EACzC,AAAiBL,MAAkB,CACnC;QACA,KAAK,CAAC;YACJe,gBAAgBC,uBAAU,CAACC,cAAc,CAAC;gBAACvB,yBAAyBiB,oBAAoB;gBAAEK,uBAAU,CAACE,2BAA2B;aAAG;YACnIC,aAAad,YAAYe,UAAU,CAACC,KAAK,CAACC,OAAO,CAACC,MAAM;YACxDC,kBAAkB;YAClBC,mBAAmB;QACrB,SARiBpB,cAAAA,kBACAL,SAAAA;QAQjBN,yBAAyBmB,iBAAiB,GAAGR,YAAYe,UAAU,CAACC,KAAK,CAACC,OAAO,CAACI,IAAI;IACxF;AAeF"}
|
|
@@ -31,7 +31,7 @@ let AuthMethodDatabase = class AuthMethodDatabase {
|
|
|
31
31
|
user = await this.usersManager.findUser(loginOrEmail, false);
|
|
32
32
|
} catch (e) {
|
|
33
33
|
this.logger.error(`${this.validateUser.name} - ${e}`);
|
|
34
|
-
throw new _common.HttpException(_appconstants.CONNECT_ERROR_CODE.has(e.cause?.code) ? 'Authentication service
|
|
34
|
+
throw new _common.HttpException(_appconstants.CONNECT_ERROR_CODE.has(e.cause?.code) ? 'Authentication service error' : e.message, _common.HttpStatus.INTERNAL_SERVER_ERROR);
|
|
35
35
|
}
|
|
36
36
|
if (!user) {
|
|
37
37
|
this.logger.warn(`${this.validateUser.name} - login or email not found for *${loginOrEmail}*`);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../backend/src/authentication/services/auth-methods/auth-method-database.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 { CONNECT_ERROR_CODE } from '../../../app.constants'\nimport { UserModel } from '../../../applications/users/models/user.model'\nimport { UsersManager } from '../../../applications/users/services/users-manager.service'\nimport { AuthMethod } from '../../models/auth-method'\n\n@Injectable()\nexport class AuthMethodDatabase implements AuthMethod {\n private readonly logger = new Logger(AuthMethodDatabase.name)\n\n constructor(private readonly usersManager: UsersManager) {}\n\n async validateUser(loginOrEmail: string, password: string, ip?: string): Promise<UserModel> {\n let user: UserModel\n try {\n user = await this.usersManager.findUser(loginOrEmail, false)\n } catch (e) {\n this.logger.error(`${this.validateUser.name} - ${e}`)\n throw new HttpException(
|
|
1
|
+
{"version":3,"sources":["../../../../../backend/src/authentication/services/auth-methods/auth-method-database.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 { CONNECT_ERROR_CODE } from '../../../app.constants'\nimport { UserModel } from '../../../applications/users/models/user.model'\nimport { UsersManager } from '../../../applications/users/services/users-manager.service'\nimport { AuthMethod } from '../../models/auth-method'\n\n@Injectable()\nexport class AuthMethodDatabase implements AuthMethod {\n private readonly logger = new Logger(AuthMethodDatabase.name)\n\n constructor(private readonly usersManager: UsersManager) {}\n\n async validateUser(loginOrEmail: string, password: string, ip?: string): Promise<UserModel> {\n let user: UserModel\n try {\n user = await this.usersManager.findUser(loginOrEmail, false)\n } catch (e) {\n this.logger.error(`${this.validateUser.name} - ${e}`)\n throw new HttpException(CONNECT_ERROR_CODE.has(e.cause?.code) ? 'Authentication service error' : e.message, HttpStatus.INTERNAL_SERVER_ERROR)\n }\n if (!user) {\n this.logger.warn(`${this.validateUser.name} - login or email not found for *${loginOrEmail}*`)\n return null\n }\n return await this.usersManager.logUser(user, password, ip)\n }\n}\n"],"names":["AuthMethodDatabase","validateUser","loginOrEmail","password","ip","user","usersManager","findUser","e","logger","error","name","HttpException","CONNECT_ERROR_CODE","has","cause","code","message","HttpStatus","INTERNAL_SERVER_ERROR","warn","logUser","Logger"],"mappings":"AAAA;;;;CAIC;;;;+BASYA;;;eAAAA;;;wBAPiD;8BAC3B;qCAEN;;;;;;;;;;AAItB,IAAA,AAAMA,qBAAN,MAAMA;IAKX,MAAMC,aAAaC,YAAoB,EAAEC,QAAgB,EAAEC,EAAW,EAAsB;QAC1F,IAAIC;QACJ,IAAI;YACFA,OAAO,MAAM,IAAI,CAACC,YAAY,CAACC,QAAQ,CAACL,cAAc;QACxD,EAAE,OAAOM,GAAG;YACV,IAAI,CAACC,MAAM,CAACC,KAAK,CAAC,GAAG,IAAI,CAACT,YAAY,CAACU,IAAI,CAAC,GAAG,EAAEH,GAAG;YACpD,MAAM,IAAII,qBAAa,CAACC,gCAAkB,CAACC,GAAG,CAACN,EAAEO,KAAK,EAAEC,QAAQ,iCAAiCR,EAAES,OAAO,EAAEC,kBAAU,CAACC,qBAAqB;QAC9I;QACA,IAAI,CAACd,MAAM;YACT,IAAI,CAACI,MAAM,CAACW,IAAI,CAAC,GAAG,IAAI,CAACnB,YAAY,CAACU,IAAI,CAAC,iCAAiC,EAAET,aAAa,CAAC,CAAC;YAC7F,OAAO;QACT;QACA,OAAO,MAAM,IAAI,CAACI,YAAY,CAACe,OAAO,CAAChB,MAAMF,UAAUC;IACzD;IAfA,YAAY,AAAiBE,YAA0B,CAAE;aAA5BA,eAAAA;aAFZG,SAAS,IAAIa,cAAM,CAACtB,mBAAmBW,IAAI;IAEF;AAgB5D"}
|
|
@@ -7,6 +7,7 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
7
7
|
value: true
|
|
8
8
|
});
|
|
9
9
|
const _testing = require("@nestjs/testing");
|
|
10
|
+
const _appconstants = require("../../../app.constants");
|
|
10
11
|
const _usermodel = require("../../../applications/users/models/user.model");
|
|
11
12
|
const _adminusersmanagerservice = require("../../../applications/users/services/admin-users-manager.service");
|
|
12
13
|
const _adminusersqueriesservice = require("../../../applications/users/services/admin-users-queries.service");
|
|
@@ -71,17 +72,18 @@ describe(_authmethoddatabaseservice.AuthMethodDatabase.name, ()=>{
|
|
|
71
72
|
usersManager.findUser = jest.fn().mockReturnValueOnce(null).mockReturnValueOnce({
|
|
72
73
|
...userTest,
|
|
73
74
|
password: await (0, _functions.hashPassword)('bar')
|
|
74
|
-
}).mockRejectedValueOnce({
|
|
75
|
-
message: 'db error',
|
|
76
|
-
code: 'ECONNREFUSED'
|
|
77
75
|
}).mockRejectedValueOnce({
|
|
78
76
|
message: 'db error',
|
|
79
77
|
code: 'OTHER'
|
|
80
|
-
})
|
|
78
|
+
}).mockRejectedValueOnce(new Error('Authentication service error', {
|
|
79
|
+
cause: {
|
|
80
|
+
code: Array.from(_appconstants.CONNECT_ERROR_CODE)[0]
|
|
81
|
+
}
|
|
82
|
+
}));
|
|
81
83
|
expect(await authMethodDatabase.validateUser(userTest.login, userTest.password)).toBeNull();
|
|
82
84
|
expect(await authMethodDatabase.validateUser(userTest.login, userTest.password)).toBeNull();
|
|
83
|
-
await expect(authMethodDatabase.validateUser(userTest.login, userTest.password)).rejects.toThrow();
|
|
84
|
-
await expect(authMethodDatabase.validateUser(userTest.login, userTest.password)).rejects.toThrow();
|
|
85
|
+
await expect(authMethodDatabase.validateUser(userTest.login, userTest.password)).rejects.toThrow(/db error/i);
|
|
86
|
+
await expect(authMethodDatabase.validateUser(userTest.login, userTest.password)).rejects.toThrow(/authentication service/i);
|
|
85
87
|
});
|
|
86
88
|
});
|
|
87
89
|
|
package/server/authentication/services/auth-methods/auth-method-database.service.spec.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../backend/src/authentication/services/auth-methods/auth-method-database.service.spec.ts"],"sourcesContent":["/*\n * Copyright (C) 2012-2025 Johan Legrand <johan.legrand@sync-in.com>\n * This file is part of Sync-in | The open source file sync and share solution\n * See the LICENSE file for licensing details\n */\n\nimport { Test, TestingModule } from '@nestjs/testing'\nimport { UserModel } from '../../../applications/users/models/user.model'\nimport { AdminUsersManager } from '../../../applications/users/services/admin-users-manager.service'\nimport { AdminUsersQueries } from '../../../applications/users/services/admin-users-queries.service'\nimport { UsersManager } from '../../../applications/users/services/users-manager.service'\nimport { UsersQueries } from '../../../applications/users/services/users-queries.service'\nimport { generateUserTest } from '../../../applications/users/utils/test'\nimport { hashPassword } from '../../../common/functions'\nimport { Cache } from '../../../infrastructure/cache/services/cache.service'\nimport { DB_TOKEN_PROVIDER } from '../../../infrastructure/database/constants'\nimport { AuthManager } from '../auth-manager.service'\nimport { AuthMethodDatabase } from './auth-method-database.service'\n\ndescribe(AuthMethodDatabase.name, () => {\n let authMethodDatabase: AuthMethodDatabase\n let usersManager: UsersManager\n let userTest: UserModel\n\n beforeAll(async () => {\n const module: TestingModule = await Test.createTestingModule({\n providers: [\n AuthMethodDatabase,\n UsersManager,\n UsersQueries,\n AdminUsersManager,\n AdminUsersQueries,\n { provide: AuthManager, useValue: {} },\n { provide: DB_TOKEN_PROVIDER, useValue: {} },\n {\n provide: Cache,\n useValue: {}\n }\n ]\n }).compile()\n\n authMethodDatabase = module.get<AuthMethodDatabase>(AuthMethodDatabase)\n usersManager = module.get<UsersManager>(UsersManager)\n module.useLogger(['fatal'])\n // mocks\n userTest = new UserModel(generateUserTest(), false)\n usersManager.updateAccesses = jest.fn(() => Promise.resolve())\n })\n\n it('should be defined', () => {\n expect(authMethodDatabase).toBeDefined()\n expect(usersManager).toBeDefined()\n expect(userTest).toBeDefined()\n })\n\n it('should validate the user', async () => {\n userTest.makePaths = jest.fn()\n usersManager.findUser = jest.fn().mockReturnValue({ ...userTest, password: await hashPassword(userTest.password) })\n expect(await authMethodDatabase.validateUser(userTest.login, userTest.password)).toBeDefined()\n expect(userTest.makePaths).toHaveBeenCalled()\n })\n\n it('should not validate the user', async () => {\n usersManager.findUser = jest\n .fn()\n .mockReturnValueOnce(null)\n .mockReturnValueOnce({ ...userTest, password: await hashPassword('bar') })\n .mockRejectedValueOnce({ message: 'db error', code: '
|
|
1
|
+
{"version":3,"sources":["../../../../../backend/src/authentication/services/auth-methods/auth-method-database.service.spec.ts"],"sourcesContent":["/*\n * Copyright (C) 2012-2025 Johan Legrand <johan.legrand@sync-in.com>\n * This file is part of Sync-in | The open source file sync and share solution\n * See the LICENSE file for licensing details\n */\n\nimport { Test, TestingModule } from '@nestjs/testing'\nimport { CONNECT_ERROR_CODE } from '../../../app.constants'\nimport { UserModel } from '../../../applications/users/models/user.model'\nimport { AdminUsersManager } from '../../../applications/users/services/admin-users-manager.service'\nimport { AdminUsersQueries } from '../../../applications/users/services/admin-users-queries.service'\nimport { UsersManager } from '../../../applications/users/services/users-manager.service'\nimport { UsersQueries } from '../../../applications/users/services/users-queries.service'\nimport { generateUserTest } from '../../../applications/users/utils/test'\nimport { hashPassword } from '../../../common/functions'\nimport { Cache } from '../../../infrastructure/cache/services/cache.service'\nimport { DB_TOKEN_PROVIDER } from '../../../infrastructure/database/constants'\nimport { AuthManager } from '../auth-manager.service'\nimport { AuthMethodDatabase } from './auth-method-database.service'\n\ndescribe(AuthMethodDatabase.name, () => {\n let authMethodDatabase: AuthMethodDatabase\n let usersManager: UsersManager\n let userTest: UserModel\n\n beforeAll(async () => {\n const module: TestingModule = await Test.createTestingModule({\n providers: [\n AuthMethodDatabase,\n UsersManager,\n UsersQueries,\n AdminUsersManager,\n AdminUsersQueries,\n { provide: AuthManager, useValue: {} },\n { provide: DB_TOKEN_PROVIDER, useValue: {} },\n {\n provide: Cache,\n useValue: {}\n }\n ]\n }).compile()\n\n authMethodDatabase = module.get<AuthMethodDatabase>(AuthMethodDatabase)\n usersManager = module.get<UsersManager>(UsersManager)\n module.useLogger(['fatal'])\n // mocks\n userTest = new UserModel(generateUserTest(), false)\n usersManager.updateAccesses = jest.fn(() => Promise.resolve())\n })\n\n it('should be defined', () => {\n expect(authMethodDatabase).toBeDefined()\n expect(usersManager).toBeDefined()\n expect(userTest).toBeDefined()\n })\n\n it('should validate the user', async () => {\n userTest.makePaths = jest.fn()\n usersManager.findUser = jest.fn().mockReturnValue({ ...userTest, password: await hashPassword(userTest.password) })\n expect(await authMethodDatabase.validateUser(userTest.login, userTest.password)).toBeDefined()\n expect(userTest.makePaths).toHaveBeenCalled()\n })\n\n it('should not validate the user', async () => {\n usersManager.findUser = jest\n .fn()\n .mockReturnValueOnce(null)\n .mockReturnValueOnce({ ...userTest, password: await hashPassword('bar') })\n .mockRejectedValueOnce({ message: 'db error', code: 'OTHER' })\n .mockRejectedValueOnce(\n new Error('Authentication service error', {\n cause: { code: Array.from(CONNECT_ERROR_CODE)[0] }\n })\n )\n expect(await authMethodDatabase.validateUser(userTest.login, userTest.password)).toBeNull()\n expect(await authMethodDatabase.validateUser(userTest.login, userTest.password)).toBeNull()\n await expect(authMethodDatabase.validateUser(userTest.login, userTest.password)).rejects.toThrow(/db error/i)\n await expect(authMethodDatabase.validateUser(userTest.login, userTest.password)).rejects.toThrow(/authentication service/i)\n })\n})\n"],"names":["describe","AuthMethodDatabase","name","authMethodDatabase","usersManager","userTest","beforeAll","module","Test","createTestingModule","providers","UsersManager","UsersQueries","AdminUsersManager","AdminUsersQueries","provide","AuthManager","useValue","DB_TOKEN_PROVIDER","Cache","compile","get","useLogger","UserModel","generateUserTest","updateAccesses","jest","fn","Promise","resolve","it","expect","toBeDefined","makePaths","findUser","mockReturnValue","password","hashPassword","validateUser","login","toHaveBeenCalled","mockReturnValueOnce","mockRejectedValueOnce","message","code","Error","cause","Array","from","CONNECT_ERROR_CODE","toBeNull","rejects","toThrow"],"mappings":"AAAA;;;;CAIC;;;;yBAEmC;8BACD;2BACT;0CACQ;0CACA;qCACL;qCACA;sBACI;2BACJ;8BACP;2BACY;oCACN;2CACO;AAEnCA,SAASC,6CAAkB,CAACC,IAAI,EAAE;IAChC,IAAIC;IACJ,IAAIC;IACJ,IAAIC;IAEJC,UAAU;QACR,MAAMC,SAAwB,MAAMC,aAAI,CAACC,mBAAmB,CAAC;YAC3DC,WAAW;gBACTT,6CAAkB;gBAClBU,iCAAY;gBACZC,iCAAY;gBACZC,2CAAiB;gBACjBC,2CAAiB;gBACjB;oBAAEC,SAASC,+BAAW;oBAAEC,UAAU,CAAC;gBAAE;gBACrC;oBAAEF,SAASG,4BAAiB;oBAAED,UAAU,CAAC;gBAAE;gBAC3C;oBACEF,SAASI,mBAAK;oBACdF,UAAU,CAAC;gBACb;aACD;QACH,GAAGG,OAAO;QAEVjB,qBAAqBI,OAAOc,GAAG,CAAqBpB,6CAAkB;QACtEG,eAAeG,OAAOc,GAAG,CAAeV,iCAAY;QACpDJ,OAAOe,SAAS,CAAC;YAAC;SAAQ;QAC1B,QAAQ;QACRjB,WAAW,IAAIkB,oBAAS,CAACC,IAAAA,sBAAgB,KAAI;QAC7CpB,aAAaqB,cAAc,GAAGC,KAAKC,EAAE,CAAC,IAAMC,QAAQC,OAAO;IAC7D;IAEAC,GAAG,qBAAqB;QACtBC,OAAO5B,oBAAoB6B,WAAW;QACtCD,OAAO3B,cAAc4B,WAAW;QAChCD,OAAO1B,UAAU2B,WAAW;IAC9B;IAEAF,GAAG,4BAA4B;QAC7BzB,SAAS4B,SAAS,GAAGP,KAAKC,EAAE;QAC5BvB,aAAa8B,QAAQ,GAAGR,KAAKC,EAAE,GAAGQ,eAAe,CAAC;YAAE,GAAG9B,QAAQ;YAAE+B,UAAU,MAAMC,IAAAA,uBAAY,EAAChC,SAAS+B,QAAQ;QAAE;QACjHL,OAAO,MAAM5B,mBAAmBmC,YAAY,CAACjC,SAASkC,KAAK,EAAElC,SAAS+B,QAAQ,GAAGJ,WAAW;QAC5FD,OAAO1B,SAAS4B,SAAS,EAAEO,gBAAgB;IAC7C;IAEAV,GAAG,gCAAgC;QACjC1B,aAAa8B,QAAQ,GAAGR,KACrBC,EAAE,GACFc,mBAAmB,CAAC,MACpBA,mBAAmB,CAAC;YAAE,GAAGpC,QAAQ;YAAE+B,UAAU,MAAMC,IAAAA,uBAAY,EAAC;QAAO,GACvEK,qBAAqB,CAAC;YAAEC,SAAS;YAAYC,MAAM;QAAQ,GAC3DF,qBAAqB,CACpB,IAAIG,MAAM,gCAAgC;YACxCC,OAAO;gBAAEF,MAAMG,MAAMC,IAAI,CAACC,gCAAkB,CAAC,CAAC,EAAE;YAAC;QACnD;QAEJlB,OAAO,MAAM5B,mBAAmBmC,YAAY,CAACjC,SAASkC,KAAK,EAAElC,SAAS+B,QAAQ,GAAGc,QAAQ;QACzFnB,OAAO,MAAM5B,mBAAmBmC,YAAY,CAACjC,SAASkC,KAAK,EAAElC,SAAS+B,QAAQ,GAAGc,QAAQ;QACzF,MAAMnB,OAAO5B,mBAAmBmC,YAAY,CAACjC,SAASkC,KAAK,EAAElC,SAAS+B,QAAQ,GAAGe,OAAO,CAACC,OAAO,CAAC;QACjG,MAAMrB,OAAO5B,mBAAmBmC,YAAY,CAACjC,SAASkC,KAAK,EAAElC,SAAS+B,QAAQ,GAAGe,OAAO,CAACC,OAAO,CAAC;IACnG;AACF"}
|
|
@@ -34,7 +34,7 @@ let AuthMethodLdapService = class AuthMethodLdapService {
|
|
|
34
34
|
let user = await this.usersManager.findUser(loginOrEmail, false);
|
|
35
35
|
if (user) {
|
|
36
36
|
if (user.isGuest) {
|
|
37
|
-
// allow guests to be authenticated from db
|
|
37
|
+
// allow guests to be authenticated from db and check if the current user is defined as active
|
|
38
38
|
return this.usersManager.logUser(user, password, ip);
|
|
39
39
|
}
|
|
40
40
|
if (!user.isActive) {
|
|
@@ -93,7 +93,7 @@ let AuthMethodLdapService = class AuthMethodLdapService {
|
|
|
93
93
|
}
|
|
94
94
|
}
|
|
95
95
|
if (error && _appconstants.CONNECT_ERROR_CODE.has(error.code)) {
|
|
96
|
-
throw new _common.HttpException('Authentication service
|
|
96
|
+
throw new _common.HttpException('Authentication service error', _common.HttpStatus.INTERNAL_SERVER_ERROR);
|
|
97
97
|
}
|
|
98
98
|
return false;
|
|
99
99
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../backend/src/authentication/services/auth-methods/auth-method-ldap.service.ts"],"sourcesContent":["/*\n * Copyright (C) 2012-2025 Johan Legrand <johan.legrand@sync-in.com>\n * This file is part of Sync-in | The open source file sync and share solution\n * See the LICENSE file for licensing details\n */\n\nimport { HttpException, HttpStatus, Injectable, Logger } from '@nestjs/common'\nimport { Client, ClientOptions, Entry, InvalidCredentialsError } from 'ldapts'\nimport { CONNECT_ERROR_CODE } from '../../../app.constants'\nimport { USER_ROLE } from '../../../applications/users/constants/user'\nimport { CreateUserDto, UpdateUserDto } from '../../../applications/users/dto/create-or-update-user.dto'\nimport { UserModel } from '../../../applications/users/models/user.model'\nimport { AdminUsersManager } from '../../../applications/users/services/admin-users-manager.service'\nimport { UsersManager } from '../../../applications/users/services/users-manager.service'\nimport { comparePassword, splitFullName } from '../../../common/functions'\nimport { configuration } from '../../../configuration/config.environment'\nimport { AuthMethod } from '../../models/auth-method'\n\ntype LdapUserEntry = Entry & { uid: string; mail: string; cn: string }\n\n@Injectable()\nexport class AuthMethodLdapService implements AuthMethod {\n private readonly logger = new Logger(AuthMethodLdapService.name)\n private readonly entryAttributes = ['uid', 'mail', 'cn']\n private clientOptions: ClientOptions = { timeout: 6000, connectTimeout: 6000, url: '' }\n\n constructor(\n private readonly usersManager: UsersManager,\n private readonly adminUsersManager: AdminUsersManager\n ) {}\n\n async validateUser(loginOrEmail: string, password: string, ip?: string): Promise<UserModel> {\n let user = await this.usersManager.findUser(loginOrEmail, false)\n if (user) {\n if (user.isGuest) {\n // allow guests to be authenticated from db & check if current user is defined as active\n return this.usersManager.logUser(user, password, ip)\n }\n if (!user.isActive) {\n this.logger.error(`${this.validateUser.name} - user *${user.login}* is locked`)\n throw new HttpException('Account locked', HttpStatus.FORBIDDEN)\n }\n }\n const entry = await this.checkAuth(loginOrEmail, password)\n if (entry === false) {\n if (user) {\n this.usersManager.updateAccesses(user, ip, false).catch((e: Error) => this.logger.error(`${this.validateUser.name} : ${e}`))\n }\n return null\n } else if (!entry.mail || !entry.uid) {\n this.logger.error(`${this.validateUser.name} - ${loginOrEmail} : some ldap fields are missing => (${JSON.stringify(entry)})`)\n return null\n }\n const identity = { login: entry.uid, email: entry.mail, password: password, ...splitFullName(entry.cn) } satisfies CreateUserDto\n user = await this.updateOrCreateUser(identity, user)\n this.usersManager.updateAccesses(user, ip, true).catch((e: Error) => this.logger.error(`${this.validateUser.name} : ${e}`))\n return user\n }\n\n private async checkAuth(uid: string, password: string): Promise<LdapUserEntry | false> {\n const servers = configuration.auth.ldap.servers\n const bindUserDN = `${configuration.auth.ldap.loginAttribute}=${uid},${configuration.auth.ldap.baseDN}`\n let client: Client\n let error: any\n for (const s of servers) {\n client = new Client({ ...this.clientOptions, url: s })\n try {\n await client.bind(bindUserDN, password)\n return await this.checkAccess(client, uid)\n } catch (e) {\n if (e.errors?.length) {\n for (const err of e.errors) {\n this.logger.warn(`${this.checkAuth.name} - ${uid} : ${err}`)\n error = err\n }\n } else {\n error = e\n this.logger.warn(`${this.checkAuth.name} - ${uid} : ${e}`)\n }\n if (error instanceof InvalidCredentialsError) {\n return false\n }\n } finally {\n await client.unbind()\n }\n }\n if (error && CONNECT_ERROR_CODE.has(error.code)) {\n throw new HttpException('Authentication service connection error', HttpStatus.INTERNAL_SERVER_ERROR)\n }\n return false\n }\n\n private async checkAccess(client: Client, uid: string): Promise<LdapUserEntry | false> {\n const searchFilter = `(&(${configuration.auth.ldap.loginAttribute}=${uid})${configuration.auth.ldap.filter || ''})`\n try {\n const { searchEntries } = await client.search(configuration.auth.ldap.baseDN, {\n scope: 'sub',\n filter: searchFilter,\n attributes: this.entryAttributes\n })\n for (const entry of searchEntries) {\n if (entry[configuration.auth.ldap.loginAttribute] === uid) {\n if (Array.isArray(entry.mail)) {\n // handles the case of multiple emails, keep the first\n entry.mail = entry.mail[0]\n }\n return entry as LdapUserEntry\n }\n }\n return false\n } catch (e) {\n this.logger.warn(`${this.checkAccess.name} - ${uid} : ${e}`)\n return false\n }\n }\n\n private async updateOrCreateUser(identity: CreateUserDto, user: UserModel): Promise<UserModel> {\n if (user === null) {\n return this.adminUsersManager.createUserOrGuest(identity, USER_ROLE.USER)\n } else {\n if (identity.login !== user.login) {\n this.logger.error(`${this.updateOrCreateUser.name} - user id mismatch : ${identity.login} !== ${user.login}`)\n throw new HttpException('Account matching error', HttpStatus.FORBIDDEN)\n }\n // check if user information has changed\n const identityHasChanged: UpdateUserDto = Object.fromEntries(\n (\n await Promise.all(\n Object.keys(identity).map(async (key: string) => {\n if (key === 'password') {\n const isSame = await comparePassword(identity[key], user.password)\n return isSame ? null : [key, identity[key]]\n }\n return identity[key] !== user[key] ? [key, identity[key]] : null\n })\n )\n ).filter(Boolean)\n )\n if (Object.keys(identityHasChanged).length > 0) {\n try {\n await this.adminUsersManager.updateUserOrGuest(user.id, identityHasChanged)\n if (identityHasChanged?.password) {\n delete identityHasChanged.password\n }\n Object.assign(user, identityHasChanged)\n } catch (e) {\n this.logger.warn(`${this.updateOrCreateUser.name} - unable to update user *${user.login}* : ${e}`)\n }\n }\n await user.makePaths()\n return user\n }\n }\n}\n"],"names":["AuthMethodLdapService","validateUser","loginOrEmail","password","ip","user","usersManager","findUser","isGuest","logUser","isActive","logger","error","name","login","HttpException","HttpStatus","FORBIDDEN","entry","checkAuth","updateAccesses","catch","e","mail","uid","JSON","stringify","identity","email","splitFullName","cn","updateOrCreateUser","servers","configuration","auth","ldap","bindUserDN","loginAttribute","baseDN","client","s","Client","clientOptions","url","bind","checkAccess","errors","length","err","warn","InvalidCredentialsError","unbind","CONNECT_ERROR_CODE","has","code","INTERNAL_SERVER_ERROR","searchFilter","filter","searchEntries","search","scope","attributes","entryAttributes","Array","isArray","adminUsersManager","createUserOrGuest","USER_ROLE","USER","identityHasChanged","Object","fromEntries","Promise","all","keys","map","key","isSame","comparePassword","Boolean","updateUserOrGuest","id","assign","makePaths","Logger","timeout","connectTimeout"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../backend/src/authentication/services/auth-methods/auth-method-ldap.service.ts"],"sourcesContent":["/*\n * Copyright (C) 2012-2025 Johan Legrand <johan.legrand@sync-in.com>\n * This file is part of Sync-in | The open source file sync and share solution\n * See the LICENSE file for licensing details\n */\n\nimport { HttpException, HttpStatus, Injectable, Logger } from '@nestjs/common'\nimport { Client, ClientOptions, Entry, InvalidCredentialsError } from 'ldapts'\nimport { CONNECT_ERROR_CODE } from '../../../app.constants'\nimport { USER_ROLE } from '../../../applications/users/constants/user'\nimport { CreateUserDto, UpdateUserDto } from '../../../applications/users/dto/create-or-update-user.dto'\nimport { UserModel } from '../../../applications/users/models/user.model'\nimport { AdminUsersManager } from '../../../applications/users/services/admin-users-manager.service'\nimport { UsersManager } from '../../../applications/users/services/users-manager.service'\nimport { comparePassword, splitFullName } from '../../../common/functions'\nimport { configuration } from '../../../configuration/config.environment'\nimport { AuthMethod } from '../../models/auth-method'\n\ntype LdapUserEntry = Entry & { uid: string; mail: string; cn: string }\n\n@Injectable()\nexport class AuthMethodLdapService implements AuthMethod {\n private readonly logger = new Logger(AuthMethodLdapService.name)\n private readonly entryAttributes = ['uid', 'mail', 'cn']\n private clientOptions: ClientOptions = { timeout: 6000, connectTimeout: 6000, url: '' }\n\n constructor(\n private readonly usersManager: UsersManager,\n private readonly adminUsersManager: AdminUsersManager\n ) {}\n\n async validateUser(loginOrEmail: string, password: string, ip?: string): Promise<UserModel> {\n let user = await this.usersManager.findUser(loginOrEmail, false)\n if (user) {\n if (user.isGuest) {\n // allow guests to be authenticated from db and check if the current user is defined as active\n return this.usersManager.logUser(user, password, ip)\n }\n if (!user.isActive) {\n this.logger.error(`${this.validateUser.name} - user *${user.login}* is locked`)\n throw new HttpException('Account locked', HttpStatus.FORBIDDEN)\n }\n }\n const entry: false | LdapUserEntry = await this.checkAuth(loginOrEmail, password)\n if (entry === false) {\n if (user) {\n this.usersManager.updateAccesses(user, ip, false).catch((e: Error) => this.logger.error(`${this.validateUser.name} : ${e}`))\n }\n return null\n } else if (!entry.mail || !entry.uid) {\n this.logger.error(`${this.validateUser.name} - ${loginOrEmail} : some ldap fields are missing => (${JSON.stringify(entry)})`)\n return null\n }\n const identity = { login: entry.uid, email: entry.mail, password: password, ...splitFullName(entry.cn) } satisfies CreateUserDto\n user = await this.updateOrCreateUser(identity, user)\n this.usersManager.updateAccesses(user, ip, true).catch((e: Error) => this.logger.error(`${this.validateUser.name} : ${e}`))\n return user\n }\n\n private async checkAuth(uid: string, password: string): Promise<LdapUserEntry | false> {\n const servers = configuration.auth.ldap.servers\n const bindUserDN = `${configuration.auth.ldap.loginAttribute}=${uid},${configuration.auth.ldap.baseDN}`\n let client: Client\n let error: any\n for (const s of servers) {\n client = new Client({ ...this.clientOptions, url: s })\n try {\n await client.bind(bindUserDN, password)\n return await this.checkAccess(client, uid)\n } catch (e) {\n if (e.errors?.length) {\n for (const err of e.errors) {\n this.logger.warn(`${this.checkAuth.name} - ${uid} : ${err}`)\n error = err\n }\n } else {\n error = e\n this.logger.warn(`${this.checkAuth.name} - ${uid} : ${e}`)\n }\n if (error instanceof InvalidCredentialsError) {\n return false\n }\n } finally {\n await client.unbind()\n }\n }\n if (error && CONNECT_ERROR_CODE.has(error.code)) {\n throw new HttpException('Authentication service error', HttpStatus.INTERNAL_SERVER_ERROR)\n }\n return false\n }\n\n private async checkAccess(client: Client, uid: string): Promise<LdapUserEntry | false> {\n const searchFilter = `(&(${configuration.auth.ldap.loginAttribute}=${uid})${configuration.auth.ldap.filter || ''})`\n try {\n const { searchEntries } = await client.search(configuration.auth.ldap.baseDN, {\n scope: 'sub',\n filter: searchFilter,\n attributes: this.entryAttributes\n })\n for (const entry of searchEntries) {\n if (entry[configuration.auth.ldap.loginAttribute] === uid) {\n if (Array.isArray(entry.mail)) {\n // handles the case of multiple emails, keep the first\n entry.mail = entry.mail[0]\n }\n return entry as LdapUserEntry\n }\n }\n return false\n } catch (e) {\n this.logger.warn(`${this.checkAccess.name} - ${uid} : ${e}`)\n return false\n }\n }\n\n private async updateOrCreateUser(identity: CreateUserDto, user: UserModel): Promise<UserModel> {\n if (user === null) {\n return this.adminUsersManager.createUserOrGuest(identity, USER_ROLE.USER)\n } else {\n if (identity.login !== user.login) {\n this.logger.error(`${this.updateOrCreateUser.name} - user id mismatch : ${identity.login} !== ${user.login}`)\n throw new HttpException('Account matching error', HttpStatus.FORBIDDEN)\n }\n // check if user information has changed\n const identityHasChanged: UpdateUserDto = Object.fromEntries(\n (\n await Promise.all(\n Object.keys(identity).map(async (key: string) => {\n if (key === 'password') {\n const isSame = await comparePassword(identity[key], user.password)\n return isSame ? null : [key, identity[key]]\n }\n return identity[key] !== user[key] ? [key, identity[key]] : null\n })\n )\n ).filter(Boolean)\n )\n if (Object.keys(identityHasChanged).length > 0) {\n try {\n await this.adminUsersManager.updateUserOrGuest(user.id, identityHasChanged)\n if (identityHasChanged?.password) {\n delete identityHasChanged.password\n }\n Object.assign(user, identityHasChanged)\n } catch (e) {\n this.logger.warn(`${this.updateOrCreateUser.name} - unable to update user *${user.login}* : ${e}`)\n }\n }\n await user.makePaths()\n return user\n }\n }\n}\n"],"names":["AuthMethodLdapService","validateUser","loginOrEmail","password","ip","user","usersManager","findUser","isGuest","logUser","isActive","logger","error","name","login","HttpException","HttpStatus","FORBIDDEN","entry","checkAuth","updateAccesses","catch","e","mail","uid","JSON","stringify","identity","email","splitFullName","cn","updateOrCreateUser","servers","configuration","auth","ldap","bindUserDN","loginAttribute","baseDN","client","s","Client","clientOptions","url","bind","checkAccess","errors","length","err","warn","InvalidCredentialsError","unbind","CONNECT_ERROR_CODE","has","code","INTERNAL_SERVER_ERROR","searchFilter","filter","searchEntries","search","scope","attributes","entryAttributes","Array","isArray","adminUsersManager","createUserOrGuest","USER_ROLE","USER","identityHasChanged","Object","fromEntries","Promise","all","keys","map","key","isSame","comparePassword","Boolean","updateUserOrGuest","id","assign","makePaths","Logger","timeout","connectTimeout"],"mappings":"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"}
|