diff --git a/LICENSE b/LICENSE index 40b88506..bb61842c 100644 --- a/LICENSE +++ b/LICENSE @@ -629,7 +629,7 @@ to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. - Sync-in® Copyright (C) 2012-2025 Johan Legrand + Sync-in® Copyright (C) 2012-2026 Johan Legrand This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published diff --git a/backend/eslint.config.mjs b/backend/eslint.config.mjs index abe182b2..0e46da18 100644 --- a/backend/eslint.config.mjs +++ b/backend/eslint.config.mjs @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - // @ts-check import eslint from '@eslint/js' import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended' diff --git a/backend/package.json b/backend/package.json index af87f11f..22e68977 100755 --- a/backend/package.json +++ b/backend/package.json @@ -69,6 +69,7 @@ "mysql2": "^3.11.4", "nestjs-pino": "^4.3.0", "nodemailer": "^7.0.0", + "openid-client": "^6.8.1", "passport": "^0.7.0", "passport-jwt": "^4.0.1", "passport-local": "^1.0.0", diff --git a/backend/src/app.bootstrap.ts b/backend/src/app.bootstrap.ts index 98ca7145..97fa98b1 100644 --- a/backend/src/app.bootstrap.ts +++ b/backend/src/app.bootstrap.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import fastifyCookie from '@fastify/cookie' import fastifyHelmet from '@fastify/helmet' import multipart from '@fastify/multipart' diff --git a/backend/src/app.constants.ts b/backend/src/app.constants.ts index 53640026..92cf253b 100644 --- a/backend/src/app.constants.ts +++ b/backend/src/app.constants.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { loadVersion } from './app.functions' export const VERSION = loadVersion() diff --git a/backend/src/app.e2e-spec.ts b/backend/src/app.e2e-spec.ts index 2178b53f..eaa979bf 100644 --- a/backend/src/app.e2e-spec.ts +++ b/backend/src/app.e2e-spec.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { NestFastifyApplication } from '@nestjs/platform-fastify' import { appBootstrap } from './app.bootstrap' import { dbCloseConnection } from './infrastructure/database/utils' diff --git a/backend/src/app.functions.ts b/backend/src/app.functions.ts index bdfd1257..6369b229 100644 --- a/backend/src/app.functions.ts +++ b/backend/src/app.functions.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { existsSync, readFileSync } from 'fs' import { join, resolve } from 'node:path' import { IS_TEST_ENV } from './configuration/config.constants' diff --git a/backend/src/app.module.ts b/backend/src/app.module.ts index d9e67c97..067f4321 100755 --- a/backend/src/app.module.ts +++ b/backend/src/app.module.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { HttpModule } from '@nestjs/axios' import { Module } from '@nestjs/common' import { ConfigModule } from '@nestjs/config' diff --git a/backend/src/app.service.spec.ts b/backend/src/app.service.spec.ts index 0e1b130c..7cc40cb4 100644 --- a/backend/src/app.service.spec.ts +++ b/backend/src/app.service.spec.ts @@ -1,10 +1,5 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Logger } from '@nestjs/common' +import { setupPrimary } from '@socket.io/cluster-adapter' import cluster from 'node:cluster' import fs from 'node:fs' import os from 'node:os' @@ -13,10 +8,10 @@ import process from 'node:process' import { AppService } from './app.service' import { ENVIRONMENT_PREFIX } from './configuration/config.constants' import { configuration, exportConfiguration } from './configuration/config.environment' + jest.mock('@socket.io/cluster-adapter', () => ({ setupPrimary: jest.fn() })) -import { setupPrimary } from '@socket.io/cluster-adapter' describe(AppService.name, () => { let appService: AppService diff --git a/backend/src/app.service.ts b/backend/src/app.service.ts index 3b7819d2..9588898f 100755 --- a/backend/src/app.service.ts +++ b/backend/src/app.service.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Injectable, Logger } from '@nestjs/common' import { setupPrimary } from '@socket.io/cluster-adapter' import cluster, { Worker } from 'node:cluster' diff --git a/backend/src/applications/admin/admin.module.ts b/backend/src/applications/admin/admin.module.ts index 63055619..39b7c454 100644 --- a/backend/src/applications/admin/admin.module.ts +++ b/backend/src/applications/admin/admin.module.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Module } from '@nestjs/common' import { AdminSchedulerService } from './services/admin-scheduler.service' import { AdminService } from './services/admin.service' diff --git a/backend/src/applications/admin/constants/routes.ts b/backend/src/applications/admin/constants/routes.ts index 74e9f4be..ca7d956a 100644 --- a/backend/src/applications/admin/constants/routes.ts +++ b/backend/src/applications/admin/constants/routes.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - export const ADMIN_ROUTE = { BASE: '/api/admin' } diff --git a/backend/src/applications/admin/interfaces/check-update.interfaces.ts b/backend/src/applications/admin/interfaces/check-update.interfaces.ts index e7b11c83..b81eb609 100644 --- a/backend/src/applications/admin/interfaces/check-update.interfaces.ts +++ b/backend/src/applications/admin/interfaces/check-update.interfaces.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - export interface ServerReleaseVersionManifest { // tag_name: `v1.0.0` tag_name: string diff --git a/backend/src/applications/admin/services/admin-scheduler.service.ts b/backend/src/applications/admin/services/admin-scheduler.service.ts index c74ecef6..a054429f 100644 --- a/backend/src/applications/admin/services/admin-scheduler.service.ts +++ b/backend/src/applications/admin/services/admin-scheduler.service.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Injectable } from '@nestjs/common' import { Cron, CronExpression } from '@nestjs/schedule' import { setTimeout } from 'node:timers/promises' diff --git a/backend/src/applications/admin/services/admin.service.spec.ts b/backend/src/applications/admin/services/admin.service.spec.ts index 44c650a2..b1d7ae04 100644 --- a/backend/src/applications/admin/services/admin.service.spec.ts +++ b/backend/src/applications/admin/services/admin.service.spec.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { HttpService } from '@nestjs/axios' import { Test, TestingModule } from '@nestjs/testing' import { Cache } from '../../../infrastructure/cache/services/cache.service' diff --git a/backend/src/applications/admin/services/admin.service.ts b/backend/src/applications/admin/services/admin.service.ts index ed727296..c9332680 100644 --- a/backend/src/applications/admin/services/admin.service.ts +++ b/backend/src/applications/admin/services/admin.service.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { HttpService } from '@nestjs/axios' import { Injectable, Logger } from '@nestjs/common' import type { AxiosResponse } from 'axios' diff --git a/backend/src/applications/admin/utils/check-update.ts b/backend/src/applications/admin/utils/check-update.ts index a2356fa8..7d714e36 100644 --- a/backend/src/applications/admin/utils/check-update.ts +++ b/backend/src/applications/admin/utils/check-update.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - export function isServerUpdateAvailable(current: string, latest: string) { const c = current.split('.').map(Number) const l = latest.split('.').map(Number) diff --git a/backend/src/applications/applications.config.ts b/backend/src/applications/applications.config.ts index 59f2a279..278c20de 100644 --- a/backend/src/applications/applications.config.ts +++ b/backend/src/applications/applications.config.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Type } from 'class-transformer' import { IsDefined, IsNotEmptyObject, IsObject, ValidateNested } from 'class-validator' import { FilesConfig } from './files/files.config' diff --git a/backend/src/applications/applications.constants.ts b/backend/src/applications/applications.constants.ts index 6d709f03..d8193b0c 100644 --- a/backend/src/applications/applications.constants.ts +++ b/backend/src/applications/applications.constants.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - export const APP_BASE_ROUTE = '/api/app' export const HTTP_WEBDAV_METHOD = { diff --git a/backend/src/applications/applications.module.ts b/backend/src/applications/applications.module.ts index 849be375..d4a580b7 100644 --- a/backend/src/applications/applications.module.ts +++ b/backend/src/applications/applications.module.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Global, Module } from '@nestjs/common' import { AdminModule } from './admin/admin.module' import { CommentsModule } from './comments/comments.module' diff --git a/backend/src/applications/comments/comments.controller.spec.ts b/backend/src/applications/comments/comments.controller.spec.ts index c81713a8..fc9c98d3 100644 --- a/backend/src/applications/comments/comments.controller.spec.ts +++ b/backend/src/applications/comments/comments.controller.spec.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Test, TestingModule } from '@nestjs/testing' import { Cache } from '../../infrastructure/cache/services/cache.service' import { ContextManager } from '../../infrastructure/context/services/context-manager.service' diff --git a/backend/src/applications/comments/comments.controller.ts b/backend/src/applications/comments/comments.controller.ts index 581ec11e..28786170 100644 --- a/backend/src/applications/comments/comments.controller.ts +++ b/backend/src/applications/comments/comments.controller.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Body, Controller, Delete, Get, Patch, Post, Query, UseGuards, UseInterceptors } from '@nestjs/common' import { ContextInterceptor } from '../../infrastructure/context/interceptors/context.interceptor' import { SkipSpaceGuard } from '../spaces/decorators/space-skip-guard.decorator' diff --git a/backend/src/applications/comments/comments.module.ts b/backend/src/applications/comments/comments.module.ts index 88ee3f61..90268e8a 100644 --- a/backend/src/applications/comments/comments.module.ts +++ b/backend/src/applications/comments/comments.module.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Module } from '@nestjs/common' import { CommentsController } from './comments.controller' import { CommentsManager } from './services/comments-manager.service' diff --git a/backend/src/applications/comments/constants/routes.ts b/backend/src/applications/comments/constants/routes.ts index 578da38a..88a68290 100644 --- a/backend/src/applications/comments/constants/routes.ts +++ b/backend/src/applications/comments/constants/routes.ts @@ -1,8 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ import { APP_BASE_ROUTE } from '../../applications.constants' export const COMMENTS_ROUTE = { diff --git a/backend/src/applications/comments/dto/comment.dto.ts b/backend/src/applications/comments/dto/comment.dto.ts index f43bb15c..8e394424 100644 --- a/backend/src/applications/comments/dto/comment.dto.ts +++ b/backend/src/applications/comments/dto/comment.dto.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { IsInt, IsNotEmpty, IsOptional, IsString } from 'class-validator' export class CreateOrUpdateCommentDto { diff --git a/backend/src/applications/comments/interfaces/comment-recent.interface.ts b/backend/src/applications/comments/interfaces/comment-recent.interface.ts index e2d34d3a..ee1ede1c 100644 --- a/backend/src/applications/comments/interfaces/comment-recent.interface.ts +++ b/backend/src/applications/comments/interfaces/comment-recent.interface.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import type { Owner } from '../../users/interfaces/owner.interface' export interface CommentRecent { diff --git a/backend/src/applications/comments/schemas/comment.interface.ts b/backend/src/applications/comments/schemas/comment.interface.ts index d22f414c..64beb407 100644 --- a/backend/src/applications/comments/schemas/comment.interface.ts +++ b/backend/src/applications/comments/schemas/comment.interface.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Owner } from '../../users/interfaces/owner.interface' import type { comments } from './comments.schema' diff --git a/backend/src/applications/comments/schemas/comments.schema.ts b/backend/src/applications/comments/schemas/comments.schema.ts index da2c0166..6e79c524 100644 --- a/backend/src/applications/comments/schemas/comments.schema.ts +++ b/backend/src/applications/comments/schemas/comments.schema.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Column, SQL, sql } from 'drizzle-orm' import { bigint, datetime, index, mysqlTable, text } from 'drizzle-orm/mysql-core' import { files } from '../../files/schemas/files.schema' diff --git a/backend/src/applications/comments/services/comments-manager.service.spec.ts b/backend/src/applications/comments/services/comments-manager.service.spec.ts index 9fb9897a..b535dfc1 100644 --- a/backend/src/applications/comments/services/comments-manager.service.spec.ts +++ b/backend/src/applications/comments/services/comments-manager.service.spec.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { HttpException, HttpStatus } from '@nestjs/common' import { Test, TestingModule } from '@nestjs/testing' import { Cache } from '../../../infrastructure/cache/services/cache.service' diff --git a/backend/src/applications/comments/services/comments-manager.service.ts b/backend/src/applications/comments/services/comments-manager.service.ts index c27a6c8b..fba718de 100644 --- a/backend/src/applications/comments/services/comments-manager.service.ts +++ b/backend/src/applications/comments/services/comments-manager.service.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { HttpException, HttpStatus, Injectable, Logger } from '@nestjs/common' import { ContextManager } from '../../../infrastructure/context/services/context-manager.service' import type { FileProps } from '../../files/interfaces/file-props.interface' diff --git a/backend/src/applications/comments/services/comments-queries.service.ts b/backend/src/applications/comments/services/comments-queries.service.ts index 2fe37790..46c50b51 100644 --- a/backend/src/applications/comments/services/comments-queries.service.ts +++ b/backend/src/applications/comments/services/comments-queries.service.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Inject, Injectable } from '@nestjs/common' import { and, desc, eq, getTableColumns, inArray, isNotNull, isNull, ne, or, SelectedFields, SQL, sql } from 'drizzle-orm' import { alias, union } from 'drizzle-orm/mysql-core' diff --git a/backend/src/applications/files/adapters/files-indexer-mysql.service.spec.ts b/backend/src/applications/files/adapters/files-indexer-mysql.service.spec.ts index 96d8cba6..22954976 100644 --- a/backend/src/applications/files/adapters/files-indexer-mysql.service.spec.ts +++ b/backend/src/applications/files/adapters/files-indexer-mysql.service.spec.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Test, TestingModule } from '@nestjs/testing' import { Cache } from '../../../infrastructure/cache/services/cache.service' import { DB_TOKEN_PROVIDER } from '../../../infrastructure/database/constants' diff --git a/backend/src/applications/files/adapters/files-indexer-mysql.service.ts b/backend/src/applications/files/adapters/files-indexer-mysql.service.ts index d775f9c4..45cdc0e9 100644 --- a/backend/src/applications/files/adapters/files-indexer-mysql.service.ts +++ b/backend/src/applications/files/adapters/files-indexer-mysql.service.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Inject, Injectable, Logger } from '@nestjs/common' import { SQL, sql } from 'drizzle-orm' import { MySqlQueryResult } from 'drizzle-orm/mysql2' diff --git a/backend/src/applications/files/constants/cache.ts b/backend/src/applications/files/constants/cache.ts index 2308b9b9..cd06483a 100644 --- a/backend/src/applications/files/constants/cache.ts +++ b/backend/src/applications/files/constants/cache.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - // cache task key = `ftask-$(userId}-${taskId}` => FileTask export const CACHE_TASK_PREFIX = 'ftask' as const export const CACHE_TASK_TTL = 86400 as const // one day diff --git a/backend/src/applications/files/constants/compress.ts b/backend/src/applications/files/constants/compress.ts index 6bec6fad..48e2beea 100644 --- a/backend/src/applications/files/constants/compress.ts +++ b/backend/src/applications/files/constants/compress.ts @@ -1,8 +1,2 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - export const tarExtension = 'tar' export const tarGzExtension = 'tgz' diff --git a/backend/src/applications/files/constants/files.ts b/backend/src/applications/files/constants/files.ts index 15eb29fa..00b391d0 100644 --- a/backend/src/applications/files/constants/files.ts +++ b/backend/src/applications/files/constants/files.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - export const DEFAULT_CHECKSUM_ALGORITHM = 'sha512-256' export const DEFAULT_HIGH_WATER_MARK = 1024 * 1024 export const DEFAULT_MIME_TYPE = 'application/octet-stream' diff --git a/backend/src/applications/files/constants/indexing.ts b/backend/src/applications/files/constants/indexing.ts index d56dab26..aba74315 100644 --- a/backend/src/applications/files/constants/indexing.ts +++ b/backend/src/applications/files/constants/indexing.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - export const userIndexPrefix = 'user_' export const spaceIndexPrefix = 'space_' export const shareIndexPrefix = 'share_' diff --git a/backend/src/applications/files/constants/operations.ts b/backend/src/applications/files/constants/operations.ts index 8ba0d8bc..ce800d45 100644 --- a/backend/src/applications/files/constants/operations.ts +++ b/backend/src/applications/files/constants/operations.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - export enum FILE_OPERATION { MAKE = 'make', COPY = 'copy', diff --git a/backend/src/applications/files/constants/routes.ts b/backend/src/applications/files/constants/routes.ts index ff8c7222..1e98a069 100644 --- a/backend/src/applications/files/constants/routes.ts +++ b/backend/src/applications/files/constants/routes.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { SPACES_ROUTE } from '../../spaces/constants/routes' import { FILE_OPERATION } from './operations' diff --git a/backend/src/applications/files/constants/samples.ts b/backend/src/applications/files/constants/samples.ts index f7f70cde..b15c22db 100644 --- a/backend/src/applications/files/constants/samples.ts +++ b/backend/src/applications/files/constants/samples.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - export const SAMPLE_PATH_WITHOUT_EXT = '../assets/samples/sample' export const DOCUMENT_TYPE = { 'Microsoft Word': '.docx', diff --git a/backend/src/applications/files/dto/file-operations.dto.ts b/backend/src/applications/files/dto/file-operations.dto.ts index 4da50717..50f986bd 100644 --- a/backend/src/applications/files/dto/file-operations.dto.ts +++ b/backend/src/applications/files/dto/file-operations.dto.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Transform } from 'class-transformer' import { ArrayMinSize, IsArray, IsBoolean, IsDefined, IsIn, IsInt, IsNotEmpty, IsOptional, IsString, IsUrl } from 'class-validator' import { RejectIfMatch } from '../../../common/decorators' diff --git a/backend/src/applications/files/events/file-task-event.ts b/backend/src/applications/files/events/file-task-event.ts index f8f562ff..806d688b 100644 --- a/backend/src/applications/files/events/file-task-event.ts +++ b/backend/src/applications/files/events/file-task-event.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import EventEmitter from 'node:events' export const FileTaskEvent: EventEmitter = new EventEmitter() diff --git a/backend/src/applications/files/files-tasks.controller.spec.ts b/backend/src/applications/files/files-tasks.controller.spec.ts index 084e464f..04b86f7e 100644 --- a/backend/src/applications/files/files-tasks.controller.spec.ts +++ b/backend/src/applications/files/files-tasks.controller.spec.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Test, TestingModule } from '@nestjs/testing' import { Cache } from '../../infrastructure/cache/services/cache.service' import { FilesTasksController } from './files-tasks.controller' diff --git a/backend/src/applications/files/files-tasks.controller.ts b/backend/src/applications/files/files-tasks.controller.ts index 6dbb3b7e..4190946d 100644 --- a/backend/src/applications/files/files-tasks.controller.ts +++ b/backend/src/applications/files/files-tasks.controller.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Controller, Delete, Get, Param, Req, Res, StreamableFile } from '@nestjs/common' import { FastifyReply } from 'fastify' import { FastifySpaceRequest } from '../spaces/interfaces/space-request.interface' diff --git a/backend/src/applications/files/files.config.ts b/backend/src/applications/files/files.config.ts index a7ab6280..f6f7feae 100644 --- a/backend/src/applications/files/files.config.ts +++ b/backend/src/applications/files/files.config.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Type } from 'class-transformer' import { IsBoolean, IsInt, IsNotEmpty, IsNotEmptyObject, IsString, ValidateNested } from 'class-validator' import { CollaboraOnlineConfig } from './modules/collabora-online/collabora-online.config' diff --git a/backend/src/applications/files/files.controller.spec.ts b/backend/src/applications/files/files.controller.spec.ts index cf819d94..b06719f4 100644 --- a/backend/src/applications/files/files.controller.spec.ts +++ b/backend/src/applications/files/files.controller.spec.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Test, TestingModule } from '@nestjs/testing' import { ContextInterceptor } from '../../infrastructure/context/interceptors/context.interceptor' import { ContextManager } from '../../infrastructure/context/services/context-manager.service' diff --git a/backend/src/applications/files/files.controller.ts b/backend/src/applications/files/files.controller.ts index 94d8647c..fc2c0503 100644 --- a/backend/src/applications/files/files.controller.ts +++ b/backend/src/applications/files/files.controller.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Body, Controller, diff --git a/backend/src/applications/files/files.module.ts b/backend/src/applications/files/files.module.ts index 47ef8306..e10f9b7a 100644 --- a/backend/src/applications/files/files.module.ts +++ b/backend/src/applications/files/files.module.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Module } from '@nestjs/common' import { configuration } from '../../configuration/config.environment' import { FilesIndexerMySQL } from './adapters/files-indexer-mysql.service' diff --git a/backend/src/applications/files/interfaces/file-db-props.interface.ts b/backend/src/applications/files/interfaces/file-db-props.interface.ts index dc291b09..22541134 100644 --- a/backend/src/applications/files/interfaces/file-db-props.interface.ts +++ b/backend/src/applications/files/interfaces/file-db-props.interface.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import type { File } from '../schemas/file.interface' export interface FileDBProps extends Partial> { diff --git a/backend/src/applications/files/interfaces/file-lock.interface.ts b/backend/src/applications/files/interfaces/file-lock.interface.ts index 9d8312d4..0d863ac7 100644 --- a/backend/src/applications/files/interfaces/file-lock.interface.ts +++ b/backend/src/applications/files/interfaces/file-lock.interface.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { SERVER_NAME } from '../../../common/shared' import { Owner } from '../../users/interfaces/owner.interface' import { LOCK_DEPTH, LOCK_SCOPE, WEBDAV_APP_LOCK } from '../../webdav/constants/webdav' diff --git a/backend/src/applications/files/interfaces/file-parse-index.ts b/backend/src/applications/files/interfaces/file-parse-index.ts index 8f72a899..eb825908 100644 --- a/backend/src/applications/files/interfaces/file-parse-index.ts +++ b/backend/src/applications/files/interfaces/file-parse-index.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - export type FileParseType = 'user' | 'space' | 'share' export interface FileParseContext { diff --git a/backend/src/applications/files/interfaces/file-props.interface.ts b/backend/src/applications/files/interfaces/file-props.interface.ts index bedfda14..25573a8a 100644 --- a/backend/src/applications/files/interfaces/file-props.interface.ts +++ b/backend/src/applications/files/interfaces/file-props.interface.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import type { Share } from '../../shares/schemas/share.interface' import type { SpaceRoot } from '../../spaces/schemas/space-root.interface' import type { Space } from '../../spaces/schemas/space.interface' diff --git a/backend/src/applications/files/interfaces/file-recent-location.interface.ts b/backend/src/applications/files/interfaces/file-recent-location.interface.ts index 18159d60..2d4acc9e 100644 --- a/backend/src/applications/files/interfaces/file-recent-location.interface.ts +++ b/backend/src/applications/files/interfaces/file-recent-location.interface.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - export interface FileRecentLocation { ownerId?: number spaceId?: number diff --git a/backend/src/applications/files/interfaces/file-space.interface.ts b/backend/src/applications/files/interfaces/file-space.interface.ts index 660db7fa..e61303ef 100644 --- a/backend/src/applications/files/interfaces/file-space.interface.ts +++ b/backend/src/applications/files/interfaces/file-space.interface.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import type { File } from '../schemas/file.interface' export class FileSpace implements Pick { diff --git a/backend/src/applications/files/interfaces/file-tree.interface.ts b/backend/src/applications/files/interfaces/file-tree.interface.ts index bb7848e9..f632aa24 100644 --- a/backend/src/applications/files/interfaces/file-tree.interface.ts +++ b/backend/src/applications/files/interfaces/file-tree.interface.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import type { FileProps } from './file-props.interface' export interface FileTree extends Pick { diff --git a/backend/src/applications/files/models/file-error.ts b/backend/src/applications/files/models/file-error.ts index 58021101..0df437d1 100644 --- a/backend/src/applications/files/models/file-error.ts +++ b/backend/src/applications/files/models/file-error.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - export class FileError extends Error { httpCode: number diff --git a/backend/src/applications/files/models/file-lock-error.ts b/backend/src/applications/files/models/file-lock-error.ts index 3b50194a..25186fa2 100644 --- a/backend/src/applications/files/models/file-lock-error.ts +++ b/backend/src/applications/files/models/file-lock-error.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { FileLock } from '../interfaces/file-lock.interface' export class LockConflict extends Error { diff --git a/backend/src/applications/files/models/file-task.ts b/backend/src/applications/files/models/file-task.ts index e62f2940..96b90db7 100644 --- a/backend/src/applications/files/models/file-task.ts +++ b/backend/src/applications/files/models/file-task.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { FILE_OPERATION } from '../constants/operations' export enum FileTaskStatus { diff --git a/backend/src/applications/files/models/files-indexer.ts b/backend/src/applications/files/models/files-indexer.ts index f2299db9..68bbb78c 100644 --- a/backend/src/applications/files/models/files-indexer.ts +++ b/backend/src/applications/files/models/files-indexer.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { FileContent } from '../schemas/file-content.interface' export abstract class FilesIndexer { diff --git a/backend/src/applications/files/modules/collabora-online/collabora-online-environment.decorator.ts b/backend/src/applications/files/modules/collabora-online/collabora-online-environment.decorator.ts index dde035e5..4ca5865b 100644 --- a/backend/src/applications/files/modules/collabora-online/collabora-online-environment.decorator.ts +++ b/backend/src/applications/files/modules/collabora-online/collabora-online-environment.decorator.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { applyDecorators, SetMetadata, UseGuards } from '@nestjs/common' import { SpaceGuard } from '../../../spaces/guards/space.guard' import { COLLABORA_CONTEXT } from './collabora-online.constants' diff --git a/backend/src/applications/files/modules/collabora-online/collabora-online-manager.service.spec.ts b/backend/src/applications/files/modules/collabora-online/collabora-online-manager.service.spec.ts index 855d2111..dd4a778d 100644 --- a/backend/src/applications/files/modules/collabora-online/collabora-online-manager.service.spec.ts +++ b/backend/src/applications/files/modules/collabora-online/collabora-online-manager.service.spec.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { HttpException, HttpStatus } from '@nestjs/common' import { JwtService } from '@nestjs/jwt' import { Test, TestingModule } from '@nestjs/testing' diff --git a/backend/src/applications/files/modules/collabora-online/collabora-online-manager.service.ts b/backend/src/applications/files/modules/collabora-online/collabora-online-manager.service.ts index 937f1a01..dcdb7df2 100644 --- a/backend/src/applications/files/modules/collabora-online/collabora-online-manager.service.ts +++ b/backend/src/applications/files/modules/collabora-online/collabora-online-manager.service.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { HttpException, HttpStatus, Injectable, Logger } from '@nestjs/common' import { JwtService } from '@nestjs/jwt' import { FastifyReply } from 'fastify' diff --git a/backend/src/applications/files/modules/collabora-online/collabora-online.config.ts b/backend/src/applications/files/modules/collabora-online/collabora-online.config.ts index bab3fc50..e5a891d2 100644 --- a/backend/src/applications/files/modules/collabora-online/collabora-online.config.ts +++ b/backend/src/applications/files/modules/collabora-online/collabora-online.config.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { IsBoolean, IsOptional, IsString } from 'class-validator' export class CollaboraOnlineConfig { diff --git a/backend/src/applications/files/modules/collabora-online/collabora-online.constants.ts b/backend/src/applications/files/modules/collabora-online/collabora-online.constants.ts index 8e220f8f..3a4abedd 100644 --- a/backend/src/applications/files/modules/collabora-online/collabora-online.constants.ts +++ b/backend/src/applications/files/modules/collabora-online/collabora-online.constants.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - export const COLLABORA_URI = 'browser/dist/cool.html' export const COLLABORA_CONTEXT = 'CollaboraOnlineEnvironment' as const export const COLLABORA_WOPI_SRC_QUERY_PARAM_NAME = 'WOPISrc' as const diff --git a/backend/src/applications/files/modules/collabora-online/collabora-online.controller.spec.ts b/backend/src/applications/files/modules/collabora-online/collabora-online.controller.spec.ts index de6b0bc9..756a31ab 100644 --- a/backend/src/applications/files/modules/collabora-online/collabora-online.controller.spec.ts +++ b/backend/src/applications/files/modules/collabora-online/collabora-online.controller.spec.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Test, TestingModule } from '@nestjs/testing' import { ContextInterceptor } from '../../../../infrastructure/context/interceptors/context.interceptor' import { ContextManager } from '../../../../infrastructure/context/services/context-manager.service' diff --git a/backend/src/applications/files/modules/collabora-online/collabora-online.controller.ts b/backend/src/applications/files/modules/collabora-online/collabora-online.controller.ts index 0dfa1af4..df51f08d 100644 --- a/backend/src/applications/files/modules/collabora-online/collabora-online.controller.ts +++ b/backend/src/applications/files/modules/collabora-online/collabora-online.controller.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Controller, Get, HttpCode, HttpStatus, Post, Request, Res, StreamableFile, UseInterceptors } from '@nestjs/common' import { FastifyReply } from 'fastify' import { ContextInterceptor } from '../../../../infrastructure/context/interceptors/context.interceptor' diff --git a/backend/src/applications/files/modules/collabora-online/collabora-online.dtos.ts b/backend/src/applications/files/modules/collabora-online/collabora-online.dtos.ts index 7d246439..a4904102 100644 --- a/backend/src/applications/files/modules/collabora-online/collabora-online.dtos.ts +++ b/backend/src/applications/files/modules/collabora-online/collabora-online.dtos.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import type { FILE_MODE } from '../../constants/operations' import type { FileLockProps } from '../../interfaces/file-props.interface' diff --git a/backend/src/applications/files/modules/collabora-online/collabora-online.guard.spec.ts b/backend/src/applications/files/modules/collabora-online/collabora-online.guard.spec.ts index 1e1f1fdb..da43ddd0 100644 --- a/backend/src/applications/files/modules/collabora-online/collabora-online.guard.spec.ts +++ b/backend/src/applications/files/modules/collabora-online/collabora-online.guard.spec.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { createMock, DeepMocked } from '@golevelup/ts-jest' import { ExecutionContext } from '@nestjs/common' import { JwtModule, JwtService } from '@nestjs/jwt' diff --git a/backend/src/applications/files/modules/collabora-online/collabora-online.guard.ts b/backend/src/applications/files/modules/collabora-online/collabora-online.guard.ts index d1c5b38f..a2b721ce 100644 --- a/backend/src/applications/files/modules/collabora-online/collabora-online.guard.ts +++ b/backend/src/applications/files/modules/collabora-online/collabora-online.guard.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { ExecutionContext, Injectable, Logger } from '@nestjs/common' import { AuthGuard, IAuthGuard } from '@nestjs/passport' diff --git a/backend/src/applications/files/modules/collabora-online/collabora-online.interface.ts b/backend/src/applications/files/modules/collabora-online/collabora-online.interface.ts index 4274d4bc..89304623 100644 --- a/backend/src/applications/files/modules/collabora-online/collabora-online.interface.ts +++ b/backend/src/applications/files/modules/collabora-online/collabora-online.interface.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import type { JwtIdentityPayload } from '../../../../authentication/interfaces/jwt-payload.interface' import type { FastifySpaceRequest } from '../../../spaces/interfaces/space-request.interface' diff --git a/backend/src/applications/files/modules/collabora-online/collabora-online.module.ts b/backend/src/applications/files/modules/collabora-online/collabora-online.module.ts index 0630e69a..feb822ad 100644 --- a/backend/src/applications/files/modules/collabora-online/collabora-online.module.ts +++ b/backend/src/applications/files/modules/collabora-online/collabora-online.module.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Module } from '@nestjs/common' import { CollaboraOnlineManager } from './collabora-online-manager.service' import { CollaboraOnlineController } from './collabora-online.controller' diff --git a/backend/src/applications/files/modules/collabora-online/collabora-online.routes.ts b/backend/src/applications/files/modules/collabora-online/collabora-online.routes.ts index 6a4c263f..f7e0a184 100644 --- a/backend/src/applications/files/modules/collabora-online/collabora-online.routes.ts +++ b/backend/src/applications/files/modules/collabora-online/collabora-online.routes.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - export const COLLABORA_ONLINE_ROUTE = { BASE: '/wopi', FILES: 'files', diff --git a/backend/src/applications/files/modules/collabora-online/collabora-online.strategy.ts b/backend/src/applications/files/modules/collabora-online/collabora-online.strategy.ts index c6b27584..c6b47b54 100644 --- a/backend/src/applications/files/modules/collabora-online/collabora-online.strategy.ts +++ b/backend/src/applications/files/modules/collabora-online/collabora-online.strategy.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Injectable } from '@nestjs/common' import { AbstractStrategy, PassportStrategy } from '@nestjs/passport' import { PinoLogger } from 'nestjs-pino' diff --git a/backend/src/applications/files/modules/collabora-online/collabora-online.utils.ts b/backend/src/applications/files/modules/collabora-online/collabora-online.utils.ts index 4b2acfa0..0e2cd179 100644 --- a/backend/src/applications/files/modules/collabora-online/collabora-online.utils.ts +++ b/backend/src/applications/files/modules/collabora-online/collabora-online.utils.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { PATH_TO_SPACE_SEGMENTS } from '../../../spaces/utils/routes' import type { FastifyCollaboraOnlineSpaceRequest } from './collabora-online.interface' diff --git a/backend/src/applications/files/modules/only-office/only-office-environment.decorator.ts b/backend/src/applications/files/modules/only-office/only-office-environment.decorator.ts index 5017a0f5..190ef0bc 100644 --- a/backend/src/applications/files/modules/only-office/only-office-environment.decorator.ts +++ b/backend/src/applications/files/modules/only-office/only-office-environment.decorator.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { applyDecorators, SetMetadata, UseGuards } from '@nestjs/common' import { SpaceGuard } from '../../../spaces/guards/space.guard' import { ONLY_OFFICE_CONTEXT } from './only-office.constants' diff --git a/backend/src/applications/files/modules/only-office/only-office-manager.service.spec.ts b/backend/src/applications/files/modules/only-office/only-office-manager.service.spec.ts index 6484a90b..149a25d8 100644 --- a/backend/src/applications/files/modules/only-office/only-office-manager.service.spec.ts +++ b/backend/src/applications/files/modules/only-office/only-office-manager.service.spec.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { HttpService } from '@nestjs/axios' import { HttpException, HttpStatus } from '@nestjs/common' import { JwtService } from '@nestjs/jwt' diff --git a/backend/src/applications/files/modules/only-office/only-office-manager.service.ts b/backend/src/applications/files/modules/only-office/only-office-manager.service.ts index e7779373..49db9a3d 100644 --- a/backend/src/applications/files/modules/only-office/only-office-manager.service.ts +++ b/backend/src/applications/files/modules/only-office/only-office-manager.service.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { HttpService } from '@nestjs/axios' import { HttpException, HttpStatus, Injectable, Logger } from '@nestjs/common' import { JwtService } from '@nestjs/jwt' diff --git a/backend/src/applications/files/modules/only-office/only-office.config.ts b/backend/src/applications/files/modules/only-office/only-office.config.ts index 7c7a754d..111c73a4 100644 --- a/backend/src/applications/files/modules/only-office/only-office.config.ts +++ b/backend/src/applications/files/modules/only-office/only-office.config.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { IsBoolean, IsNotEmpty, IsOptional, IsString, ValidateIf } from 'class-validator' export class OnlyOfficeConfig { diff --git a/backend/src/applications/files/modules/only-office/only-office.constants.ts b/backend/src/applications/files/modules/only-office/only-office.constants.ts index 9b08f187..0b970a82 100644 --- a/backend/src/applications/files/modules/only-office/only-office.constants.ts +++ b/backend/src/applications/files/modules/only-office/only-office.constants.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - export const ONLY_OFFICE_INTERNAL_URI = '/onlyoffice' // used by nginx as a proxy export const ONLY_OFFICE_CONTEXT = 'OnlyOfficeEnvironment' as const export const ONLY_OFFICE_TOKEN_QUERY_PARAM_NAME = 'token' as const diff --git a/backend/src/applications/files/modules/only-office/only-office.controller.spec.ts b/backend/src/applications/files/modules/only-office/only-office.controller.spec.ts index a419e9ee..0311b72d 100644 --- a/backend/src/applications/files/modules/only-office/only-office.controller.spec.ts +++ b/backend/src/applications/files/modules/only-office/only-office.controller.spec.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Test, TestingModule } from '@nestjs/testing' import { ContextInterceptor } from '../../../../infrastructure/context/interceptors/context.interceptor' import { ContextManager } from '../../../../infrastructure/context/services/context-manager.service' diff --git a/backend/src/applications/files/modules/only-office/only-office.controller.ts b/backend/src/applications/files/modules/only-office/only-office.controller.ts index b2260b5a..815d0757 100644 --- a/backend/src/applications/files/modules/only-office/only-office.controller.ts +++ b/backend/src/applications/files/modules/only-office/only-office.controller.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Body, Controller, Get, HttpCode, HttpStatus, Post, Request, Res, StreamableFile, UseInterceptors } from '@nestjs/common' import { FastifyReply } from 'fastify' import { ContextInterceptor } from '../../../../infrastructure/context/interceptors/context.interceptor' diff --git a/backend/src/applications/files/modules/only-office/only-office.dtos.ts b/backend/src/applications/files/modules/only-office/only-office.dtos.ts index 622f3079..2263b80f 100644 --- a/backend/src/applications/files/modules/only-office/only-office.dtos.ts +++ b/backend/src/applications/files/modules/only-office/only-office.dtos.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import type { FileLockProps } from '../../interfaces/file-props.interface' import type { OnlyOfficeConfig } from './only-office.interface' diff --git a/backend/src/applications/files/modules/only-office/only-office.guard.spec.ts b/backend/src/applications/files/modules/only-office/only-office.guard.spec.ts index fd6b9556..9eda9893 100644 --- a/backend/src/applications/files/modules/only-office/only-office.guard.spec.ts +++ b/backend/src/applications/files/modules/only-office/only-office.guard.spec.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { createMock, DeepMocked } from '@golevelup/ts-jest' import { ExecutionContext } from '@nestjs/common' import { JwtModule, JwtService } from '@nestjs/jwt' diff --git a/backend/src/applications/files/modules/only-office/only-office.guard.ts b/backend/src/applications/files/modules/only-office/only-office.guard.ts index 77c518f5..d4c8fc80 100644 --- a/backend/src/applications/files/modules/only-office/only-office.guard.ts +++ b/backend/src/applications/files/modules/only-office/only-office.guard.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { ExecutionContext, Injectable, Logger } from '@nestjs/common' import { AuthGuard, IAuthGuard } from '@nestjs/passport' diff --git a/backend/src/applications/files/modules/only-office/only-office.interface.ts b/backend/src/applications/files/modules/only-office/only-office.interface.ts index 826a67e7..87801c87 100644 --- a/backend/src/applications/files/modules/only-office/only-office.interface.ts +++ b/backend/src/applications/files/modules/only-office/only-office.interface.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { FILE_MODE } from '../../constants/operations' export interface OnlyOfficeConvertForm { diff --git a/backend/src/applications/files/modules/only-office/only-office.module.ts b/backend/src/applications/files/modules/only-office/only-office.module.ts index 855d907f..339d64ff 100644 --- a/backend/src/applications/files/modules/only-office/only-office.module.ts +++ b/backend/src/applications/files/modules/only-office/only-office.module.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Module } from '@nestjs/common' import { OnlyOfficeManager } from './only-office-manager.service' import { OnlyOfficeController } from './only-office.controller' diff --git a/backend/src/applications/files/modules/only-office/only-office.routes.ts b/backend/src/applications/files/modules/only-office/only-office.routes.ts index abef1aa6..26465f3c 100644 --- a/backend/src/applications/files/modules/only-office/only-office.routes.ts +++ b/backend/src/applications/files/modules/only-office/only-office.routes.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { SPACES_ROUTE } from '../../../spaces/constants/routes' export const ONLY_OFFICE_ROUTE = { diff --git a/backend/src/applications/files/modules/only-office/only-office.strategy.ts b/backend/src/applications/files/modules/only-office/only-office.strategy.ts index c933ce2c..b85fb83c 100644 --- a/backend/src/applications/files/modules/only-office/only-office.strategy.ts +++ b/backend/src/applications/files/modules/only-office/only-office.strategy.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Injectable } from '@nestjs/common' import { AbstractStrategy, PassportStrategy } from '@nestjs/passport' import { PinoLogger } from 'nestjs-pino' diff --git a/backend/src/applications/files/schemas/file-content.interface.ts b/backend/src/applications/files/schemas/file-content.interface.ts index e7772276..695927d5 100644 --- a/backend/src/applications/files/schemas/file-content.interface.ts +++ b/backend/src/applications/files/schemas/file-content.interface.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - export interface FileContent { id: number path: string diff --git a/backend/src/applications/files/schemas/file-recent.interface.ts b/backend/src/applications/files/schemas/file-recent.interface.ts index 28380c72..f165716f 100644 --- a/backend/src/applications/files/schemas/file-recent.interface.ts +++ b/backend/src/applications/files/schemas/file-recent.interface.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { filesRecents } from './files-recents.schema' type FileRecentSchema = typeof filesRecents.$inferSelect diff --git a/backend/src/applications/files/schemas/file.interface.ts b/backend/src/applications/files/schemas/file.interface.ts index 4a7210f6..3d7a4bb7 100644 --- a/backend/src/applications/files/schemas/file.interface.ts +++ b/backend/src/applications/files/schemas/file.interface.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import type { files } from './files.schema' type FileSchema = typeof files.$inferSelect diff --git a/backend/src/applications/files/schemas/files-content.schema.ts b/backend/src/applications/files/schemas/files-content.schema.ts index 0d0b07c2..ce2952f3 100644 --- a/backend/src/applications/files/schemas/files-content.schema.ts +++ b/backend/src/applications/files/schemas/files-content.schema.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - export const FILES_CONTENT_TABLE_PREFIX = 'files_content_' as const // The utf8mb4_uca1400_ai_ci COLLATE is better for precision but slower diff --git a/backend/src/applications/files/schemas/files-recents.schema.ts b/backend/src/applications/files/schemas/files-recents.schema.ts index 0672de40..66cb1502 100644 --- a/backend/src/applications/files/schemas/files-recents.schema.ts +++ b/backend/src/applications/files/schemas/files-recents.schema.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { bigint, index, mysqlTable, varchar } from 'drizzle-orm/mysql-core' import { shares } from '../../shares/schemas/shares.schema' import { spaces } from '../../spaces/schemas/spaces.schema' diff --git a/backend/src/applications/files/schemas/files.schema.ts b/backend/src/applications/files/schemas/files.schema.ts index edf7cceb..2b930a9f 100644 --- a/backend/src/applications/files/schemas/files.schema.ts +++ b/backend/src/applications/files/schemas/files.schema.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { SQL, sql } from 'drizzle-orm' import { AnyMySqlColumn, bigint, boolean, index, mysqlTable, varchar } from 'drizzle-orm/mysql-core' import { escapeSQLRegexp } from '../../../common/functions' diff --git a/backend/src/applications/files/services/files-content-manager.service.ts b/backend/src/applications/files/services/files-content-manager.service.ts index 8f34ed44..79e66346 100644 --- a/backend/src/applications/files/services/files-content-manager.service.ts +++ b/backend/src/applications/files/services/files-content-manager.service.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Injectable, Logger } from '@nestjs/common' import fs from 'fs/promises' import { Stats } from 'node:fs' diff --git a/backend/src/applications/files/services/files-lock-manager.service.spec.ts b/backend/src/applications/files/services/files-lock-manager.service.spec.ts index e21c3f2a..b4570e59 100644 --- a/backend/src/applications/files/services/files-lock-manager.service.spec.ts +++ b/backend/src/applications/files/services/files-lock-manager.service.spec.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Test, TestingModule } from '@nestjs/testing' import { Cache } from '../../../infrastructure/cache/services/cache.service' import { DB_TOKEN_PROVIDER } from '../../../infrastructure/database/constants' diff --git a/backend/src/applications/files/services/files-lock-manager.service.ts b/backend/src/applications/files/services/files-lock-manager.service.ts index 9698d313..30d0cdd8 100644 --- a/backend/src/applications/files/services/files-lock-manager.service.ts +++ b/backend/src/applications/files/services/files-lock-manager.service.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Injectable, Logger } from '@nestjs/common' import crypto from 'node:crypto' diff --git a/backend/src/applications/files/services/files-manager.service.spec.ts b/backend/src/applications/files/services/files-manager.service.spec.ts index 7a86f5c4..eef29ebe 100644 --- a/backend/src/applications/files/services/files-manager.service.spec.ts +++ b/backend/src/applications/files/services/files-manager.service.spec.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { HttpService } from '@nestjs/axios' import { Test, TestingModule } from '@nestjs/testing' import { ContextManager } from '../../../infrastructure/context/services/context-manager.service' diff --git a/backend/src/applications/files/services/files-manager.service.ts b/backend/src/applications/files/services/files-manager.service.ts index f6b16041..883c2d11 100644 --- a/backend/src/applications/files/services/files-manager.service.ts +++ b/backend/src/applications/files/services/files-manager.service.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { HttpService } from '@nestjs/axios' import { HttpStatus, Injectable, Logger } from '@nestjs/common' import archiver, { Archiver, ArchiverError } from 'archiver' diff --git a/backend/src/applications/files/services/files-methods.service.spec.ts b/backend/src/applications/files/services/files-methods.service.spec.ts index 5d4c2a62..5c32debb 100644 --- a/backend/src/applications/files/services/files-methods.service.spec.ts +++ b/backend/src/applications/files/services/files-methods.service.spec.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Test, TestingModule } from '@nestjs/testing' import path from 'node:path' import { transformAndValidate } from '../../../common/functions' diff --git a/backend/src/applications/files/services/files-methods.service.ts b/backend/src/applications/files/services/files-methods.service.ts index 9adc7b1c..2d33ee18 100644 --- a/backend/src/applications/files/services/files-methods.service.ts +++ b/backend/src/applications/files/services/files-methods.service.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { HttpException, HttpStatus, Injectable, Logger, StreamableFile } from '@nestjs/common' import { FastifyReply } from 'fastify' import path from 'node:path' diff --git a/backend/src/applications/files/services/files-parser.service.spec.ts b/backend/src/applications/files/services/files-parser.service.spec.ts index eec00511..be77d6a4 100644 --- a/backend/src/applications/files/services/files-parser.service.spec.ts +++ b/backend/src/applications/files/services/files-parser.service.spec.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Test, TestingModule } from '@nestjs/testing' import { DB_TOKEN_PROVIDER } from '../../../infrastructure/database/constants' import { FilesParser } from './files-parser.service' diff --git a/backend/src/applications/files/services/files-parser.service.ts b/backend/src/applications/files/services/files-parser.service.ts index d0df7d9f..8afab095 100644 --- a/backend/src/applications/files/services/files-parser.service.ts +++ b/backend/src/applications/files/services/files-parser.service.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Inject, Injectable, Logger } from '@nestjs/common' import { and, eq, inArray, isNotNull, isNull, lte, or, sql } from 'drizzle-orm' import path from 'node:path' diff --git a/backend/src/applications/files/services/files-queries.service.ts b/backend/src/applications/files/services/files-queries.service.ts index ad51c187..9eb35bde 100644 --- a/backend/src/applications/files/services/files-queries.service.ts +++ b/backend/src/applications/files/services/files-queries.service.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Inject, Injectable, Logger } from '@nestjs/common' import { and, desc, eq, getTableColumns, inArray, isNull, or, SelectedFields, sql, SQL } from 'drizzle-orm' import { popFromObject } from '../../../common/shared' diff --git a/backend/src/applications/files/services/files-recents.service.spec.ts b/backend/src/applications/files/services/files-recents.service.spec.ts index 5f3aba25..0174d559 100644 --- a/backend/src/applications/files/services/files-recents.service.spec.ts +++ b/backend/src/applications/files/services/files-recents.service.spec.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Test, TestingModule } from '@nestjs/testing' import { SharesQueries } from '../../shares/services/shares-queries.service' import { SpacesQueries } from '../../spaces/services/spaces-queries.service' diff --git a/backend/src/applications/files/services/files-recents.service.ts b/backend/src/applications/files/services/files-recents.service.ts index fd7e5837..d41a7346 100644 --- a/backend/src/applications/files/services/files-recents.service.ts +++ b/backend/src/applications/files/services/files-recents.service.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Injectable } from '@nestjs/common' import { convertDiffUpdate, convertHumanTimeToMs, diffCollection } from '../../../common/functions' import { currentTimeStamp } from '../../../common/shared' diff --git a/backend/src/applications/files/services/files-scheduler.service.ts b/backend/src/applications/files/services/files-scheduler.service.ts index 174b2897..8153e1c1 100644 --- a/backend/src/applications/files/services/files-scheduler.service.ts +++ b/backend/src/applications/files/services/files-scheduler.service.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Inject, Injectable, Logger } from '@nestjs/common' import { Cron, CronExpression, Timeout } from '@nestjs/schedule' import { isNotNull, sql } from 'drizzle-orm' diff --git a/backend/src/applications/files/services/files-search-manager.service.spec.ts b/backend/src/applications/files/services/files-search-manager.service.spec.ts index 7b77cbfe..95b99383 100644 --- a/backend/src/applications/files/services/files-search-manager.service.spec.ts +++ b/backend/src/applications/files/services/files-search-manager.service.spec.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Test, TestingModule } from '@nestjs/testing' import { SharesQueries } from '../../shares/services/shares-queries.service' import { SpacesQueries } from '../../spaces/services/spaces-queries.service' diff --git a/backend/src/applications/files/services/files-search-manager.service.ts b/backend/src/applications/files/services/files-search-manager.service.ts index 904093b5..e6ac10b0 100644 --- a/backend/src/applications/files/services/files-search-manager.service.ts +++ b/backend/src/applications/files/services/files-search-manager.service.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { HttpException, HttpStatus, Injectable, Logger } from '@nestjs/common' import fs from 'fs/promises' import { Stats } from 'node:fs' diff --git a/backend/src/applications/files/services/files-tasks-manager.service.spec.ts b/backend/src/applications/files/services/files-tasks-manager.service.spec.ts index e49e488a..947a957c 100644 --- a/backend/src/applications/files/services/files-tasks-manager.service.spec.ts +++ b/backend/src/applications/files/services/files-tasks-manager.service.spec.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Test, TestingModule } from '@nestjs/testing' import { Cache } from '../../../infrastructure/cache/services/cache.service' import { SpacesManager } from '../../spaces/services/spaces-manager.service' diff --git a/backend/src/applications/files/services/files-tasks-manager.service.ts b/backend/src/applications/files/services/files-tasks-manager.service.ts index 3d93a0fc..6c5b9d80 100644 --- a/backend/src/applications/files/services/files-tasks-manager.service.ts +++ b/backend/src/applications/files/services/files-tasks-manager.service.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { HttpException, HttpStatus, Injectable, Logger, StreamableFile } from '@nestjs/common' import { FastifyReply } from 'fastify' import crypto from 'node:crypto' diff --git a/backend/src/applications/files/utils/doc-textify/adapters/excel.ts b/backend/src/applications/files/utils/doc-textify/adapters/excel.ts index 8e26887b..aeb0a89b 100644 --- a/backend/src/applications/files/utils/doc-textify/adapters/excel.ts +++ b/backend/src/applications/files/utils/doc-textify/adapters/excel.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { promisify } from 'node:util' import sax from 'sax' import { open as openZip, Options, ZipFile } from 'yauzl' diff --git a/backend/src/applications/files/utils/doc-textify/adapters/html.ts b/backend/src/applications/files/utils/doc-textify/adapters/html.ts index a9d787c0..df913dac 100644 --- a/backend/src/applications/files/utils/doc-textify/adapters/html.ts +++ b/backend/src/applications/files/utils/doc-textify/adapters/html.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import fs from 'fs/promises' import { compile } from 'html-to-text' diff --git a/backend/src/applications/files/utils/doc-textify/adapters/open-office.ts b/backend/src/applications/files/utils/doc-textify/adapters/open-office.ts index 056553ce..125e5823 100644 --- a/backend/src/applications/files/utils/doc-textify/adapters/open-office.ts +++ b/backend/src/applications/files/utils/doc-textify/adapters/open-office.ts @@ -1,8 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ import { promisify } from 'node:util' import sax from 'sax' import { Entry, open as openZip, Options, ZipFile } from 'yauzl' diff --git a/backend/src/applications/files/utils/doc-textify/adapters/pdf.ts b/backend/src/applications/files/utils/doc-textify/adapters/pdf.ts index a2d7196a..b97c960e 100644 --- a/backend/src/applications/files/utils/doc-textify/adapters/pdf.ts +++ b/backend/src/applications/files/utils/doc-textify/adapters/pdf.ts @@ -1,8 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ import { readFile } from 'node:fs/promises' import { getDocumentProxy } from 'unpdf' import type { PDFDocumentProxy } from 'unpdf/pdfjs' diff --git a/backend/src/applications/files/utils/doc-textify/adapters/power-point.ts b/backend/src/applications/files/utils/doc-textify/adapters/power-point.ts index 95c6a2b8..bdcde674 100644 --- a/backend/src/applications/files/utils/doc-textify/adapters/power-point.ts +++ b/backend/src/applications/files/utils/doc-textify/adapters/power-point.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { promisify } from 'node:util' import sax from 'sax' import { Entry, open as openZip, Options, ZipFile } from 'yauzl' diff --git a/backend/src/applications/files/utils/doc-textify/adapters/text.ts b/backend/src/applications/files/utils/doc-textify/adapters/text.ts index 3033d3ae..959cac14 100644 --- a/backend/src/applications/files/utils/doc-textify/adapters/text.ts +++ b/backend/src/applications/files/utils/doc-textify/adapters/text.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import fs from 'fs/promises' export async function parseText(filePath: string): Promise { diff --git a/backend/src/applications/files/utils/doc-textify/adapters/word.ts b/backend/src/applications/files/utils/doc-textify/adapters/word.ts index b93154b2..34fcfb92 100644 --- a/backend/src/applications/files/utils/doc-textify/adapters/word.ts +++ b/backend/src/applications/files/utils/doc-textify/adapters/word.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { promisify } from 'node:util' import sax from 'sax' import { Entry, open as openZip, Options, ZipFile } from 'yauzl' diff --git a/backend/src/applications/files/utils/doc-textify/doc-textify.ts b/backend/src/applications/files/utils/doc-textify/doc-textify.ts index 314d38b0..473c7ae5 100644 --- a/backend/src/applications/files/utils/doc-textify/doc-textify.ts +++ b/backend/src/applications/files/utils/doc-textify/doc-textify.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import fs from 'node:fs/promises' import path from 'node:path' import { parseExcel } from './adapters/excel' diff --git a/backend/src/applications/files/utils/doc-textify/interfaces/doc-textify.interfaces.ts b/backend/src/applications/files/utils/doc-textify/interfaces/doc-textify.interfaces.ts index d3560f91..9a298f83 100644 --- a/backend/src/applications/files/utils/doc-textify/interfaces/doc-textify.interfaces.ts +++ b/backend/src/applications/files/utils/doc-textify/interfaces/doc-textify.interfaces.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - export interface DocTextifyOptions { outputErrorToConsole?: boolean newlineDelimiter: string diff --git a/backend/src/applications/files/utils/doc-textify/utils/clean.ts b/backend/src/applications/files/utils/doc-textify/utils/clean.ts index 2a3f24da..0fdf4523 100644 --- a/backend/src/applications/files/utils/doc-textify/utils/clean.ts +++ b/backend/src/applications/files/utils/doc-textify/utils/clean.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { DocTextifyOptions } from '../interfaces/doc-textify.interfaces' const regexAlphanumeric = /[a-zA-Z0-9]/ diff --git a/backend/src/applications/files/utils/files-search.ts b/backend/src/applications/files/utils/files-search.ts index 6833dc83..b9dab0ee 100644 --- a/backend/src/applications/files/utils/files-search.ts +++ b/backend/src/applications/files/utils/files-search.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { escapeString } from '../../../common/functions' import { minCharsToSearch } from '../constants/indexing' diff --git a/backend/src/applications/files/utils/files-tree.ts b/backend/src/applications/files/utils/files-tree.ts index 501a3f1f..8e306afe 100644 --- a/backend/src/applications/files/utils/files-tree.ts +++ b/backend/src/applications/files/utils/files-tree.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import path from 'node:path' import { sortObjByName } from '../../../common/functions' import { SHARE_ALL_OPERATIONS } from '../../shares/constants/shares' diff --git a/backend/src/applications/files/utils/files.ts b/backend/src/applications/files/utils/files.ts index 8f478138..1c832389 100644 --- a/backend/src/applications/files/utils/files.ts +++ b/backend/src/applications/files/utils/files.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { HttpStatus } from '@nestjs/common' import { WriteStream } from 'fs' import fse from 'fs-extra' diff --git a/backend/src/applications/files/utils/send-file.ts b/backend/src/applications/files/utils/send-file.ts index 3e4a095f..2776fc65 100644 --- a/backend/src/applications/files/utils/send-file.ts +++ b/backend/src/applications/files/utils/send-file.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { send, SendOptions, SendResult } from '@fastify/send' import { HttpStatus, StreamableFile } from '@nestjs/common' import { FastifyReply, FastifyRequest } from 'fastify' diff --git a/backend/src/applications/files/utils/unzip-file.ts b/backend/src/applications/files/utils/unzip-file.ts index 33da35a8..6fe65e63 100644 --- a/backend/src/applications/files/utils/unzip-file.ts +++ b/backend/src/applications/files/utils/unzip-file.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import fs from 'node:fs' import path from 'node:path' import { promisify } from 'node:util' diff --git a/backend/src/applications/files/utils/url-file.ts b/backend/src/applications/files/utils/url-file.ts index f4fe8569..f613a8f2 100644 --- a/backend/src/applications/files/utils/url-file.ts +++ b/backend/src/applications/files/utils/url-file.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - const parts = [ // IPv4 loopback (127.0.0.0/8) '127\\.(?:\\d{1,3}\\.){2}\\d{1,3}', diff --git a/backend/src/applications/links/constants/cache.ts b/backend/src/applications/links/constants/cache.ts index 4b4dd192..f4d1cd2b 100644 --- a/backend/src/applications/links/constants/cache.ts +++ b/backend/src/applications/links/constants/cache.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - // cache link key = `link-uuid-${userId}-${uuid}` => ${uuid} export const CACHE_LINK_UUID_PREFIX = 'link-uuid' export const CACHE_LINK_UUID_TTL = 900 // 15 minutes diff --git a/backend/src/applications/links/constants/links.ts b/backend/src/applications/links/constants/links.ts index 955e14cd..2a797114 100644 --- a/backend/src/applications/links/constants/links.ts +++ b/backend/src/applications/links/constants/links.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - export const LINK_ERROR = { UNAUTHORIZED: 'unauthorized', DISABLED: 'disabled', diff --git a/backend/src/applications/links/constants/routes.ts b/backend/src/applications/links/constants/routes.ts index f71c624d..26238ccb 100644 --- a/backend/src/applications/links/constants/routes.ts +++ b/backend/src/applications/links/constants/routes.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { APP_BASE_ROUTE } from '../../applications.constants' export const PUBLIC_LINKS_ROUTE = { diff --git a/backend/src/applications/links/dto/create-or-update-link.dto.ts b/backend/src/applications/links/dto/create-or-update-link.dto.ts index e99a6a03..84fc5b95 100644 --- a/backend/src/applications/links/dto/create-or-update-link.dto.ts +++ b/backend/src/applications/links/dto/create-or-update-link.dto.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Transform } from 'class-transformer' import { IsBoolean, IsDate, IsOptional, IsString, MinLength } from 'class-validator' import { currentDate } from '../../../common/shared' diff --git a/backend/src/applications/links/interfaces/link-guest.interface.ts b/backend/src/applications/links/interfaces/link-guest.interface.ts index 147a8871..4d888c63 100644 --- a/backend/src/applications/links/interfaces/link-guest.interface.ts +++ b/backend/src/applications/links/interfaces/link-guest.interface.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import type { ShareMembers } from '../../shares/schemas/share-members.interface' import type { User } from '../../users/schemas/user.interface' import type { Link } from '../schemas/link.interface' diff --git a/backend/src/applications/links/interfaces/link-space.interface.ts b/backend/src/applications/links/interfaces/link-space.interface.ts index bd904bd6..aa361100 100644 --- a/backend/src/applications/links/interfaces/link-space.interface.ts +++ b/backend/src/applications/links/interfaces/link-space.interface.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import type { FileEditorProvider } from '../../../configuration/config.interfaces' export interface SpaceLink { diff --git a/backend/src/applications/links/links.controller.spec.ts b/backend/src/applications/links/links.controller.spec.ts index 02bf85d3..3ecf0f1f 100644 --- a/backend/src/applications/links/links.controller.spec.ts +++ b/backend/src/applications/links/links.controller.spec.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { StreamableFile } from '@nestjs/common' import { Test, TestingModule } from '@nestjs/testing' import { LinksController } from './links.controller' diff --git a/backend/src/applications/links/links.controller.ts b/backend/src/applications/links/links.controller.ts index 945ced35..06ed9d1f 100644 --- a/backend/src/applications/links/links.controller.ts +++ b/backend/src/applications/links/links.controller.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Body, Controller, Get, Param, Post, Req, Res, StreamableFile } from '@nestjs/common' import { FastifyReply, FastifyRequest } from 'fastify' import { AuthTokenOptional } from '../../authentication/decorators/auth-token-optional.decorator' diff --git a/backend/src/applications/links/schemas/link.interface.ts b/backend/src/applications/links/schemas/link.interface.ts index 455d43ea..9344353b 100644 --- a/backend/src/applications/links/schemas/link.interface.ts +++ b/backend/src/applications/links/schemas/link.interface.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import type { links } from './links.schema' type LinkSchema = typeof links.$inferSelect diff --git a/backend/src/applications/links/schemas/links.schema.ts b/backend/src/applications/links/schemas/links.schema.ts index 433eb24e..536f9746 100644 --- a/backend/src/applications/links/schemas/links.schema.ts +++ b/backend/src/applications/links/schemas/links.schema.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { sql } from 'drizzle-orm' import { bigint, boolean, date, datetime, index, int, mysqlTable, uniqueIndex, varchar } from 'drizzle-orm/mysql-core' import { users } from '../../users/schemas/users.schema' diff --git a/backend/src/applications/links/services/links-manager.service.spec.ts b/backend/src/applications/links/services/links-manager.service.spec.ts index 3c824c3b..32c3cbc5 100644 --- a/backend/src/applications/links/services/links-manager.service.spec.ts +++ b/backend/src/applications/links/services/links-manager.service.spec.ts @@ -1,14 +1,8 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { HttpService } from '@nestjs/axios' import { ConfigService } from '@nestjs/config' import { JwtService } from '@nestjs/jwt' import { Test, TestingModule } from '@nestjs/testing' -import { AuthManager } from '../../../authentication/services/auth-manager.service' +import { AuthManager } from '../../../authentication/auth.service' import { Cache } from '../../../infrastructure/cache/services/cache.service' import { ContextManager } from '../../../infrastructure/context/services/context-manager.service' import { DB_TOKEN_PROVIDER } from '../../../infrastructure/database/constants' diff --git a/backend/src/applications/links/services/links-manager.service.ts b/backend/src/applications/links/services/links-manager.service.ts index 30092543..e9fe29f9 100644 --- a/backend/src/applications/links/services/links-manager.service.ts +++ b/backend/src/applications/links/services/links-manager.service.ts @@ -1,14 +1,8 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { HttpException, HttpStatus, Injectable, Logger, StreamableFile } from '@nestjs/common' import { FastifyReply, FastifyRequest } from 'fastify' +import { AuthManager } from '../../../authentication/auth.service' import { LoginResponseDto } from '../../../authentication/dto/login-response.dto' import { JwtIdentityPayload } from '../../../authentication/interfaces/jwt-payload.interface' -import { AuthManager } from '../../../authentication/services/auth-manager.service' import { serverConfig } from '../../../configuration/config.environment' import { FilesManager } from '../../files/services/files-manager.service' import { SendFile } from '../../files/utils/send-file' diff --git a/backend/src/applications/links/services/links-queries.service.ts b/backend/src/applications/links/services/links-queries.service.ts index 103a33ba..2a91f683 100644 --- a/backend/src/applications/links/services/links-queries.service.ts +++ b/backend/src/applications/links/services/links-queries.service.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Inject, Injectable } from '@nestjs/common' import { and, eq, getTableColumns, isNotNull, isNull, or, sql } from 'drizzle-orm' import { alias } from 'drizzle-orm/mysql-core' diff --git a/backend/src/applications/notifications/constants/notifications.ts b/backend/src/applications/notifications/constants/notifications.ts index e79dae27..bb1eb39d 100644 --- a/backend/src/applications/notifications/constants/notifications.ts +++ b/backend/src/applications/notifications/constants/notifications.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { ACTION } from '../../../common/constants' export enum NOTIFICATION_APP { diff --git a/backend/src/applications/notifications/constants/routes.ts b/backend/src/applications/notifications/constants/routes.ts index 81dd9fa2..3ee2218e 100644 --- a/backend/src/applications/notifications/constants/routes.ts +++ b/backend/src/applications/notifications/constants/routes.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { APP_BASE_ROUTE } from '../../applications.constants' export const NOTIFICATIONS_ROUTE = { diff --git a/backend/src/applications/notifications/constants/websocket.ts b/backend/src/applications/notifications/constants/websocket.ts index 6dacfae1..7af3596a 100644 --- a/backend/src/applications/notifications/constants/websocket.ts +++ b/backend/src/applications/notifications/constants/websocket.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - export const NOTIFICATIONS_WS = { NAME_SPACE: '/', EVENTS: { NOTIFICATION: 'notification' } diff --git a/backend/src/applications/notifications/i18n/de.ts b/backend/src/applications/notifications/i18n/de.ts index 76e4f012..d75be0ef 100644 --- a/backend/src/applications/notifications/i18n/de.ts +++ b/backend/src/applications/notifications/i18n/de.ts @@ -1,14 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - export const de = { 'If you no longer wish to receive notifications, change your preferences directly from your user space.': 'Wenn Sie keine Benachrichtigungen mehr erhalten möchten, ändern Sie Ihre Einstellungen direkt in Ihrem Benutzerbereich.', diff --git a/backend/src/applications/notifications/i18n/es.ts b/backend/src/applications/notifications/i18n/es.ts index d174ded6..33ea5315 100644 --- a/backend/src/applications/notifications/i18n/es.ts +++ b/backend/src/applications/notifications/i18n/es.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - export const es = { 'If you no longer wish to receive notifications, change your preferences directly from your user space.': 'Si ya no desea recibir notificaciones, cambie sus preferencias directamente desde su espacio de usuario.', diff --git a/backend/src/applications/notifications/i18n/fr.ts b/backend/src/applications/notifications/i18n/fr.ts index f5bb0f3a..2e17e384 100644 --- a/backend/src/applications/notifications/i18n/fr.ts +++ b/backend/src/applications/notifications/i18n/fr.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - export const fr = { 'If you no longer wish to receive notifications, change your preferences directly from your user space.': 'Si vous ne souhaitez plus recevoir de notifications, modifiez vos préferences directement depuis votre espace utilisateur.', diff --git a/backend/src/applications/notifications/i18n/hi.ts b/backend/src/applications/notifications/i18n/hi.ts index 0c12d16f..9162538d 100644 --- a/backend/src/applications/notifications/i18n/hi.ts +++ b/backend/src/applications/notifications/i18n/hi.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - export const hi = { 'If you no longer wish to receive notifications, change your preferences directly from your user space.': 'यदि आप अब सूचनाएँ प्राप्त नहीं करना चाहते हैं, तो अपने उपयोगकर्ता क्षेत्र से सीधे अपनी प्राथमिकताएँ बदलें।', diff --git a/backend/src/applications/notifications/i18n/index.ts b/backend/src/applications/notifications/i18n/index.ts index 7b6e728b..841ab442 100644 --- a/backend/src/applications/notifications/i18n/index.ts +++ b/backend/src/applications/notifications/i18n/index.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { i18nLocale } from '../../../common/i18n' import { de } from './de' import { es } from './es' diff --git a/backend/src/applications/notifications/i18n/it.ts b/backend/src/applications/notifications/i18n/it.ts index 229b2298..740eb587 100644 --- a/backend/src/applications/notifications/i18n/it.ts +++ b/backend/src/applications/notifications/i18n/it.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - export const it = { 'If you no longer wish to receive notifications, change your preferences directly from your user space.': 'Se non desideri più ricevere notifiche, modifica le tue preferenze direttamente dalla tua area utente.', diff --git a/backend/src/applications/notifications/i18n/ja.ts b/backend/src/applications/notifications/i18n/ja.ts index 2ff0ba78..32f8a636 100644 --- a/backend/src/applications/notifications/i18n/ja.ts +++ b/backend/src/applications/notifications/i18n/ja.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - export const ja = { 'If you no longer wish to receive notifications, change your preferences directly from your user space.': '通知の受信を希望しない場合は、ユーザー用スペースから直接設定を変更してください。', diff --git a/backend/src/applications/notifications/i18n/ko.ts b/backend/src/applications/notifications/i18n/ko.ts index 2ead493a..b45a23b2 100644 --- a/backend/src/applications/notifications/i18n/ko.ts +++ b/backend/src/applications/notifications/i18n/ko.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - export const ko = { 'If you no longer wish to receive notifications, change your preferences directly from your user space.': '더 이상 알림을 받고 싶지 않다면, 사용자 공간에서 바로 환경설정을 변경하세요.', diff --git a/backend/src/applications/notifications/i18n/pl.ts b/backend/src/applications/notifications/i18n/pl.ts index ff658fdd..125f482c 100644 --- a/backend/src/applications/notifications/i18n/pl.ts +++ b/backend/src/applications/notifications/i18n/pl.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - export const pl = { 'If you no longer wish to receive notifications, change your preferences directly from your user space.': 'Jeśli nie chcesz już otrzymywać powiadomień, zmień swoje preferencje bezpośrednio w przestrzeni użytkownika.', diff --git a/backend/src/applications/notifications/i18n/pt.ts b/backend/src/applications/notifications/i18n/pt.ts index 4347e9fe..8e51f73a 100644 --- a/backend/src/applications/notifications/i18n/pt.ts +++ b/backend/src/applications/notifications/i18n/pt.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - export const pt = { 'If you no longer wish to receive notifications, change your preferences directly from your user space.': 'Se já não deseja receber notificações, altere as suas preferências diretamente no seu espaço de utilizador.', diff --git a/backend/src/applications/notifications/i18n/pt_br.ts b/backend/src/applications/notifications/i18n/pt_br.ts index 63f8b774..6f3b66fd 100644 --- a/backend/src/applications/notifications/i18n/pt_br.ts +++ b/backend/src/applications/notifications/i18n/pt_br.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - export const pt_BR = { 'If you no longer wish to receive notifications, change your preferences directly from your user space.': 'Se você não deseja mais receber notificações, altere suas preferências diretamente no seu espaço do usuário.', diff --git a/backend/src/applications/notifications/i18n/ru.ts b/backend/src/applications/notifications/i18n/ru.ts index f311c7a9..2eaf6bd5 100644 --- a/backend/src/applications/notifications/i18n/ru.ts +++ b/backend/src/applications/notifications/i18n/ru.ts @@ -1,8 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ export const ru = { 'If you no longer wish to receive notifications, change your preferences directly from your user space.': 'Если вы больше не хотите получать уведомления, измените настройки прямо в вашем пользовательском пространстве.', diff --git a/backend/src/applications/notifications/i18n/tr.ts b/backend/src/applications/notifications/i18n/tr.ts index fe8fb6d7..97a36209 100644 --- a/backend/src/applications/notifications/i18n/tr.ts +++ b/backend/src/applications/notifications/i18n/tr.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - export const tr = { 'If you no longer wish to receive notifications, change your preferences directly from your user space.': 'Artık bildirim almak istemiyorsanız, kullanıcı alanınızdan doğrudan tercihlerinizi değiştirin.', diff --git a/backend/src/applications/notifications/i18n/zh.ts b/backend/src/applications/notifications/i18n/zh.ts index 3f0b5b5e..15cc8594 100644 --- a/backend/src/applications/notifications/i18n/zh.ts +++ b/backend/src/applications/notifications/i18n/zh.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - export const zh = { 'If you no longer wish to receive notifications, change your preferences directly from your user space.': '如果您不再希望接收通知,请在您的用户空间中直接更改偏好设置。', diff --git a/backend/src/applications/notifications/interfaces/notification-properties.interface.ts b/backend/src/applications/notifications/interfaces/notification-properties.interface.ts index 9d29bb66..9194303e 100644 --- a/backend/src/applications/notifications/interfaces/notification-properties.interface.ts +++ b/backend/src/applications/notifications/interfaces/notification-properties.interface.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import type { ACTION } from '../../../common/constants' import type { Owner } from '../../users/interfaces/owner.interface' import type { UserModel } from '../../users/models/user.model' diff --git a/backend/src/applications/notifications/interfaces/user-mail-notification.interface.ts b/backend/src/applications/notifications/interfaces/user-mail-notification.interface.ts index 499cb556..5b645b5f 100644 --- a/backend/src/applications/notifications/interfaces/user-mail-notification.interface.ts +++ b/backend/src/applications/notifications/interfaces/user-mail-notification.interface.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { USER_NOTIFICATION } from '../../users/constants/user' export interface UserMailNotification { diff --git a/backend/src/applications/notifications/mails/models.ts b/backend/src/applications/notifications/mails/models.ts index cc8c4a8a..0480522a 100644 --- a/backend/src/applications/notifications/mails/models.ts +++ b/backend/src/applications/notifications/mails/models.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { ACTION } from '../../../common/constants' import { i18nLocale } from '../../../common/i18n' import { capitalizeString, SERVER_NAME } from '../../../common/shared' diff --git a/backend/src/applications/notifications/mails/templates.ts b/backend/src/applications/notifications/mails/templates.ts index f78c9691..bb38a408 100644 --- a/backend/src/applications/notifications/mails/templates.ts +++ b/backend/src/applications/notifications/mails/templates.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { capitalizeString } from '../../../common/shared' import { UserModel } from '../../users/models/user.model' diff --git a/backend/src/applications/notifications/mails/urls.ts b/backend/src/applications/notifications/mails/urls.ts index e97b932e..25a5da92 100644 --- a/backend/src/applications/notifications/mails/urls.ts +++ b/backend/src/applications/notifications/mails/urls.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { PUBLIC_LINKS_ROUTE } from '../../links/constants/routes' import { SPACES_BASE_ROUTE } from '../../spaces/constants/routes' import { SYNC_BASE_ROUTE } from '../../sync/constants/routes' diff --git a/backend/src/applications/notifications/notifications.controller.spec.ts b/backend/src/applications/notifications/notifications.controller.spec.ts index c52c63fa..ab0780ca 100644 --- a/backend/src/applications/notifications/notifications.controller.spec.ts +++ b/backend/src/applications/notifications/notifications.controller.spec.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Test, TestingModule } from '@nestjs/testing' import { NotificationsController } from './notifications.controller' import { NotificationsManager } from './services/notifications-manager.service' diff --git a/backend/src/applications/notifications/notifications.controller.ts b/backend/src/applications/notifications/notifications.controller.ts index 6029e57b..7a8565a9 100644 --- a/backend/src/applications/notifications/notifications.controller.ts +++ b/backend/src/applications/notifications/notifications.controller.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Controller, Delete, Get, Param, ParseIntPipe, Patch } from '@nestjs/common' import { GetUser } from '../users/decorators/user.decorator' import type { UserModel } from '../users/models/user.model' diff --git a/backend/src/applications/notifications/notifications.gateway.ts b/backend/src/applications/notifications/notifications.gateway.ts index 6b5681d8..c3a748cc 100644 --- a/backend/src/applications/notifications/notifications.gateway.ts +++ b/backend/src/applications/notifications/notifications.gateway.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { WebSocketGateway, WebSocketServer } from '@nestjs/websockets' import { Server } from 'socket.io' import { USER_ROOM_PREFIX } from '../users/constants/websocket' diff --git a/backend/src/applications/notifications/notifications.module.ts b/backend/src/applications/notifications/notifications.module.ts index bddf1b3d..b971999e 100644 --- a/backend/src/applications/notifications/notifications.module.ts +++ b/backend/src/applications/notifications/notifications.module.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Module } from '@nestjs/common' import { NotificationsController } from './notifications.controller' import { WebSocketNotifications } from './notifications.gateway' diff --git a/backend/src/applications/notifications/schemas/notification.interface.ts b/backend/src/applications/notifications/schemas/notification.interface.ts index ce53b641..4f496263 100644 --- a/backend/src/applications/notifications/schemas/notification.interface.ts +++ b/backend/src/applications/notifications/schemas/notification.interface.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { NotificationContent } from '../interfaces/notification-properties.interface' import { notifications } from './notifications.schema' diff --git a/backend/src/applications/notifications/schemas/notifications.schema.ts b/backend/src/applications/notifications/schemas/notifications.schema.ts index f9e4cd52..a3787aa1 100644 --- a/backend/src/applications/notifications/schemas/notifications.schema.ts +++ b/backend/src/applications/notifications/schemas/notifications.schema.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { sql } from 'drizzle-orm' import { bigint, boolean, datetime, index, mysqlTable } from 'drizzle-orm/mysql-core' import { jsonColumn } from '../../../infrastructure/database/columns' diff --git a/backend/src/applications/notifications/services/notifications-manager.service.spec.ts b/backend/src/applications/notifications/services/notifications-manager.service.spec.ts index d253cda5..6a7ae101 100644 --- a/backend/src/applications/notifications/services/notifications-manager.service.spec.ts +++ b/backend/src/applications/notifications/services/notifications-manager.service.spec.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Test, TestingModule } from '@nestjs/testing' import { Mailer } from '../../../infrastructure/mailer/mailer.service' import { USER_NOTIFICATION } from '../../users/constants/user' diff --git a/backend/src/applications/notifications/services/notifications-manager.service.ts b/backend/src/applications/notifications/services/notifications-manager.service.ts index 638e5728..2b3d1369 100644 --- a/backend/src/applications/notifications/services/notifications-manager.service.ts +++ b/backend/src/applications/notifications/services/notifications-manager.service.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Injectable, Logger } from '@nestjs/common' import { i18nLocale } from '../../../common/i18n' import { MailProps } from '../../../infrastructure/mailer/interfaces/mail.interface' diff --git a/backend/src/applications/notifications/services/notifications-queries.service.ts b/backend/src/applications/notifications/services/notifications-queries.service.ts index 7b97991b..82de6db8 100644 --- a/backend/src/applications/notifications/services/notifications-queries.service.ts +++ b/backend/src/applications/notifications/services/notifications-queries.service.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Inject, Injectable } from '@nestjs/common' import { and, desc, eq, inArray, SelectedFields, SQL } from 'drizzle-orm' import { DB_TOKEN_PROVIDER } from '../../../infrastructure/database/constants' diff --git a/backend/src/applications/shares/constants/routes.ts b/backend/src/applications/shares/constants/routes.ts index 81bee5fe..6de4e10e 100644 --- a/backend/src/applications/shares/constants/routes.ts +++ b/backend/src/applications/shares/constants/routes.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { APP_BASE_ROUTE } from '../../applications.constants' export const SHARES_ROUTE = { diff --git a/backend/src/applications/shares/constants/shares.ts b/backend/src/applications/shares/constants/shares.ts index faa4c8a6..bfd38bde 100644 --- a/backend/src/applications/shares/constants/shares.ts +++ b/backend/src/applications/shares/constants/shares.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { SPACE_OPERATION, SPACE_PERMS_SEP } from '../../spaces/constants/spaces' export enum SHARE_TYPE { diff --git a/backend/src/applications/shares/dto/create-or-update-share.dto.ts b/backend/src/applications/shares/dto/create-or-update-share.dto.ts index 5914a24d..8ffb5479 100644 --- a/backend/src/applications/shares/dto/create-or-update-share.dto.ts +++ b/backend/src/applications/shares/dto/create-or-update-share.dto.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Transform, Type } from 'class-transformer' import { IsArray, IsBoolean, IsEnum, IsInt, IsNotEmpty, IsOptional, IsString, ValidateIf, ValidateNested } from 'class-validator' import { FileSpace } from '../../files/interfaces/file-space.interface' diff --git a/backend/src/applications/shares/interfaces/share-child.interface.ts b/backend/src/applications/shares/interfaces/share-child.interface.ts index cb2ad03f..a2338964 100644 --- a/backend/src/applications/shares/interfaces/share-child.interface.ts +++ b/backend/src/applications/shares/interfaces/share-child.interface.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import type { Share } from '../schemas/share.interface' export interface ShareChildQuery extends Pick { diff --git a/backend/src/applications/shares/interfaces/share-env.interface.ts b/backend/src/applications/shares/interfaces/share-env.interface.ts index 7980f113..b8352c69 100644 --- a/backend/src/applications/shares/interfaces/share-env.interface.ts +++ b/backend/src/applications/shares/interfaces/share-env.interface.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { SpaceEnv } from '../../spaces/models/space-env.model' export interface ShareEnv extends Partial { diff --git a/backend/src/applications/shares/interfaces/share-file.interface.ts b/backend/src/applications/shares/interfaces/share-file.interface.ts index 518611f7..c66e6427 100644 --- a/backend/src/applications/shares/interfaces/share-file.interface.ts +++ b/backend/src/applications/shares/interfaces/share-file.interface.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import type { FileSpace } from '../../files/interfaces/file-space.interface' import type { Share } from '../schemas/share.interface' diff --git a/backend/src/applications/shares/interfaces/share-link.interface.ts b/backend/src/applications/shares/interfaces/share-link.interface.ts index 5d63fd6c..c3541896 100644 --- a/backend/src/applications/shares/interfaces/share-link.interface.ts +++ b/backend/src/applications/shares/interfaces/share-link.interface.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import type { FileSpace } from '../../files/interfaces/file-space.interface' import type { LinkGuest } from '../../links/interfaces/link-guest.interface' import type { Share } from '../schemas/share.interface' diff --git a/backend/src/applications/shares/interfaces/share-props.interface.ts b/backend/src/applications/shares/interfaces/share-props.interface.ts index 950a4f4f..819b839f 100644 --- a/backend/src/applications/shares/interfaces/share-props.interface.ts +++ b/backend/src/applications/shares/interfaces/share-props.interface.ts @@ -1,8 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ import type { FileSpace } from '../../files/interfaces/file-space.interface' import type { Member } from '../../users/interfaces/member.interface' import type { Share } from '../schemas/share.interface' diff --git a/backend/src/applications/shares/models/share-child.model.ts b/backend/src/applications/shares/models/share-child.model.ts index 2cdbb43e..9a98e8b7 100644 --- a/backend/src/applications/shares/models/share-child.model.ts +++ b/backend/src/applications/shares/models/share-child.model.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { popFromObject } from '../../../common/shared' import type { Owner } from '../../users/interfaces/owner.interface' import type { ShareChildQuery } from '../interfaces/share-child.interface' diff --git a/backend/src/applications/shares/schemas/share-members.interface.ts b/backend/src/applications/shares/schemas/share-members.interface.ts index 00114f52..3fad0124 100644 --- a/backend/src/applications/shares/schemas/share-members.interface.ts +++ b/backend/src/applications/shares/schemas/share-members.interface.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import type { sharesMembers } from './shares-members.schema' type ShareMembersSchema = typeof sharesMembers.$inferSelect diff --git a/backend/src/applications/shares/schemas/share.interface.ts b/backend/src/applications/shares/schemas/share.interface.ts index 7b94d3e9..f831900e 100644 --- a/backend/src/applications/shares/schemas/share.interface.ts +++ b/backend/src/applications/shares/schemas/share.interface.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import type { shares } from './shares.schema' type ShareSchema = typeof shares.$inferSelect diff --git a/backend/src/applications/shares/schemas/shares-members.schema.ts b/backend/src/applications/shares/schemas/shares-members.schema.ts index df4681e1..29aa9038 100644 --- a/backend/src/applications/shares/schemas/shares-members.schema.ts +++ b/backend/src/applications/shares/schemas/shares-members.schema.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { sql } from 'drizzle-orm' import { bigint, datetime, index, mysqlTable, unique, varchar } from 'drizzle-orm/mysql-core' import { links } from '../../links/schemas/links.schema' diff --git a/backend/src/applications/shares/schemas/shares.schema.ts b/backend/src/applications/shares/schemas/shares.schema.ts index c4ded410..69827eb4 100644 --- a/backend/src/applications/shares/schemas/shares.schema.ts +++ b/backend/src/applications/shares/schemas/shares.schema.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { sql } from 'drizzle-orm' import { AnyMySqlColumn, bigint, boolean, datetime, index, mysqlTable, tinyint, uniqueIndex, varchar } from 'drizzle-orm/mysql-core' import { files } from '../../files/schemas/files.schema' diff --git a/backend/src/applications/shares/services/shares-manager.service.spec.ts b/backend/src/applications/shares/services/shares-manager.service.spec.ts index a7a974fe..3686b8ad 100644 --- a/backend/src/applications/shares/services/shares-manager.service.spec.ts +++ b/backend/src/applications/shares/services/shares-manager.service.spec.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { HttpException, HttpStatus } from '@nestjs/common' import { Test, TestingModule } from '@nestjs/testing' import * as commonFunctions from '../../../common/functions' diff --git a/backend/src/applications/shares/services/shares-manager.service.ts b/backend/src/applications/shares/services/shares-manager.service.ts index 9d8f85f0..f237c103 100644 --- a/backend/src/applications/shares/services/shares-manager.service.ts +++ b/backend/src/applications/shares/services/shares-manager.service.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { HttpException, HttpStatus, Injectable, Logger } from '@nestjs/common' import path from 'node:path' import { ACTION } from '../../../common/constants' diff --git a/backend/src/applications/shares/services/shares-queries.service.ts b/backend/src/applications/shares/services/shares-queries.service.ts index 3a3c127a..501c706b 100644 --- a/backend/src/applications/shares/services/shares-queries.service.ts +++ b/backend/src/applications/shares/services/shares-queries.service.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Inject, Injectable, Logger } from '@nestjs/common' import { and, count, eq, inArray, isNotNull, isNull, ne, or, SelectedFields, SQL, sql } from 'drizzle-orm' import { alias, MySqlSelectDynamic, union } from 'drizzle-orm/mysql-core' diff --git a/backend/src/applications/shares/shares.controller.spec.ts b/backend/src/applications/shares/shares.controller.spec.ts index 44ec04b1..45aa0011 100644 --- a/backend/src/applications/shares/shares.controller.spec.ts +++ b/backend/src/applications/shares/shares.controller.spec.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Test, TestingModule } from '@nestjs/testing' import { Cache } from '../../infrastructure/cache/services/cache.service' import { ContextManager } from '../../infrastructure/context/services/context-manager.service' diff --git a/backend/src/applications/shares/shares.controller.ts b/backend/src/applications/shares/shares.controller.ts index 64d7e63c..fde363e1 100644 --- a/backend/src/applications/shares/shares.controller.ts +++ b/backend/src/applications/shares/shares.controller.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Body, Controller, Delete, Get, Param, ParseIntPipe, Post, Put, UseGuards, UseInterceptors } from '@nestjs/common' import { ContextInterceptor } from '../../infrastructure/context/interceptors/context.interceptor' import { LINK_TYPE } from '../links/constants/links' diff --git a/backend/src/applications/shares/shares.module.ts b/backend/src/applications/shares/shares.module.ts index 24a689aa..5021dd95 100644 --- a/backend/src/applications/shares/shares.module.ts +++ b/backend/src/applications/shares/shares.module.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Module } from '@nestjs/common' import { LinksController } from '../links/links.controller' import { LinksManager } from '../links/services/links-manager.service' diff --git a/backend/src/applications/spaces/constants/cache.ts b/backend/src/applications/spaces/constants/cache.ts index 02eaf6c3..bf57bff2 100644 --- a/backend/src/applications/spaces/constants/cache.ts +++ b/backend/src/applications/spaces/constants/cache.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - // cache quota key = `(quota-user|quota-space)-${id}` => number export const CACHE_QUOTA_USER_PREFIX = 'quota-user' export const CACHE_QUOTA_SPACE_PREFIX = 'quota-space' diff --git a/backend/src/applications/spaces/constants/routes.ts b/backend/src/applications/spaces/constants/routes.ts index fb6b31ea..9cb21f7c 100644 --- a/backend/src/applications/spaces/constants/routes.ts +++ b/backend/src/applications/spaces/constants/routes.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { APP_BASE_ROUTE } from '../../applications.constants' import { SPACE_REPOSITORY } from './spaces' diff --git a/backend/src/applications/spaces/constants/spaces.ts b/backend/src/applications/spaces/constants/spaces.ts index d3bc3684..917bef06 100644 --- a/backend/src/applications/spaces/constants/spaces.ts +++ b/backend/src/applications/spaces/constants/spaces.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - export const SPACE_MAX_DISABLED_DAYS = 30 //days export const SPACE_PERMS_SEP = ':' diff --git a/backend/src/applications/spaces/decorators/space-override-permission.decorator.ts b/backend/src/applications/spaces/decorators/space-override-permission.decorator.ts index bd97b3b4..63326594 100644 --- a/backend/src/applications/spaces/decorators/space-override-permission.decorator.ts +++ b/backend/src/applications/spaces/decorators/space-override-permission.decorator.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Reflector } from '@nestjs/core' import { SPACE_OPERATION } from '../constants/spaces' diff --git a/backend/src/applications/spaces/decorators/space-skip-guard.decorator.ts b/backend/src/applications/spaces/decorators/space-skip-guard.decorator.ts index d1103902..028ee461 100644 --- a/backend/src/applications/spaces/decorators/space-skip-guard.decorator.ts +++ b/backend/src/applications/spaces/decorators/space-skip-guard.decorator.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { SetMetadata } from '@nestjs/common' export const SKIP_SPACE_GUARD = 'skipSpaceGuard' diff --git a/backend/src/applications/spaces/decorators/space-skip-permissions.decorator.ts b/backend/src/applications/spaces/decorators/space-skip-permissions.decorator.ts index 76a73096..edf32dae 100644 --- a/backend/src/applications/spaces/decorators/space-skip-permissions.decorator.ts +++ b/backend/src/applications/spaces/decorators/space-skip-permissions.decorator.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { SetMetadata } from '@nestjs/common' export const SKIP_SPACE_PERMISSIONS_CHECK = 'skipSpacePermissionsCheck' diff --git a/backend/src/applications/spaces/decorators/space.decorator.ts b/backend/src/applications/spaces/decorators/space.decorator.ts index bfd2088b..f235ce29 100644 --- a/backend/src/applications/spaces/decorators/space.decorator.ts +++ b/backend/src/applications/spaces/decorators/space.decorator.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { createParamDecorator, ExecutionContext } from '@nestjs/common' import { SpaceEnv } from '../models/space-env.model' diff --git a/backend/src/applications/spaces/dto/create-or-update-space.dto.ts b/backend/src/applications/spaces/dto/create-or-update-space.dto.ts index be2e0333..2db50e71 100644 --- a/backend/src/applications/spaces/dto/create-or-update-space.dto.ts +++ b/backend/src/applications/spaces/dto/create-or-update-space.dto.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Transform, Type } from 'class-transformer' import { ArrayMinSize, IsArray, IsBoolean, IsInt, IsNotEmpty, IsOptional, IsString, ValidateNested } from 'class-validator' import { sanitizeName } from '../../files/utils/files' diff --git a/backend/src/applications/spaces/dto/delete-space.dto.ts b/backend/src/applications/spaces/dto/delete-space.dto.ts index 577f3e04..4c7d8114 100644 --- a/backend/src/applications/spaces/dto/delete-space.dto.ts +++ b/backend/src/applications/spaces/dto/delete-space.dto.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { IsBoolean, IsOptional } from 'class-validator' export class DeleteSpaceDto { diff --git a/backend/src/applications/spaces/dto/search-space.dto.ts b/backend/src/applications/spaces/dto/search-space.dto.ts index 5baff8e2..96634f79 100644 --- a/backend/src/applications/spaces/dto/search-space.dto.ts +++ b/backend/src/applications/spaces/dto/search-space.dto.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Transform } from 'class-transformer' import { IsBoolean, IsInt, IsOptional, IsString } from 'class-validator' diff --git a/backend/src/applications/spaces/dto/space-roots.dto.ts b/backend/src/applications/spaces/dto/space-roots.dto.ts index 9ba6b1c9..e7b6a9d4 100644 --- a/backend/src/applications/spaces/dto/space-roots.dto.ts +++ b/backend/src/applications/spaces/dto/space-roots.dto.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Transform, Type } from 'class-transformer' import { IsDefined, IsInt, IsNotEmpty, IsNotEmptyObject, IsObject, IsOptional, IsString, ValidateIf, ValidateNested } from 'class-validator' import { sanitizeName, sanitizePath } from '../../files/utils/files' diff --git a/backend/src/applications/spaces/guards/space.guard.spec.ts b/backend/src/applications/spaces/guards/space.guard.spec.ts index abe3b765..ec39d9e7 100644 --- a/backend/src/applications/spaces/guards/space.guard.spec.ts +++ b/backend/src/applications/spaces/guards/space.guard.spec.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { createMock, DeepMocked } from '@golevelup/ts-jest' import { ExecutionContext, HttpException, HttpStatus } from '@nestjs/common' import { Test, TestingModule } from '@nestjs/testing' diff --git a/backend/src/applications/spaces/guards/space.guard.ts b/backend/src/applications/spaces/guards/space.guard.ts index 8908525b..51af1a5d 100644 --- a/backend/src/applications/spaces/guards/space.guard.ts +++ b/backend/src/applications/spaces/guards/space.guard.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { CanActivate, ExecutionContext, HttpException, HttpStatus, Injectable, Logger } from '@nestjs/common' import { Reflector } from '@nestjs/core' import { HTTP_METHOD } from '../../applications.constants' diff --git a/backend/src/applications/spaces/interfaces/space-diff.interface.ts b/backend/src/applications/spaces/interfaces/space-diff.interface.ts index cfa88d38..25674485 100644 --- a/backend/src/applications/spaces/interfaces/space-diff.interface.ts +++ b/backend/src/applications/spaces/interfaces/space-diff.interface.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { SyncDiffDto } from '../../sync/dtos/sync-operations.dto' export interface ParseDiffContext { diff --git a/backend/src/applications/spaces/interfaces/space-files.interface.ts b/backend/src/applications/spaces/interfaces/space-files.interface.ts index 7b45eb5b..752f347a 100644 --- a/backend/src/applications/spaces/interfaces/space-files.interface.ts +++ b/backend/src/applications/spaces/interfaces/space-files.interface.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import type { FileProps } from '../../files/interfaces/file-props.interface' export interface SpaceFiles { diff --git a/backend/src/applications/spaces/interfaces/space-request.interface.ts b/backend/src/applications/spaces/interfaces/space-request.interface.ts index d258245e..68706299 100644 --- a/backend/src/applications/spaces/interfaces/space-request.interface.ts +++ b/backend/src/applications/spaces/interfaces/space-request.interface.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { FastifyAuthenticatedRequest } from '../../../authentication/interfaces/auth-request.interface' import { SpaceEnv } from '../models/space-env.model' diff --git a/backend/src/applications/spaces/interfaces/space-trash.interface.ts b/backend/src/applications/spaces/interfaces/space-trash.interface.ts index 54f44451..a220978d 100644 --- a/backend/src/applications/spaces/interfaces/space-trash.interface.ts +++ b/backend/src/applications/spaces/interfaces/space-trash.interface.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - export interface SpaceTrash { id: number name: string diff --git a/backend/src/applications/spaces/models/space-env.model.ts b/backend/src/applications/spaces/models/space-env.model.ts index 37384313..869a3017 100644 --- a/backend/src/applications/spaces/models/space-env.model.ts +++ b/backend/src/applications/spaces/models/space-env.model.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { uniquePermissions } from '../../../common/functions' import { FileDBProps } from '../../files/interfaces/file-db-props.interface' import { FileTaskProps } from '../../files/models/file-task' diff --git a/backend/src/applications/spaces/models/space-props.model.ts b/backend/src/applications/spaces/models/space-props.model.ts index 59163054..5b3df975 100644 --- a/backend/src/applications/spaces/models/space-props.model.ts +++ b/backend/src/applications/spaces/models/space-props.model.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { uniquePermissions } from '../../../common/functions' import { MEMBER_TYPE } from '../../users/constants/member' import { Member } from '../../users/interfaces/member.interface' diff --git a/backend/src/applications/spaces/models/space-root-props.model.ts b/backend/src/applications/spaces/models/space-root-props.model.ts index 838ee3f3..9b1361d0 100644 --- a/backend/src/applications/spaces/models/space-root-props.model.ts +++ b/backend/src/applications/spaces/models/space-root-props.model.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import type { FileProps } from '../../files/interfaces/file-props.interface' import type { Owner } from '../../users/interfaces/owner.interface' import type { SpaceRoot } from '../schemas/space-root.interface' diff --git a/backend/src/applications/spaces/models/space.model.ts b/backend/src/applications/spaces/models/space.model.ts index a61f20f3..55a85085 100644 --- a/backend/src/applications/spaces/models/space.model.ts +++ b/backend/src/applications/spaces/models/space.model.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import fs from 'node:fs/promises' import path from 'node:path' import { configuration } from '../../../configuration/config.environment' diff --git a/backend/src/applications/spaces/schemas/space-members.interface.ts b/backend/src/applications/spaces/schemas/space-members.interface.ts index dd0f9e7f..c07d24eb 100644 --- a/backend/src/applications/spaces/schemas/space-members.interface.ts +++ b/backend/src/applications/spaces/schemas/space-members.interface.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import type { spacesMembers } from './spaces-members.schema' type SpaceMembersSchema = typeof spacesMembers.$inferSelect diff --git a/backend/src/applications/spaces/schemas/space-root.interface.ts b/backend/src/applications/spaces/schemas/space-root.interface.ts index 49803ad4..a405fd7f 100644 --- a/backend/src/applications/spaces/schemas/space-root.interface.ts +++ b/backend/src/applications/spaces/schemas/space-root.interface.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import type { spacesRoots } from './spaces-roots.schema' type SpaceRootSchema = typeof spacesRoots.$inferSelect diff --git a/backend/src/applications/spaces/schemas/space.interface.ts b/backend/src/applications/spaces/schemas/space.interface.ts index f3ac4119..b340bb07 100644 --- a/backend/src/applications/spaces/schemas/space.interface.ts +++ b/backend/src/applications/spaces/schemas/space.interface.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import type { spaces } from './spaces.schema' type SpaceSchema = typeof spaces.$inferSelect diff --git a/backend/src/applications/spaces/schemas/spaces-members.schema.ts b/backend/src/applications/spaces/schemas/spaces-members.schema.ts index 7cb4345c..5878ba67 100644 --- a/backend/src/applications/spaces/schemas/spaces-members.schema.ts +++ b/backend/src/applications/spaces/schemas/spaces-members.schema.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { sql } from 'drizzle-orm' import { bigint, datetime, index, mysqlTable, tinyint, unique, varchar } from 'drizzle-orm/mysql-core' import { links } from '../../links/schemas/links.schema' diff --git a/backend/src/applications/spaces/schemas/spaces-roots.schema.ts b/backend/src/applications/spaces/schemas/spaces-roots.schema.ts index 0c7cdefb..e97d278e 100644 --- a/backend/src/applications/spaces/schemas/spaces-roots.schema.ts +++ b/backend/src/applications/spaces/schemas/spaces-roots.schema.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { sql } from 'drizzle-orm' import { bigint, datetime, index, mysqlTable, unique, varchar } from 'drizzle-orm/mysql-core' import { files } from '../../files/schemas/files.schema' diff --git a/backend/src/applications/spaces/schemas/spaces.schema.ts b/backend/src/applications/spaces/schemas/spaces.schema.ts index d38fda49..0c3e86ca 100644 --- a/backend/src/applications/spaces/schemas/spaces.schema.ts +++ b/backend/src/applications/spaces/schemas/spaces.schema.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { SQL, sql } from 'drizzle-orm' import { bigint, boolean, datetime, mysqlTable, uniqueIndex, varchar } from 'drizzle-orm/mysql-core' import { SPACE_PERMS_SEP } from '../constants/spaces' diff --git a/backend/src/applications/spaces/services/spaces-browser.service.spec.ts b/backend/src/applications/spaces/services/spaces-browser.service.spec.ts index 53ef5330..726fdb32 100644 --- a/backend/src/applications/spaces/services/spaces-browser.service.spec.ts +++ b/backend/src/applications/spaces/services/spaces-browser.service.spec.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { ConfigModule } from '@nestjs/config' import { Test, TestingModule } from '@nestjs/testing' import { exportConfiguration } from '../../../configuration/config.environment' diff --git a/backend/src/applications/spaces/services/spaces-browser.service.ts b/backend/src/applications/spaces/services/spaces-browser.service.ts index f6e45478..b6f1170d 100644 --- a/backend/src/applications/spaces/services/spaces-browser.service.ts +++ b/backend/src/applications/spaces/services/spaces-browser.service.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { HttpException, Injectable, Logger } from '@nestjs/common' import fs from 'node:fs/promises' import path from 'node:path' diff --git a/backend/src/applications/spaces/services/spaces-manager.service.spec.ts b/backend/src/applications/spaces/services/spaces-manager.service.spec.ts index 800290e0..069118fc 100644 --- a/backend/src/applications/spaces/services/spaces-manager.service.spec.ts +++ b/backend/src/applications/spaces/services/spaces-manager.service.spec.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { ConfigModule, ConfigService } from '@nestjs/config' import { Test, TestingModule } from '@nestjs/testing' import fs from 'node:fs/promises' diff --git a/backend/src/applications/spaces/services/spaces-manager.service.ts b/backend/src/applications/spaces/services/spaces-manager.service.ts index f34c89af..0012af35 100644 --- a/backend/src/applications/spaces/services/spaces-manager.service.ts +++ b/backend/src/applications/spaces/services/spaces-manager.service.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { HttpException, HttpStatus, Injectable, Logger } from '@nestjs/common' import { eq, isNotNull, lte } from 'drizzle-orm' import fs from 'node:fs/promises' diff --git a/backend/src/applications/spaces/services/spaces-queries.service.ts b/backend/src/applications/spaces/services/spaces-queries.service.ts index bd738324..d5b6bc08 100644 --- a/backend/src/applications/spaces/services/spaces-queries.service.ts +++ b/backend/src/applications/spaces/services/spaces-queries.service.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Inject, Injectable, Logger } from '@nestjs/common' import { and, countDistinct, eq, isNotNull, isNull, max, SelectedFields, SQL, sql } from 'drizzle-orm' import { alias, union } from 'drizzle-orm/mysql-core' diff --git a/backend/src/applications/spaces/services/spaces-scheduler.service.ts b/backend/src/applications/spaces/services/spaces-scheduler.service.ts index 29b774a0..9ea22e38 100644 --- a/backend/src/applications/spaces/services/spaces-scheduler.service.ts +++ b/backend/src/applications/spaces/services/spaces-scheduler.service.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Injectable, Logger } from '@nestjs/common' import { Cron, CronExpression, Timeout } from '@nestjs/schedule' import { SharesManager } from '../../shares/services/shares-manager.service' diff --git a/backend/src/applications/spaces/spaces.controller.spec.ts b/backend/src/applications/spaces/spaces.controller.spec.ts index cf7dfeb0..89f88126 100644 --- a/backend/src/applications/spaces/spaces.controller.spec.ts +++ b/backend/src/applications/spaces/spaces.controller.spec.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Test, TestingModule } from '@nestjs/testing' import { Cache } from '../../infrastructure/cache/services/cache.service' import { ContextManager } from '../../infrastructure/context/services/context-manager.service' diff --git a/backend/src/applications/spaces/spaces.controller.ts b/backend/src/applications/spaces/spaces.controller.ts index 95496adb..52a7c7f3 100644 --- a/backend/src/applications/spaces/spaces.controller.ts +++ b/backend/src/applications/spaces/spaces.controller.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Body, Controller, diff --git a/backend/src/applications/spaces/spaces.module.ts b/backend/src/applications/spaces/spaces.module.ts index 1f793abd..cad464af 100644 --- a/backend/src/applications/spaces/spaces.module.ts +++ b/backend/src/applications/spaces/spaces.module.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Module } from '@nestjs/common' import { SpaceGuard } from './guards/space.guard' import { SpacesBrowser } from './services/spaces-browser.service' diff --git a/backend/src/applications/spaces/utils/paths.ts b/backend/src/applications/spaces/utils/paths.ts index ee6e2f7f..fb59c18a 100644 --- a/backend/src/applications/spaces/utils/paths.ts +++ b/backend/src/applications/spaces/utils/paths.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { HttpStatus } from '@nestjs/common' import fs from 'fs/promises' import path from 'node:path' diff --git a/backend/src/applications/spaces/utils/permissions.ts b/backend/src/applications/spaces/utils/permissions.ts index 49564f72..ffa27e8d 100644 --- a/backend/src/applications/spaces/utils/permissions.ts +++ b/backend/src/applications/spaces/utils/permissions.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { intersectPermissions } from '../../../common/shared' import { USER_PERMISSION } from '../../users/constants/user' import type { UserModel } from '../../users/models/user.model' diff --git a/backend/src/applications/spaces/utils/routes.ts b/backend/src/applications/spaces/utils/routes.ts index adcc9776..ea172675 100644 --- a/backend/src/applications/spaces/utils/routes.ts +++ b/backend/src/applications/spaces/utils/routes.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { sanitizePath } from '../../files/utils/files' export function PATH_TO_SPACE_SEGMENTS(path: string): string[] { diff --git a/backend/src/applications/sync/constants/auth.ts b/backend/src/applications/sync/constants/auth.ts index e4c351dd..bcd9dc7b 100644 --- a/backend/src/applications/sync/constants/auth.ts +++ b/backend/src/applications/sync/constants/auth.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - export enum CLIENT_AUTH_TYPE { COOKIE = 'cookie', TOKEN = 'token' diff --git a/backend/src/applications/sync/constants/routes.ts b/backend/src/applications/sync/constants/routes.ts index 0a7dfc41..f00552aa 100644 --- a/backend/src/applications/sync/constants/routes.ts +++ b/backend/src/applications/sync/constants/routes.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { APP_BASE_ROUTE } from '../../applications.constants' export const SYNC_BASE_ROUTE = 'sync' @@ -12,6 +6,7 @@ export const SYNC_ROUTE = { HANDSHAKE: 'handshake', REGISTER: 'register', UNREGISTER: 'unregister', + REGISTER_AUTH: 'register/auth', APP_STORE: 'app-store', AUTH: 'auth', CLIENTS: 'clients', @@ -23,3 +18,4 @@ export const SYNC_ROUTE = { export const API_SYNC_AUTH_COOKIE = `${SYNC_ROUTE.BASE}/${SYNC_ROUTE.AUTH}/cookie` export const API_SYNC_CLIENTS = `${SYNC_ROUTE.BASE}/${SYNC_ROUTE.CLIENTS}` +export const API_SYNC_REGISTER_AUTH = `${SYNC_ROUTE.BASE}/${SYNC_ROUTE.REGISTER_AUTH}` diff --git a/backend/src/applications/sync/constants/store.ts b/backend/src/applications/sync/constants/store.ts index cdb92fac..b4e97159 100644 --- a/backend/src/applications/sync/constants/store.ts +++ b/backend/src/applications/sync/constants/store.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - export const APP_STORE_URL = 'https://updates.sync-in.org' export const APP_STORE_DIRNAME = 'releases' export const APP_STORE_MANIFEST_FILE = 'latest.json' diff --git a/backend/src/applications/sync/constants/sync.ts b/backend/src/applications/sync/constants/sync.ts index 41473982..d24be33e 100644 --- a/backend/src/applications/sync/constants/sync.ts +++ b/backend/src/applications/sync/constants/sync.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { SPACE_ALIAS, SPACE_REPOSITORY } from '../../spaces/constants/spaces' export const SYNC_IN_SERVER_AGENT = 'sync-in' as const diff --git a/backend/src/applications/sync/decorators/sync-context.decorator.ts b/backend/src/applications/sync/decorators/sync-context.decorator.ts index 6df5b4f8..f435523b 100644 --- a/backend/src/applications/sync/decorators/sync-context.decorator.ts +++ b/backend/src/applications/sync/decorators/sync-context.decorator.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { SetMetadata } from '@nestjs/common' export const SYNC_CONTEXT = 'SyncContext' diff --git a/backend/src/applications/sync/decorators/sync-environment.decorator.ts b/backend/src/applications/sync/decorators/sync-environment.decorator.ts index 51e20420..09385140 100644 --- a/backend/src/applications/sync/decorators/sync-environment.decorator.ts +++ b/backend/src/applications/sync/decorators/sync-environment.decorator.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { applyDecorators, UseGuards } from '@nestjs/common' import { SpaceGuard } from '../../spaces/guards/space.guard' import { USER_PERMISSION } from '../../users/constants/user' diff --git a/backend/src/applications/sync/dtos/sync-client-auth.dto.ts b/backend/src/applications/sync/dtos/sync-client-auth.dto.ts index 3dfb6bf8..fc92f358 100644 --- a/backend/src/applications/sync/dtos/sync-client-auth.dto.ts +++ b/backend/src/applications/sync/dtos/sync-client-auth.dto.ts @@ -1,11 +1,6 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - -import { IsDefined, IsNotEmpty, IsNotEmptyObject, IsObject, IsString, IsUUID } from 'class-validator' -import { SyncClientInfo } from '../interfaces/sync-client.interface' +import { Type } from 'class-transformer' +import { IsBoolean, IsDefined, IsNotEmpty, IsNotEmptyObject, IsObject, IsOptional, IsString, IsUUID, ValidateNested } from 'class-validator' +import { SyncClientInfoDto } from './sync-client-info.dto' export class SyncClientAuthDto { @IsNotEmpty() @@ -19,7 +14,13 @@ export class SyncClientAuthDto { token: string @IsDefined() - @IsNotEmptyObject() @IsObject() - info: SyncClientInfo + @IsNotEmptyObject() + @ValidateNested() + @Type(() => SyncClientInfoDto) + info: SyncClientInfoDto + + @IsOptional() + @IsBoolean() + tokenHasExpired?: boolean } diff --git a/backend/src/applications/sync/dtos/sync-client-info.dto.ts b/backend/src/applications/sync/dtos/sync-client-info.dto.ts new file mode 100644 index 00000000..ce218890 --- /dev/null +++ b/backend/src/applications/sync/dtos/sync-client-info.dto.ts @@ -0,0 +1,23 @@ +import { IsEnum, IsString } from 'class-validator' +import { SYNC_CLIENT_TYPE } from '../constants/sync' +import { SyncClientInfo } from '../interfaces/sync-client.interface' + +export class SyncClientInfoDto implements SyncClientInfo { + @IsString() + node: string + + @IsString() + os: string + + @IsString() + osRelease: string + + @IsString() + user: string + + @IsEnum(SYNC_CLIENT_TYPE) + type: SYNC_CLIENT_TYPE + + @IsString() + version: string +} diff --git a/backend/src/applications/sync/dtos/sync-client-registration.dto.ts b/backend/src/applications/sync/dtos/sync-client-registration.dto.ts index 7acfad96..9fe477c4 100644 --- a/backend/src/applications/sync/dtos/sync-client-registration.dto.ts +++ b/backend/src/applications/sync/dtos/sync-client-registration.dto.ts @@ -1,11 +1,6 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - -import { IsDefined, IsNotEmpty, IsNotEmptyObject, IsObject, IsOptional, IsString, IsUUID } from 'class-validator' -import { SyncClientInfo } from '../interfaces/sync-client.interface' +import { Type } from 'class-transformer' +import { IsDefined, IsNotEmpty, IsNotEmptyObject, IsObject, IsOptional, IsString, IsUUID, ValidateNested } from 'class-validator' +import { SyncClientInfoDto } from './sync-client-info.dto' export class SyncClientRegistrationDto { @IsNotEmpty() @@ -26,7 +21,23 @@ export class SyncClientRegistrationDto { clientId: string @IsDefined() + @IsObject() @IsNotEmptyObject() + @ValidateNested() + @Type(() => SyncClientInfoDto) + info: SyncClientInfoDto +} + +export class SyncClientAuthRegistrationDto { + @IsOptional() + @IsString() + @IsUUID() + clientId?: string + + @IsDefined() @IsObject() - info: SyncClientInfo + @IsNotEmptyObject() + @ValidateNested() + @Type(() => SyncClientInfoDto) + info: SyncClientInfoDto } diff --git a/backend/src/applications/sync/dtos/sync-operations.dto.ts b/backend/src/applications/sync/dtos/sync-operations.dto.ts index 2e6cc075..d30448e7 100644 --- a/backend/src/applications/sync/dtos/sync-operations.dto.ts +++ b/backend/src/applications/sync/dtos/sync-operations.dto.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Transform } from 'class-transformer' import { IsBoolean, IsDefined, IsInt, IsNotEmpty, IsObject, IsOptional, IsString } from 'class-validator' import { MakeFileDto } from '../../files/dto/file-operations.dto' diff --git a/backend/src/applications/sync/dtos/sync-path.dto.ts b/backend/src/applications/sync/dtos/sync-path.dto.ts index b6c5b081..d9f42e96 100644 --- a/backend/src/applications/sync/dtos/sync-path.dto.ts +++ b/backend/src/applications/sync/dtos/sync-path.dto.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Transform, Type } from 'class-transformer' import { IsArray, IsBoolean, IsEnum, IsInt, IsNotEmpty, IsOptional, IsString, ValidateNested } from 'class-validator' import { SYNC_PATH_CONFLICT_MODE, SYNC_PATH_DIFF_MODE, SYNC_PATH_MODE, SYNC_PATH_SCHEDULER_UNIT } from '../constants/sync' diff --git a/backend/src/applications/sync/dtos/sync-upload.dto.ts b/backend/src/applications/sync/dtos/sync-upload.dto.ts index de0d551c..f3e54fe4 100644 --- a/backend/src/applications/sync/dtos/sync-upload.dto.ts +++ b/backend/src/applications/sync/dtos/sync-upload.dto.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Transform } from 'class-transformer' import { IsDefined, IsInt, IsNotEmpty, IsOptional, IsString } from 'class-validator' diff --git a/backend/src/applications/sync/interceptors/sync-diff-gzip-body.interceptor.spec.ts b/backend/src/applications/sync/interceptors/sync-diff-gzip-body.interceptor.spec.ts index a83fd19c..98da1c11 100644 --- a/backend/src/applications/sync/interceptors/sync-diff-gzip-body.interceptor.spec.ts +++ b/backend/src/applications/sync/interceptors/sync-diff-gzip-body.interceptor.spec.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { CallHandler, ExecutionContext, HttpException, HttpStatus } from '@nestjs/common' import { Test, TestingModule } from '@nestjs/testing' import { lastValueFrom, of } from 'rxjs' diff --git a/backend/src/applications/sync/interceptors/sync-diff-gzip-body.interceptor.ts b/backend/src/applications/sync/interceptors/sync-diff-gzip-body.interceptor.ts index 18672da6..b4f63eae 100644 --- a/backend/src/applications/sync/interceptors/sync-diff-gzip-body.interceptor.ts +++ b/backend/src/applications/sync/interceptors/sync-diff-gzip-body.interceptor.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { CallHandler, ExecutionContext, HttpException, HttpStatus, Injectable, NestInterceptor } from '@nestjs/common' import { FastifyRequest } from 'fastify' import { buffer } from 'node:stream/consumers' diff --git a/backend/src/applications/sync/interfaces/store-manifest.interface.ts b/backend/src/applications/sync/interfaces/store-manifest.interface.ts index df6c5c31..a349c809 100644 --- a/backend/src/applications/sync/interfaces/store-manifest.interface.ts +++ b/backend/src/applications/sync/interfaces/store-manifest.interface.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { APP_STORE_PLATFORM, APP_STORE_REPOSITORY } from '../constants/store' interface PackageManifest { diff --git a/backend/src/applications/sync/interfaces/sync-client-auth.interface.ts b/backend/src/applications/sync/interfaces/sync-client-auth.interface.ts index 3867fea6..b7570bdf 100644 --- a/backend/src/applications/sync/interfaces/sync-client-auth.interface.ts +++ b/backend/src/applications/sync/interfaces/sync-client-auth.interface.ts @@ -1,18 +1,11 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ +import type { LoginResponseDto } from '../../../authentication/dto/login-response.dto' +import type { TokenResponseDto } from '../../../authentication/dto/token-response.dto' -import { LoginResponseDto } from '../../../authentication/dto/login-response.dto' -import { TokenResponseDto } from '../../../authentication/dto/token-response.dto' +// send the new client token +export type SyncClientAuthCookie = LoginResponseDto & { client_token_update?: string } +export type SyncClientAuthToken = TokenResponseDto & { client_token_update?: string } -export class ClientAuthCookieDto extends LoginResponseDto { - // send the new client token - client_token_update?: string -} - -export class ClientAuthTokenDto extends TokenResponseDto { - // send the new client token - client_token_update?: string +export interface SyncClientAuthRegistration { + clientId: string + clientToken: string } diff --git a/backend/src/applications/sync/interfaces/sync-client-paths.interface.ts b/backend/src/applications/sync/interfaces/sync-client-paths.interface.ts index ed0f5386..1ec28dfd 100644 --- a/backend/src/applications/sync/interfaces/sync-client-paths.interface.ts +++ b/backend/src/applications/sync/interfaces/sync-client-paths.interface.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { SyncClient } from '../schemas/sync-client.interface' import { SyncPath } from '../schemas/sync-path.interface' import { SyncClientInfo } from './sync-client.interface' diff --git a/backend/src/applications/sync/interfaces/sync-client.interface.ts b/backend/src/applications/sync/interfaces/sync-client.interface.ts index 5988f9a9..8565d7c3 100644 --- a/backend/src/applications/sync/interfaces/sync-client.interface.ts +++ b/backend/src/applications/sync/interfaces/sync-client.interface.ts @@ -1,10 +1,4 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - -import { SYNC_CLIENT_TYPE } from '../constants/sync' +import type { SYNC_CLIENT_TYPE } from '../constants/sync' export interface SyncClientInfo { node: string diff --git a/backend/src/applications/sync/interfaces/sync-diff.interface.ts b/backend/src/applications/sync/interfaces/sync-diff.interface.ts index 45667012..3cd25960 100644 --- a/backend/src/applications/sync/interfaces/sync-diff.interface.ts +++ b/backend/src/applications/sync/interfaces/sync-diff.interface.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - // use FSTAT positions for stats array import { F_SPECIAL_STAT } from '../constants/sync' diff --git a/backend/src/applications/sync/interfaces/sync-path.interface.ts b/backend/src/applications/sync/interfaces/sync-path.interface.ts index b98dabd6..d638a3c3 100644 --- a/backend/src/applications/sync/interfaces/sync-path.interface.ts +++ b/backend/src/applications/sync/interfaces/sync-path.interface.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import type { SYNC_PATH_CONFLICT_MODE, SYNC_PATH_DIFF_MODE, SYNC_PATH_MODE } from '../constants/sync' import type { SyncPath } from '../schemas/sync-path.interface' diff --git a/backend/src/applications/sync/schemas/sync-client.interface.ts b/backend/src/applications/sync/schemas/sync-client.interface.ts index e0967c7e..109ab67b 100644 --- a/backend/src/applications/sync/schemas/sync-client.interface.ts +++ b/backend/src/applications/sync/schemas/sync-client.interface.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { SyncClientInfo } from '../interfaces/sync-client.interface' import type { syncClients } from './sync-clients.schema' diff --git a/backend/src/applications/sync/schemas/sync-clients.schema.ts b/backend/src/applications/sync/schemas/sync-clients.schema.ts index d0726c48..251b231c 100644 --- a/backend/src/applications/sync/schemas/sync-clients.schema.ts +++ b/backend/src/applications/sync/schemas/sync-clients.schema.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { sql } from 'drizzle-orm' import { bigint, boolean, char, datetime, index, mysqlTable, varchar } from 'drizzle-orm/mysql-core' import { jsonColumn } from '../../../infrastructure/database/columns' diff --git a/backend/src/applications/sync/schemas/sync-path.interface.ts b/backend/src/applications/sync/schemas/sync-path.interface.ts index dc50aa09..2aae640a 100644 --- a/backend/src/applications/sync/schemas/sync-path.interface.ts +++ b/backend/src/applications/sync/schemas/sync-path.interface.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import type { SyncPathSettings } from '../interfaces/sync-path.interface' import { syncPaths } from './sync-paths.schema' diff --git a/backend/src/applications/sync/schemas/sync-paths.schema.ts b/backend/src/applications/sync/schemas/sync-paths.schema.ts index 293b0f8a..2b4aee6a 100644 --- a/backend/src/applications/sync/schemas/sync-paths.schema.ts +++ b/backend/src/applications/sync/schemas/sync-paths.schema.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { sql } from 'drizzle-orm' import { bigint, char, datetime, index, mysqlTable } from 'drizzle-orm/mysql-core' import { jsonColumn } from '../../../infrastructure/database/columns' diff --git a/backend/src/applications/sync/services/sync-clients-manager.service.spec.ts b/backend/src/applications/sync/services/sync-clients-manager.service.spec.ts index e9f3dd10..37102d99 100644 --- a/backend/src/applications/sync/services/sync-clients-manager.service.spec.ts +++ b/backend/src/applications/sync/services/sync-clients-manager.service.spec.ts @@ -1,18 +1,12 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { HttpService } from '@nestjs/axios' import { HttpStatus } from '@nestjs/common' import { Test, TestingModule } from '@nestjs/testing' import { FastifyReply } from 'fastify' import crypto from 'node:crypto' import fs from 'node:fs/promises' -import { AuthMethod } from '../../../authentication/models/auth-method' -import { AuthManager } from '../../../authentication/services/auth-manager.service' -import { AuthMethod2FA } from '../../../authentication/services/auth-methods/auth-method-two-fa.service' +import { AuthManager } from '../../../authentication/auth.service' +import { AuthProvider } from '../../../authentication/providers/auth-providers.models' +import { AuthProvider2FA } from '../../../authentication/providers/two-fa/auth-provider-two-fa.service' import * as commonFunctions from '../../../common/functions' import * as commonShared from '../../../common/shared' import { configuration } from '../../../configuration/config.environment' @@ -23,6 +17,7 @@ import { UsersManager } from '../../users/services/users-manager.service' import { CLIENT_AUTH_TYPE, CLIENT_TOKEN_EXPIRED_ERROR } from '../constants/auth' import { APP_STORE_DIRNAME, APP_STORE_REPOSITORY } from '../constants/store' import { SYNC_CLIENT_TYPE } from '../constants/sync' +import { SyncClientAuthRegistration } from '../interfaces/sync-client-auth.interface' import { SyncClientsManager } from './sync-clients-manager.service' import { SyncQueries } from './sync-queries.service' @@ -54,7 +49,7 @@ describe(SyncClientsManager.name, () => { // Mocks let http: { axiosRef: jest.Mock } let authManager: { setCookies: jest.Mock; getTokens: jest.Mock } - let authMethod: { validateUser: jest.Mock } + let authProvider: { validateUser: jest.Mock } let usersManager: { fromUserId: jest.Mock; updateAccesses: jest.Mock } let syncQueries: { getOrCreateClient: jest.Mock @@ -94,7 +89,7 @@ describe(SyncClientsManager.name, () => { beforeAll(async () => { http = { axiosRef: jest.fn() } authManager = { setCookies: jest.fn(), getTokens: jest.fn() } - authMethod = { validateUser: jest.fn() } + authProvider = { validateUser: jest.fn() } usersManager = { fromUserId: jest.fn(), updateAccesses: jest.fn() } syncQueries = { getOrCreateClient: jest.fn(), @@ -119,8 +114,8 @@ describe(SyncClientsManager.name, () => { { provide: SyncQueries, useValue: syncQueries }, { provide: UsersManager, useValue: usersManager }, { provide: AuthManager, useValue: authManager }, - { provide: AuthMethod, useValue: authMethod }, - { provide: AuthMethod2FA, useValue: {} } + { provide: AuthProvider, useValue: authProvider }, + { provide: AuthProvider2FA, useValue: {} } ] }).compile() @@ -162,21 +157,21 @@ describe(SyncClientsManager.name, () => { ['Unauthorized when credentials are invalid', null, HttpStatus.UNAUTHORIZED], ['Forbidden when user lacks DESKTOP_APP permission', { id: 10, login: 'john', havePermission: () => false }, HttpStatus.FORBIDDEN] ])('should throw %s', async (_label, user, status) => { - authMethod.validateUser.mockResolvedValue(user) + authProvider.validateUser.mockResolvedValue(user) await expect(service.register(baseDto as any, '1.2.3.4')).rejects.toMatchObject({ status }) }) it('should return client token when registration succeeds', async () => { - authMethod.validateUser.mockResolvedValue({ id: 10, login: 'john', havePermission: () => true }) + authProvider.validateUser.mockResolvedValue({ id: 10, login: 'john', havePermission: () => true }) syncQueries.getOrCreateClient.mockResolvedValue('token-abc') const r = await service.register(baseDto as any, '1.2.3.4') - expect(r).toEqual({ clientToken: 'token-abc' }) + expect(r).toEqual({ clientId: 'client-1', clientToken: 'token-abc' } satisfies SyncClientAuthRegistration) expect(syncQueries.getOrCreateClient).toHaveBeenCalledWith(10, 'client-1', baseDto.info, '1.2.3.4') }) it('should throw Internal Server Error when persistence fails', async () => { - authMethod.validateUser.mockResolvedValue({ id: 10, login: 'john', havePermission: () => true }) + authProvider.validateUser.mockResolvedValue({ id: 10, login: 'john', havePermission: () => true }) syncQueries.getOrCreateClient.mockRejectedValue(new Error('db error')) await expect(service.register(baseDto as any, '1.2.3.4')).rejects.toMatchObject({ status: HttpStatus.INTERNAL_SERVER_ERROR }) }) diff --git a/backend/src/applications/sync/services/sync-clients-manager.service.ts b/backend/src/applications/sync/services/sync-clients-manager.service.ts index 6281a53c..6ef5ea69 100644 --- a/backend/src/applications/sync/services/sync-clients-manager.service.ts +++ b/backend/src/applications/sync/services/sync-clients-manager.service.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { HttpService } from '@nestjs/axios' import { HttpException, HttpStatus, Injectable, Logger } from '@nestjs/common' import { AxiosResponse } from 'axios' @@ -11,9 +5,10 @@ import { FastifyReply } from 'fastify' import crypto from 'node:crypto' import fs from 'node:fs/promises' import path from 'node:path' -import { AuthMethod } from '../../../authentication/models/auth-method' -import { AuthManager } from '../../../authentication/services/auth-manager.service' -import { AuthMethod2FA } from '../../../authentication/services/auth-methods/auth-method-two-fa.service' +import { AuthManager } from '../../../authentication/auth.service' +import { FastifyAuthenticatedRequest } from '../../../authentication/interfaces/auth-request.interface' +import { AuthProvider } from '../../../authentication/providers/auth-providers.models' +import { AuthProvider2FA } from '../../../authentication/providers/two-fa/auth-provider-two-fa.service' import { convertHumanTimeToSeconds } from '../../../common/functions' import { currentTimeStamp } from '../../../common/shared' import { STATIC_PATH } from '../../../configuration/config.constants' @@ -28,10 +23,11 @@ import { CLIENT_AUTH_TYPE, CLIENT_TOKEN_EXPIRATION_TIME, CLIENT_TOKEN_EXPIRED_ER import { APP_STORE_DIRNAME, APP_STORE_MANIFEST_FILE, APP_STORE_REPOSITORY, APP_STORE_URL } from '../constants/store' import { SYNC_CLIENT_TYPE } from '../constants/sync' import type { SyncClientAuthDto } from '../dtos/sync-client-auth.dto' -import type { SyncClientRegistrationDto } from '../dtos/sync-client-registration.dto' +import { SyncClientAuthRegistrationDto, SyncClientRegistrationDto } from '../dtos/sync-client-registration.dto' import { AppStoreManifest } from '../interfaces/store-manifest.interface' -import { ClientAuthCookieDto, ClientAuthTokenDto } from '../interfaces/sync-client-auth.interface' +import { SyncClientAuthCookie, SyncClientAuthRegistration, SyncClientAuthToken } from '../interfaces/sync-client-auth.interface' import { SyncClientPaths } from '../interfaces/sync-client-paths.interface' +import { SyncClientInfo } from '../interfaces/sync-client.interface' import { SyncClient } from '../schemas/sync-client.interface' import { SyncQueries } from './sync-queries.service' @@ -42,14 +38,14 @@ export class SyncClientsManager { constructor( private readonly http: HttpService, private readonly authManager: AuthManager, - private readonly authMethod: AuthMethod, - private readonly authMethod2Fa: AuthMethod2FA, + private readonly authProvider: AuthProvider, + private readonly authProvider2FA: AuthProvider2FA, private readonly usersManager: UsersManager, private readonly syncQueries: SyncQueries ) {} - async register(clientRegistrationDto: SyncClientRegistrationDto, ip: string): Promise<{ clientToken: string }> { - const user: UserModel = await this.authMethod.validateUser(clientRegistrationDto.login, clientRegistrationDto.password) + async register(clientRegistrationDto: SyncClientRegistrationDto, ip: string): Promise { + const user: UserModel = await this.authProvider.validateUser(clientRegistrationDto.login, clientRegistrationDto.password) if (!user) { this.logger.warn(`${this.register.name} - auth failed for user *${clientRegistrationDto.login}*`) throw new HttpException('Wrong login or password', HttpStatus.UNAUTHORIZED) @@ -59,25 +55,31 @@ export class SyncClientsManager { throw new HttpException('Missing permission', HttpStatus.FORBIDDEN) } if (configuration.auth.mfa.totp.enabled && user.twoFaEnabled) { + // Checking TOTP code and recovery code if (!clientRegistrationDto.code) { this.logger.warn(`${this.register.name} - missing two-fa code for user *${user.login}* (${user.id})`) throw new HttpException('Missing TWO-FA code', HttpStatus.UNAUTHORIZED) } - const auth = this.authMethod2Fa.validateTwoFactorCode(clientRegistrationDto.code, user.secrets.twoFaSecret) - if (!auth.success) { - this.logger.warn(`${this.register.name} - wrong two-fa code for user *${user.login}* (${user.id})`) - this.usersManager.updateAccesses(user, ip, false).catch((e: Error) => this.logger.error(`${this.register.name} - ${e}`)) - throw new HttpException(auth.message, HttpStatus.UNAUTHORIZED) + const authCode = this.authProvider2FA.validateTwoFactorCode(clientRegistrationDto.code, user.secrets.twoFaSecret) + if (!authCode.success) { + this.logger.warn(`${this.register.name} - two-fa code for *${user.login}* (${user.id}) - ${authCode.message}`) + const authRCode = await this.authProvider2FA.validateRecoveryCode(user.id, clientRegistrationDto.code, user.secrets.recoveryCodes) + if (!authRCode.success) { + this.logger.warn(`${this.register.name} - two-fa recovery code for *${user.login}* (${user.id}) - ${authRCode.message}`) + this.usersManager.updateAccesses(user, ip, false).catch((e: Error) => this.logger.error(`${this.register.name} - ${e}`)) + throw new HttpException(authCode.message, HttpStatus.UNAUTHORIZED) + } } } - try { - const token = await this.syncQueries.getOrCreateClient(user.id, clientRegistrationDto.clientId, clientRegistrationDto.info, ip) - this.logger.log(`${this.register.name} - client *${clientRegistrationDto.info.type}* was registered for user *${user.login}* (${user.id})`) - return { clientToken: token } - } catch (e) { - this.logger.error(`${this.register.name} - ${e}`) - throw new HttpException('Error during the client registration', HttpStatus.INTERNAL_SERVER_ERROR) - } + return this.getOrCreateClient(user, clientRegistrationDto.clientId, clientRegistrationDto.info, ip) + } + + async registerWithAuth( + clientAuthenticatedRegistrationDto: SyncClientAuthRegistrationDto, + req: FastifyAuthenticatedRequest + ): Promise { + const clientId = clientAuthenticatedRegistrationDto.clientId || crypto.randomUUID() + return this.getOrCreateClient(req.user, clientId, clientAuthenticatedRegistrationDto.info, req.ip) } async unregister(user: UserModel): Promise { @@ -94,7 +96,7 @@ export class SyncClientsManager { syncClientAuthDto: SyncClientAuthDto, ip: string, res: FastifyReply - ): Promise { + ): Promise { const client = await this.syncQueries.getClient(syncClientAuthDto.clientId, null, syncClientAuthDto.token) if (!client) { throw new HttpException('Client is unknown', HttpStatus.FORBIDDEN) @@ -121,7 +123,7 @@ export class SyncClientsManager { user.clientId = client.id // update accesses this.usersManager.updateAccesses(user, ip, true).catch((e: Error) => this.logger.error(`${this.authenticate.name} - ${e}`)) - let r: ClientAuthTokenDto | ClientAuthCookieDto + let r: SyncClientAuthToken | SyncClientAuthCookie if (authType === CLIENT_AUTH_TYPE.COOKIE) { // used by the desktop app to perform the login setup using cookies r = await this.authManager.setCookies(user, res) @@ -204,4 +206,15 @@ export class SyncClientsManager { } return manifest } + + private async getOrCreateClient(user: UserModel, clientId: string, clientInfo: SyncClientInfo, ip: string): Promise { + try { + const token = await this.syncQueries.getOrCreateClient(user.id, clientId, clientInfo, ip) + this.logger.log(`${this.register.name} - client *${clientInfo.type}* was registered for user *${user.login}* (${user.id})`) + return { clientId: clientId, clientToken: token } satisfies SyncClientAuthRegistration + } catch (e) { + this.logger.error(`${this.register.name} - ${e}`) + throw new HttpException('Error during the client registration', HttpStatus.INTERNAL_SERVER_ERROR) + } + } } diff --git a/backend/src/applications/sync/services/sync-manager.service.spec.ts b/backend/src/applications/sync/services/sync-manager.service.spec.ts index 2b84dd94..3acb161b 100644 --- a/backend/src/applications/sync/services/sync-manager.service.spec.ts +++ b/backend/src/applications/sync/services/sync-manager.service.spec.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { HttpException, HttpStatus, StreamableFile } from '@nestjs/common' import { Test, TestingModule } from '@nestjs/testing' // Helpers to access mocked fs/promises diff --git a/backend/src/applications/sync/services/sync-manager.service.ts b/backend/src/applications/sync/services/sync-manager.service.ts index 6829bbc2..dfbc226c 100644 --- a/backend/src/applications/sync/services/sync-manager.service.ts +++ b/backend/src/applications/sync/services/sync-manager.service.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { HttpException, HttpStatus, Injectable, Logger, StreamableFile } from '@nestjs/common' import { FastifyReply } from 'fastify' import fs from 'fs/promises' diff --git a/backend/src/applications/sync/services/sync-paths-manager.service.spec.ts b/backend/src/applications/sync/services/sync-paths-manager.service.spec.ts index 623a0fdb..2da77d6d 100644 --- a/backend/src/applications/sync/services/sync-paths-manager.service.spec.ts +++ b/backend/src/applications/sync/services/sync-paths-manager.service.spec.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { HttpStatus } from '@nestjs/common' import { Test, TestingModule } from '@nestjs/testing' import { currentTimeStamp } from '../../../common/shared' @@ -460,18 +454,22 @@ describe(SyncPathsManager.name, () => { it('should throw BAD_REQUEST for shares list selection', async () => { await expect((service as any).getDBProps({ inSharesList: true } as any)).rejects.toMatchObject({ status: HttpStatus.BAD_REQUEST, - message: 'Sync all shares is not supported, you must select a sub-directory' + message: 'Syncing all shares is not supported. Please select a subdirectory' }) }) - it('should return ownerId only for personal space at root', async () => { - const res = await (service as any).getDBProps({ - inSharesList: false, - inPersonalSpace: true, - paths: [], - dbFile: { ownerId: 42 } - } as any) - expect(res).toEqual({ ownerId: 42 }) + it('should throw BAD_REQUEST for personal space selection', async () => { + await expect( + (service as any).getDBProps({ + inSharesList: false, + inPersonalSpace: true, + paths: [], + dbFile: { ownerId: 42 } + } as any) + ).rejects.toMatchObject({ + status: HttpStatus.BAD_REQUEST, + message: 'Syncing all personal files is not supported. Please select a subdirectory' + }) }) it('should return ownerId and fileId for personal space subdir', async () => { @@ -488,7 +486,7 @@ describe(SyncPathsManager.name, () => { it('should throw BAD_REQUEST for whole files repository without alias', async () => { await expect((service as any).getDBProps({ inFilesRepository: true, root: { alias: null }, paths: [] } as any)).rejects.toMatchObject({ status: HttpStatus.BAD_REQUEST, - message: 'Sync all space is not yet supported, you must select a sub-directory' + message: 'Syncing the entire space is not yet supported. Please select a subdirectory' }) }) diff --git a/backend/src/applications/sync/services/sync-paths-manager.service.ts b/backend/src/applications/sync/services/sync-paths-manager.service.ts index c2dc7094..a4bcfd00 100644 --- a/backend/src/applications/sync/services/sync-paths-manager.service.ts +++ b/backend/src/applications/sync/services/sync-paths-manager.service.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { HttpException, HttpStatus, Injectable, Logger } from '@nestjs/common' import { ACTION } from '../../../common/constants' import { currentTimeStamp } from '../../../common/shared' @@ -45,6 +39,10 @@ export class SyncPathsManager { if (req.space.quotaIsExceeded) { throw new HttpException('Storage quota exceeded', HttpStatus.INSUFFICIENT_STORAGE) } + + // Check DB path + const syncDBProps: SyncDBProps = await this.getDBProps(req.space) + if (!(await isPathExists(req.space.realPath))) { throw new HttpException(`Remote path not found : ${syncPathDto.remotePath}`, HttpStatus.NOT_FOUND) } @@ -55,9 +53,8 @@ export class SyncPathsManager { if (!client) { throw new HttpException('Client not found', HttpStatus.NOT_FOUND) } - const syncDBProps: SyncDBProps = await this.getDBProps(req.space) - // important : ensures the right remote path is used and stored + // important: ensures the right remote path is used and stored syncPathDto.remotePath = req.params['*'] // add permissions (skip end point protection using getEnvPermission) syncPathDto.permissions = getEnvPermissions(req.space, req.space.root) @@ -194,17 +191,17 @@ export class SyncPathsManager { private async getDBProps(space: SpaceEnv): Promise { if (space.inSharesList) { - throw new HttpException('Sync all shares is not supported, you must select a sub-directory', HttpStatus.BAD_REQUEST) + throw new HttpException('Syncing all shares is not supported. Please select a subdirectory', HttpStatus.BAD_REQUEST) } else if (space.inPersonalSpace) { if (space.paths.length) { return { ownerId: space.dbFile.ownerId, fileId: await this.getOrCreateFileId(space) } } else { - return { ownerId: space.dbFile.ownerId } + throw new HttpException('Syncing all personal files is not supported. Please select a subdirectory', HttpStatus.BAD_REQUEST) } } else if (space.inFilesRepository) { if (!space?.root?.alias) { // The synchronization direction should be adapted for each root depending on the permissions, this is not yet supported - throw new HttpException('Sync all space is not yet supported, you must select a sub-directory', HttpStatus.BAD_REQUEST) + throw new HttpException('Syncing the entire space is not yet supported. Please select a subdirectory', HttpStatus.BAD_REQUEST) } if (space.root.id && !space.paths.length) { return { spaceId: space.id, spaceRootId: space.root.id } diff --git a/backend/src/applications/sync/services/sync-queries.service.ts b/backend/src/applications/sync/services/sync-queries.service.ts index afb43143..bf5a047b 100644 --- a/backend/src/applications/sync/services/sync-queries.service.ts +++ b/backend/src/applications/sync/services/sync-queries.service.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Inject, Injectable } from '@nestjs/common' import { and, desc, eq, SQL, sql } from 'drizzle-orm' import { alias } from 'drizzle-orm/mysql-core' diff --git a/backend/src/applications/sync/sync.config.ts b/backend/src/applications/sync/sync.config.ts index 583a3776..f997f658 100644 --- a/backend/src/applications/sync/sync.config.ts +++ b/backend/src/applications/sync/sync.config.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { IsEnum } from 'class-validator' import { APP_STORE_REPOSITORY } from './constants/store' diff --git a/backend/src/applications/sync/sync.controller.spec.ts b/backend/src/applications/sync/sync.controller.spec.ts index 9677556b..5237d7db 100644 --- a/backend/src/applications/sync/sync.controller.spec.ts +++ b/backend/src/applications/sync/sync.controller.spec.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Test, TestingModule } from '@nestjs/testing' import { ContextManager } from '../../infrastructure/context/services/context-manager.service' import { SpacesManager } from '../spaces/services/spaces-manager.service' diff --git a/backend/src/applications/sync/sync.controller.ts b/backend/src/applications/sync/sync.controller.ts index dc73e2fa..31d41b1b 100644 --- a/backend/src/applications/sync/sync.controller.ts +++ b/backend/src/applications/sync/sync.controller.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Body, Controller, @@ -28,6 +22,7 @@ import { } from '@nestjs/common' import { FastifyReply, FastifyRequest } from 'fastify' import { AuthTokenSkip } from '../../authentication/decorators/auth-token-skip.decorator' +import { FastifyAuthenticatedRequest } from '../../authentication/interfaces/auth-request.interface' import { ContextInterceptor } from '../../infrastructure/context/interceptors/context.interceptor' import { SkipSpacePermissionsCheck } from '../spaces/decorators/space-skip-permissions.decorator' import { FastifySpaceRequest } from '../spaces/interfaces/space-request.interface' @@ -41,13 +36,13 @@ import { SYNC_ROUTE } from './constants/routes' import { CHECK_SERVER_RESP, SYNC_IN_SERVER_AGENT } from './constants/sync' import { SyncEnvironment } from './decorators/sync-environment.decorator' import { SyncClientAuthDto } from './dtos/sync-client-auth.dto' -import type { SyncClientRegistrationDto } from './dtos/sync-client-registration.dto' +import { SyncClientAuthRegistrationDto, SyncClientRegistrationDto } from './dtos/sync-client-registration.dto' import { SyncCopyMoveDto, SyncDiffDto, SyncMakeDto, SyncPropsDto } from './dtos/sync-operations.dto' import { SyncPathDto, SyncPathUpdateDto } from './dtos/sync-path.dto' import { SyncUploadDto } from './dtos/sync-upload.dto' import { SyncDiffGzipBodyInterceptor } from './interceptors/sync-diff-gzip-body.interceptor' import { AppStoreManifest } from './interfaces/store-manifest.interface' -import { ClientAuthCookieDto, ClientAuthTokenDto } from './interfaces/sync-client-auth.interface' +import { SyncClientAuthCookie, SyncClientAuthRegistration, SyncClientAuthToken } from './interfaces/sync-client-auth.interface' import { SyncClientPaths } from './interfaces/sync-client-paths.interface' import { SyncPathSettings } from './interfaces/sync-path.interface' import { SyncClientsManager } from './services/sync-clients-manager.service' @@ -75,10 +70,20 @@ export class SyncController { @Post(SYNC_ROUTE.REGISTER) @AuthTokenSkip() - register(@Body() syncClientRegistrationDto: SyncClientRegistrationDto, @Req() req: FastifyRequest): Promise<{ clientToken: string }> { + register(@Body() syncClientRegistrationDto: SyncClientRegistrationDto, @Req() req: FastifyRequest): Promise { return this.syncClientsManager.register(syncClientRegistrationDto, req.ip) } + @Post(SYNC_ROUTE.REGISTER_AUTH) + @UserHavePermission(USER_PERMISSION.DESKTOP_APP) + @UseGuards(UserPermissionsGuard) + registerWithAuth( + @Body() clientAuthenticatedRegistrationDto: SyncClientAuthRegistrationDto, + @Req() req: FastifyAuthenticatedRequest + ): Promise { + return this.syncClientsManager.registerWithAuth(clientAuthenticatedRegistrationDto, req) + } + @Post(SYNC_ROUTE.UNREGISTER) @UserHavePermission(USER_PERMISSION.DESKTOP_APP) @UseGuards(UserPermissionsGuard) @@ -89,6 +94,7 @@ export class SyncController { @Get(SYNC_ROUTE.APP_STORE) @AuthTokenSkip() checkAppStore(): Promise { + // This route must be public to allow clients to receive updates return this.syncClientsManager.checkAppStore() } @@ -99,7 +105,7 @@ export class SyncController { @Body() clientAuthDto: SyncClientAuthDto, @Req() req: FastifyRequest, @Res({ passthrough: true }) res: FastifyReply - ): Promise { + ): Promise { return this.syncClientsManager.authenticate(type, clientAuthDto, req.ip, res) } diff --git a/backend/src/applications/sync/sync.module.ts b/backend/src/applications/sync/sync.module.ts index 3d5a8a52..e3f39030 100644 --- a/backend/src/applications/sync/sync.module.ts +++ b/backend/src/applications/sync/sync.module.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Module } from '@nestjs/common' import { SyncDiffGzipBodyInterceptor } from './interceptors/sync-diff-gzip-body.interceptor' import { SyncClientsManager } from './services/sync-clients-manager.service' diff --git a/backend/src/applications/sync/utils/functions.ts b/backend/src/applications/sync/utils/functions.ts index e33225ca..5f1ecc0d 100644 --- a/backend/src/applications/sync/utils/functions.ts +++ b/backend/src/applications/sync/utils/functions.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import path from 'node:path' import { SYNC_FILE_NAME_PREFIX } from '../constants/sync' diff --git a/backend/src/applications/sync/utils/normalizedMap.ts b/backend/src/applications/sync/utils/normalizedMap.ts index 32e37693..86c05033 100644 --- a/backend/src/applications/sync/utils/normalizedMap.ts +++ b/backend/src/applications/sync/utils/normalizedMap.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - export class NormalizedMap extends Map { // NFC-normalized path → actual key private index = new Map() diff --git a/backend/src/applications/sync/utils/routes.ts b/backend/src/applications/sync/utils/routes.ts index 95edf5f1..22e0883e 100644 --- a/backend/src/applications/sync/utils/routes.ts +++ b/backend/src/applications/sync/utils/routes.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { PATH_TO_SPACE_SEGMENTS } from '../../spaces/utils/routes' import { SYNC_PATH_REPOSITORY } from '../constants/sync' diff --git a/backend/src/applications/users/admin-users.controller.spec.ts b/backend/src/applications/users/admin-users.controller.spec.ts index ac982296..35e8776a 100644 --- a/backend/src/applications/users/admin-users.controller.spec.ts +++ b/backend/src/applications/users/admin-users.controller.spec.ts @@ -1,15 +1,9 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { ConfigModule } from '@nestjs/config' import { JwtService } from '@nestjs/jwt' import { Test, TestingModule } from '@nestjs/testing' -import { AuthTwoFaGuard } from '../../authentication/guards/auth-two-fa-guard' -import { AuthManager } from '../../authentication/services/auth-manager.service' -import { AuthMethod2FA } from '../../authentication/services/auth-methods/auth-method-two-fa.service' +import { AuthManager } from '../../authentication/auth.service' +import { AuthProvider2FA } from '../../authentication/providers/two-fa/auth-provider-two-fa.service' +import { AuthTwoFaGuard } from '../../authentication/providers/two-fa/auth-two-fa-guard' import { exportConfiguration } from '../../configuration/config.environment' import { Cache } from '../../infrastructure/cache/services/cache.service' import { DB_TOKEN_PROVIDER } from '../../infrastructure/database/constants' @@ -33,7 +27,7 @@ describe(AdminUsersController.name, () => { provide: Cache, useValue: {} }, - { provide: AuthMethod2FA, useValue: {} }, + { provide: AuthProvider2FA, useValue: {} }, { provide: AuthTwoFaGuard, useValue: {} }, { provide: NotificationsManager, useValue: {} }, JwtService, diff --git a/backend/src/applications/users/admin-users.controller.ts b/backend/src/applications/users/admin-users.controller.ts index b38afa76..fb23d901 100644 --- a/backend/src/applications/users/admin-users.controller.ts +++ b/backend/src/applications/users/admin-users.controller.ts @@ -1,13 +1,7 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Body, Controller, Delete, Get, Param, ParseIntPipe, Patch, Post, Put, Res, Search, UseGuards } from '@nestjs/common' import { FastifyReply } from 'fastify' import { LoginResponseDto } from '../../authentication/dto/login-response.dto' -import { AuthTwoFaGuard, AuthTwoFaGuardWithoutPassword } from '../../authentication/guards/auth-two-fa-guard' +import { AuthTwoFaGuard, AuthTwoFaGuardWithoutPassword } from '../../authentication/providers/two-fa/auth-two-fa-guard' import { GROUP_TYPE } from './constants/group' import { ADMIN_USERS_ROUTE } from './constants/routes' import { USER_ROLE } from './constants/user' diff --git a/backend/src/applications/users/constants/group.ts b/backend/src/applications/users/constants/group.ts index 5e0db76d..c1eb2e95 100644 --- a/backend/src/applications/users/constants/group.ts +++ b/backend/src/applications/users/constants/group.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - export enum GROUP_TYPE { USER, PERSONAL diff --git a/backend/src/applications/users/constants/member.ts b/backend/src/applications/users/constants/member.ts index f1b19a8f..f5651a30 100644 --- a/backend/src/applications/users/constants/member.ts +++ b/backend/src/applications/users/constants/member.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - export enum MEMBER_TYPE { USER = 'user', GUEST = 'guest', diff --git a/backend/src/applications/users/constants/routes.ts b/backend/src/applications/users/constants/routes.ts index c4968828..c78e156b 100644 --- a/backend/src/applications/users/constants/routes.ts +++ b/backend/src/applications/users/constants/routes.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { ADMIN_ROUTE } from '../../admin/constants/routes' export const USERS_ROUTE = { diff --git a/backend/src/applications/users/constants/user.ts b/backend/src/applications/users/constants/user.ts index 7d200c06..f3baa2c7 100644 --- a/backend/src/applications/users/constants/user.ts +++ b/backend/src/applications/users/constants/user.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - export const USER_PASSWORD_MIN_LENGTH = 8 export const USER_MAX_PASSWORD_ATTEMPTS = 10 export const USER_LOGIN_VALIDATION = /^(?! )(?!.* $)[a-zA-Z0-9@\-\\._ ]{2,255}$/ diff --git a/backend/src/applications/users/constants/websocket.ts b/backend/src/applications/users/constants/websocket.ts index 09576b6b..2204be32 100644 --- a/backend/src/applications/users/constants/websocket.ts +++ b/backend/src/applications/users/constants/websocket.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - export const USER_ROOM_PREFIX = 'uid_' as const export const USERS_WS = { NAME_SPACE: '/', diff --git a/backend/src/applications/users/decorators/permissions.decorator.ts b/backend/src/applications/users/decorators/permissions.decorator.ts index 4f29d7e8..82a74570 100644 --- a/backend/src/applications/users/decorators/permissions.decorator.ts +++ b/backend/src/applications/users/decorators/permissions.decorator.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Reflector } from '@nestjs/core' import type { USER_PERMISSION } from '../constants/user' diff --git a/backend/src/applications/users/decorators/roles.decorator.ts b/backend/src/applications/users/decorators/roles.decorator.ts index da22be24..a2de3c98 100644 --- a/backend/src/applications/users/decorators/roles.decorator.ts +++ b/backend/src/applications/users/decorators/roles.decorator.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Reflector } from '@nestjs/core' import { USER_ROLE } from '../constants/user' diff --git a/backend/src/applications/users/decorators/user.decorator.ts b/backend/src/applications/users/decorators/user.decorator.ts index 04a106ed..657583b2 100644 --- a/backend/src/applications/users/decorators/user.decorator.ts +++ b/backend/src/applications/users/decorators/user.decorator.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { createParamDecorator, ExecutionContext } from '@nestjs/common' import { UserModel } from '../models/user.model' diff --git a/backend/src/applications/users/dto/create-or-update-group.dto.ts b/backend/src/applications/users/dto/create-or-update-group.dto.ts index 72e6d02a..17c14dd8 100644 --- a/backend/src/applications/users/dto/create-or-update-group.dto.ts +++ b/backend/src/applications/users/dto/create-or-update-group.dto.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Transform } from 'class-transformer' import { IsEnum, IsInt, IsOptional, IsString } from 'class-validator' import { GROUP_VISIBILITY } from '../constants/group' diff --git a/backend/src/applications/users/dto/create-or-update-user.dto.ts b/backend/src/applications/users/dto/create-or-update-user.dto.ts index 8b493c87..a6acf23f 100644 --- a/backend/src/applications/users/dto/create-or-update-user.dto.ts +++ b/backend/src/applications/users/dto/create-or-update-user.dto.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Transform } from 'class-transformer' import { IsArray, diff --git a/backend/src/applications/users/dto/delete-user.dto.ts b/backend/src/applications/users/dto/delete-user.dto.ts index d8d87b7b..9b3ce975 100644 --- a/backend/src/applications/users/dto/delete-user.dto.ts +++ b/backend/src/applications/users/dto/delete-user.dto.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { IsBoolean } from 'class-validator' export class DeleteUserDto { diff --git a/backend/src/applications/users/dto/search-members.dto.ts b/backend/src/applications/users/dto/search-members.dto.ts index 86ebad41..34e9923a 100644 --- a/backend/src/applications/users/dto/search-members.dto.ts +++ b/backend/src/applications/users/dto/search-members.dto.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Transform } from 'class-transformer' import { IsBoolean, IsInt, IsOptional, IsString } from 'class-validator' diff --git a/backend/src/applications/users/dto/user-properties.dto.ts b/backend/src/applications/users/dto/user-properties.dto.ts index f9da2c68..9627c4f5 100644 --- a/backend/src/applications/users/dto/user-properties.dto.ts +++ b/backend/src/applications/users/dto/user-properties.dto.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Transform } from 'class-transformer' import { IsBoolean, IsDate, IsDefined, IsEnum, IsInt, IsNotEmpty, IsOptional, IsString, MinLength, ValidateIf } from 'class-validator' import { AUTH_SCOPE } from '../../../authentication/constants/scope' diff --git a/backend/src/applications/users/guards/permissions.guard.spec.ts b/backend/src/applications/users/guards/permissions.guard.spec.ts index 273f7fe6..5c244c76 100644 --- a/backend/src/applications/users/guards/permissions.guard.spec.ts +++ b/backend/src/applications/users/guards/permissions.guard.spec.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { createMock, DeepMocked } from '@golevelup/ts-jest' import { ExecutionContext, HttpException, Logger } from '@nestjs/common' import { Reflector } from '@nestjs/core' diff --git a/backend/src/applications/users/guards/permissions.guard.ts b/backend/src/applications/users/guards/permissions.guard.ts index a7cbed64..082810ce 100644 --- a/backend/src/applications/users/guards/permissions.guard.ts +++ b/backend/src/applications/users/guards/permissions.guard.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { CanActivate, ExecutionContext, HttpException, HttpStatus, Injectable, Logger } from '@nestjs/common' import { Reflector } from '@nestjs/core' import { FastifyAuthenticatedRequest } from '../../../authentication/interfaces/auth-request.interface' diff --git a/backend/src/applications/users/guards/roles.guard.spec.ts b/backend/src/applications/users/guards/roles.guard.spec.ts index d06d8bcc..7cfa1d16 100644 --- a/backend/src/applications/users/guards/roles.guard.spec.ts +++ b/backend/src/applications/users/guards/roles.guard.spec.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { createMock, DeepMocked } from '@golevelup/ts-jest' import { ExecutionContext, Logger } from '@nestjs/common' import { Reflector } from '@nestjs/core' diff --git a/backend/src/applications/users/guards/roles.guard.ts b/backend/src/applications/users/guards/roles.guard.ts index e5312cc9..341c8699 100644 --- a/backend/src/applications/users/guards/roles.guard.ts +++ b/backend/src/applications/users/guards/roles.guard.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { CanActivate, ExecutionContext, Injectable, Logger } from '@nestjs/common' import { Reflector } from '@nestjs/core' import { FastifyAuthenticatedRequest } from '../../../authentication/interfaces/auth-request.interface' diff --git a/backend/src/applications/users/interfaces/admin-group.interface.ts b/backend/src/applications/users/interfaces/admin-group.interface.ts index 225a694a..cc21e4e9 100644 --- a/backend/src/applications/users/interfaces/admin-group.interface.ts +++ b/backend/src/applications/users/interfaces/admin-group.interface.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { GROUP_TYPE, GROUP_VISIBILITY } from '../constants/group' import type { Group } from '../schemas/group.interface' diff --git a/backend/src/applications/users/interfaces/admin-user.interface.ts b/backend/src/applications/users/interfaces/admin-user.interface.ts index 5e3cf7de..874d9b5c 100644 --- a/backend/src/applications/users/interfaces/admin-user.interface.ts +++ b/backend/src/applications/users/interfaces/admin-user.interface.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import type { User } from '../schemas/user.interface' import type { Member } from './member.interface' diff --git a/backend/src/applications/users/interfaces/group-browse.interface.ts b/backend/src/applications/users/interfaces/group-browse.interface.ts index 5404f661..b6c6bb73 100644 --- a/backend/src/applications/users/interfaces/group-browse.interface.ts +++ b/backend/src/applications/users/interfaces/group-browse.interface.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { GROUP_TYPE } from '../constants/group' import { USER_GROUP_ROLE } from '../constants/user' import type { Member } from './member.interface' diff --git a/backend/src/applications/users/interfaces/group-member.ts b/backend/src/applications/users/interfaces/group-member.ts index 9d3bbf65..296841c5 100644 --- a/backend/src/applications/users/interfaces/group-member.ts +++ b/backend/src/applications/users/interfaces/group-member.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import type { MEMBER_TYPE } from '../constants/member' import type { Group } from '../schemas/group.interface' import type { Member } from './member.interface' diff --git a/backend/src/applications/users/interfaces/guest-user.interface.ts b/backend/src/applications/users/interfaces/guest-user.interface.ts index 21a0e210..d8cf4270 100644 --- a/backend/src/applications/users/interfaces/guest-user.interface.ts +++ b/backend/src/applications/users/interfaces/guest-user.interface.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import type { User } from '../schemas/user.interface' import type { Member } from './member.interface' diff --git a/backend/src/applications/users/interfaces/member.interface.ts b/backend/src/applications/users/interfaces/member.interface.ts index 8ca13d0a..d4291010 100644 --- a/backend/src/applications/users/interfaces/member.interface.ts +++ b/backend/src/applications/users/interfaces/member.interface.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import type { CreateOrUpdateLinkDto } from '../../links/dto/create-or-update-link.dto' import type { SPACE_ROLE } from '../../spaces/constants/spaces' import type { MEMBER_TYPE } from '../constants/member' diff --git a/backend/src/applications/users/interfaces/owner.interface.ts b/backend/src/applications/users/interfaces/owner.interface.ts index c9a30226..0758827d 100644 --- a/backend/src/applications/users/interfaces/owner.interface.ts +++ b/backend/src/applications/users/interfaces/owner.interface.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - export interface Owner { id?: number login: string diff --git a/backend/src/applications/users/interfaces/user-secrets.interface.ts b/backend/src/applications/users/interfaces/user-secrets.interface.ts index f947c57b..949f6981 100644 --- a/backend/src/applications/users/interfaces/user-secrets.interface.ts +++ b/backend/src/applications/users/interfaces/user-secrets.interface.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { AUTH_SCOPE } from '../../../authentication/constants/scope' import { USER_SECRET } from '../constants/user' diff --git a/backend/src/applications/users/interfaces/websocket.interface.ts b/backend/src/applications/users/interfaces/websocket.interface.ts index 4c71f9fd..0e48146a 100644 --- a/backend/src/applications/users/interfaces/websocket.interface.ts +++ b/backend/src/applications/users/interfaces/websocket.interface.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import type { USER_ONLINE_STATUS } from '../constants/user' export interface UserOnline { diff --git a/backend/src/applications/users/models/user.model.ts b/backend/src/applications/users/models/user.model.ts index 7b8b6d70..c2589992 100644 --- a/backend/src/applications/users/models/user.model.ts +++ b/backend/src/applications/users/models/user.model.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Exclude, Expose } from 'class-transformer' import fs from 'node:fs/promises' import path from 'node:path' diff --git a/backend/src/applications/users/schemas/group.interface.ts b/backend/src/applications/users/schemas/group.interface.ts index 55c9b1f7..473ced12 100644 --- a/backend/src/applications/users/schemas/group.interface.ts +++ b/backend/src/applications/users/schemas/group.interface.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import type { groups } from './groups.schema' type GroupSchema = typeof groups.$inferSelect diff --git a/backend/src/applications/users/schemas/groups.schema.ts b/backend/src/applications/users/schemas/groups.schema.ts index b9074677..7fc40b05 100644 --- a/backend/src/applications/users/schemas/groups.schema.ts +++ b/backend/src/applications/users/schemas/groups.schema.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { sql } from 'drizzle-orm' import { AnyMySqlColumn, bigint, datetime, index, mysqlTable, tinyint, uniqueIndex, varchar } from 'drizzle-orm/mysql-core' diff --git a/backend/src/applications/users/schemas/user-group.interface.ts b/backend/src/applications/users/schemas/user-group.interface.ts index 244f3a1b..1272aaa5 100644 --- a/backend/src/applications/users/schemas/user-group.interface.ts +++ b/backend/src/applications/users/schemas/user-group.interface.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import type { usersGroups } from './users-groups.schema' type UserGroupSchema = typeof usersGroups.$inferSelect diff --git a/backend/src/applications/users/schemas/user.interface.ts b/backend/src/applications/users/schemas/user.interface.ts index a1c338d9..a83ea3df 100644 --- a/backend/src/applications/users/schemas/user.interface.ts +++ b/backend/src/applications/users/schemas/user.interface.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import type { UserSecrets } from '../interfaces/user-secrets.interface' import type { users } from './users.schema' diff --git a/backend/src/applications/users/schemas/users-groups.schema.ts b/backend/src/applications/users/schemas/users-groups.schema.ts index 5c80be0b..3bf651a8 100644 --- a/backend/src/applications/users/schemas/users-groups.schema.ts +++ b/backend/src/applications/users/schemas/users-groups.schema.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { sql } from 'drizzle-orm' import { bigint, datetime, index, mysqlTable, primaryKey, tinyint } from 'drizzle-orm/mysql-core' import { groups } from './groups.schema' diff --git a/backend/src/applications/users/schemas/users-guests.schema.ts b/backend/src/applications/users/schemas/users-guests.schema.ts index 0dd0106f..4881eb37 100644 --- a/backend/src/applications/users/schemas/users-guests.schema.ts +++ b/backend/src/applications/users/schemas/users-guests.schema.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { sql } from 'drizzle-orm' import { bigint, check, datetime, index, mysqlTable, primaryKey } from 'drizzle-orm/mysql-core' import { users } from './users.schema' diff --git a/backend/src/applications/users/schemas/users.schema.ts b/backend/src/applications/users/schemas/users.schema.ts index 9f84a000..d582fa49 100644 --- a/backend/src/applications/users/schemas/users.schema.ts +++ b/backend/src/applications/users/schemas/users.schema.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { SQL, sql } from 'drizzle-orm' import { bigint, boolean, datetime, index, mysqlTable, tinyint, uniqueIndex, varchar } from 'drizzle-orm/mysql-core' import { jsonColumn } from '../../../infrastructure/database/columns' diff --git a/backend/src/applications/users/services/admin-users-manager.service.spec.ts b/backend/src/applications/users/services/admin-users-manager.service.spec.ts index 5cdc1e8b..4998b5a4 100644 --- a/backend/src/applications/users/services/admin-users-manager.service.spec.ts +++ b/backend/src/applications/users/services/admin-users-manager.service.spec.ts @@ -1,12 +1,6 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { HttpException } from '@nestjs/common' import { Test, TestingModule } from '@nestjs/testing' -import { AuthManager } from '../../../authentication/services/auth-manager.service' +import { AuthManager } from '../../../authentication/auth.service' import { GROUP_TYPE } from '../constants/group' import { USER_GROUP_ROLE, USER_ROLE } from '../constants/user' import type { CreateOrUpdateGroupDto } from '../dto/create-or-update-group.dto' diff --git a/backend/src/applications/users/services/admin-users-manager.service.ts b/backend/src/applications/users/services/admin-users-manager.service.ts index ef04eed9..6ac7e316 100644 --- a/backend/src/applications/users/services/admin-users-manager.service.ts +++ b/backend/src/applications/users/services/admin-users-manager.service.ts @@ -1,13 +1,7 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { HttpException, HttpStatus, Injectable, Logger } from '@nestjs/common' import { FastifyReply } from 'fastify' +import { AuthManager } from '../../../authentication/auth.service' import { LoginResponseDto } from '../../../authentication/dto/login-response.dto' -import { AuthManager } from '../../../authentication/services/auth-manager.service' import { anonymizePassword, hashPassword } from '../../../common/functions' import { isPathExists, moveFiles, removeFiles } from '../../files/utils/files' import { GROUP_TYPE } from '../constants/group' diff --git a/backend/src/applications/users/services/admin-users-queries.service.ts b/backend/src/applications/users/services/admin-users-queries.service.ts index 851c4f64..f461b0f2 100644 --- a/backend/src/applications/users/services/admin-users-queries.service.ts +++ b/backend/src/applications/users/services/admin-users-queries.service.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Inject, Injectable, Logger } from '@nestjs/common' import { and, count, countDistinct, eq, inArray, isNotNull, isNull, lte, SelectedFields, SQL, sql } from 'drizzle-orm' import { alias, union } from 'drizzle-orm/mysql-core' diff --git a/backend/src/applications/users/services/users-manager.service.spec.ts b/backend/src/applications/users/services/users-manager.service.spec.ts index b8232a6e..9fb9c7f5 100755 --- a/backend/src/applications/users/services/users-manager.service.spec.ts +++ b/backend/src/applications/users/services/users-manager.service.spec.ts @@ -1,14 +1,8 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Test, TestingModule } from '@nestjs/testing' import bcrypt from 'bcryptjs' import path from 'node:path' import { Readable } from 'node:stream' -import { AuthManager } from '../../../authentication/services/auth-manager.service' +import { AuthManager } from '../../../authentication/auth.service' import { comparePassword } from '../../../common/functions' import * as imageModule from '../../../common/image' import { pngMimeType, svgMimeType } from '../../../common/image' diff --git a/backend/src/applications/users/services/users-manager.service.ts b/backend/src/applications/users/services/users-manager.service.ts index 2194c414..a1fc56d5 100755 --- a/backend/src/applications/users/services/users-manager.service.ts +++ b/backend/src/applications/users/services/users-manager.service.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { MultipartFile } from '@fastify/multipart' import { HttpException, HttpStatus, Injectable, Logger } from '@nestjs/common' import bcrypt from 'bcryptjs' @@ -71,7 +65,7 @@ export class UsersManager { return user ? new UserModel(user, removePassword) : null } - async logUser(user: UserModel, password: string, ip: string, scope?: AUTH_SCOPE): Promise { + async logUser(user: UserModel, password: string, ip: string, scope?: AUTH_SCOPE): Promise { this.validateUserAccess(user, ip) let authSuccess: boolean = await comparePassword(password, user.password) if (!authSuccess && scope) { diff --git a/backend/src/applications/users/services/users-queries.service.ts b/backend/src/applications/users/services/users-queries.service.ts index 6eef0f90..9f5c19d6 100644 --- a/backend/src/applications/users/services/users-queries.service.ts +++ b/backend/src/applications/users/services/users-queries.service.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Inject, Injectable, Logger } from '@nestjs/common' import { and, countDistinct, eq, inArray, isNotNull, like, lte, ne, notInArray, or, SelectedFields, SQL, sql } from 'drizzle-orm' import { alias } from 'drizzle-orm/mysql-core' diff --git a/backend/src/applications/users/users.controller.spec.ts b/backend/src/applications/users/users.controller.spec.ts index 44efc52d..1147f510 100755 --- a/backend/src/applications/users/users.controller.spec.ts +++ b/backend/src/applications/users/users.controller.spec.ts @@ -1,14 +1,8 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { ConfigModule } from '@nestjs/config' import { JwtService } from '@nestjs/jwt' import { Test, TestingModule } from '@nestjs/testing' -import { AuthManager } from '../../authentication/services/auth-manager.service' -import { AuthMethod2FA } from '../../authentication/services/auth-methods/auth-method-two-fa.service' +import { AuthManager } from '../../authentication/auth.service' +import { AuthProvider2FA } from '../../authentication/providers/two-fa/auth-provider-two-fa.service' import { exportConfiguration } from '../../configuration/config.environment' import { Cache } from '../../infrastructure/cache/services/cache.service' import { DB_TOKEN_PROVIDER } from '../../infrastructure/database/constants' @@ -44,7 +38,7 @@ describe(UsersController.name, () => { AdminUsersQueries, AuthManager, JwtService, - AuthMethod2FA, + AuthProvider2FA, { provide: NotificationsManager, useValue: {} } ] }).compile() diff --git a/backend/src/applications/users/users.controller.ts b/backend/src/applications/users/users.controller.ts index 50e9c0fb..9b245803 100755 --- a/backend/src/applications/users/users.controller.ts +++ b/backend/src/applications/users/users.controller.ts @@ -1,14 +1,8 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Body, Controller, Delete, Get, Header, Param, ParseIntPipe, Patch, Post, Put, Req, Search, StreamableFile, UseGuards } from '@nestjs/common' import { createReadStream } from 'fs' import { LoginResponseDto } from '../../authentication/dto/login-response.dto' -import { AuthTwoFaGuardWithoutPassword } from '../../authentication/guards/auth-two-fa-guard' import { FastifyAuthenticatedRequest } from '../../authentication/interfaces/auth-request.interface' +import { AuthTwoFaGuardWithoutPassword } from '../../authentication/providers/two-fa/auth-two-fa-guard' import { makeContentDispositionAttachment } from '../files/utils/send-file' import { USERS_ROUTE } from './constants/routes' import { USER_PERMISSION, USER_ROLE } from './constants/user' diff --git a/backend/src/applications/users/users.e2e-spec.ts b/backend/src/applications/users/users.e2e-spec.ts index 5c4b8fd4..2e829312 100644 --- a/backend/src/applications/users/users.e2e-spec.ts +++ b/backend/src/applications/users/users.e2e-spec.ts @@ -1,13 +1,7 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { NestFastifyApplication } from '@nestjs/platform-fastify' import { appBootstrap } from '../../app.bootstrap' +import { AuthManager } from '../../authentication/auth.service' import { TokenResponseDto } from '../../authentication/dto/token-response.dto' -import { AuthManager } from '../../authentication/services/auth-manager.service' import { dbCheckConnection } from '../../infrastructure/database/utils' import { API_USERS_AVATAR, API_USERS_ME } from './constants/routes' import { USER_ROLE } from './constants/user' diff --git a/backend/src/applications/users/users.gateway.spec.ts b/backend/src/applications/users/users.gateway.spec.ts index 89212a35..d54fdb23 100644 --- a/backend/src/applications/users/users.gateway.spec.ts +++ b/backend/src/applications/users/users.gateway.spec.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Test, TestingModule } from '@nestjs/testing' import { UsersManager } from './services/users-manager.service' import { WebSocketUsers } from './users.gateway' diff --git a/backend/src/applications/users/users.gateway.ts b/backend/src/applications/users/users.gateway.ts index c3a90b9d..bd5deff2 100644 --- a/backend/src/applications/users/users.gateway.ts +++ b/backend/src/applications/users/users.gateway.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { BeforeApplicationShutdown, Logger } from '@nestjs/common' import { MessageBody, diff --git a/backend/src/applications/users/users.module.ts b/backend/src/applications/users/users.module.ts index 67bf24cb..1a0b678f 100755 --- a/backend/src/applications/users/users.module.ts +++ b/backend/src/applications/users/users.module.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Global, Module } from '@nestjs/common' import { AdminUsersController } from './admin-users.controller' import { UserPermissionsGuard } from './guards/permissions.guard' diff --git a/backend/src/applications/users/utils/avatar.ts b/backend/src/applications/users/utils/avatar.ts index de5ee802..eeb635e9 100644 --- a/backend/src/applications/users/utils/avatar.ts +++ b/backend/src/applications/users/utils/avatar.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import path from 'node:path' import { convertImageToBase64 } from '../../../common/image' import { STATIC_ASSETS_PATH } from '../../../configuration/config.constants' diff --git a/backend/src/applications/users/utils/test.ts b/backend/src/applications/users/utils/test.ts index 7b614934..83a90c40 100644 --- a/backend/src/applications/users/utils/test.ts +++ b/backend/src/applications/users/utils/test.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { faker } from '@faker-js/faker' import { SERVER_NAME } from '../../../common/shared' import { USER_PERMISSION, USER_ROLE } from '../constants/user' diff --git a/backend/src/applications/webdav/constants/routes.ts b/backend/src/applications/webdav/constants/routes.ts index f76bdeca..0f1379dd 100644 --- a/backend/src/applications/webdav/constants/routes.ts +++ b/backend/src/applications/webdav/constants/routes.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { SERVER_NAME } from '../../../common/shared' import { SPACE_ALIAS, SPACE_REPOSITORY } from '../../spaces/constants/spaces' diff --git a/backend/src/applications/webdav/constants/webdav.ts b/backend/src/applications/webdav/constants/webdav.ts index ab22001c..80444451 100644 --- a/backend/src/applications/webdav/constants/webdav.ts +++ b/backend/src/applications/webdav/constants/webdav.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { SERVER_NAME } from '../../../common/shared' import { HTTP_STANDARD_METHOD, HTTP_WEBDAV_METHOD } from '../../applications.constants' import { WEBDAV_BASE_PATH } from './routes' diff --git a/backend/src/applications/webdav/decorators/if-header.decorator.ts b/backend/src/applications/webdav/decorators/if-header.decorator.ts index cb7a66f7..4c4dfa89 100644 --- a/backend/src/applications/webdav/decorators/if-header.decorator.ts +++ b/backend/src/applications/webdav/decorators/if-header.decorator.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { FastifyReply } from 'fastify' import { FastifyDAVRequest } from '../interfaces/webdav.interface' diff --git a/backend/src/applications/webdav/decorators/webdav-context.decorator.ts b/backend/src/applications/webdav/decorators/webdav-context.decorator.ts index 958c0ebb..c6be16e2 100644 --- a/backend/src/applications/webdav/decorators/webdav-context.decorator.ts +++ b/backend/src/applications/webdav/decorators/webdav-context.decorator.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { applyDecorators, SetMetadata, UseFilters, UseGuards } from '@nestjs/common' import { AuthBasicGuard } from '../../../authentication/guards/auth-basic.guard' import { WebDAVExceptionsFilter } from '../filters/webdav.filter' diff --git a/backend/src/applications/webdav/filters/webdav.filter.spec.ts b/backend/src/applications/webdav/filters/webdav.filter.spec.ts index 6322a6f0..766e1446 100644 --- a/backend/src/applications/webdav/filters/webdav.filter.spec.ts +++ b/backend/src/applications/webdav/filters/webdav.filter.spec.ts @@ -1,8 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ import { ArgumentsHost, HttpException } from '@nestjs/common' import { Test } from '@nestjs/testing' import { SERVER_NAME } from '../../../common/shared' diff --git a/backend/src/applications/webdav/filters/webdav.filter.ts b/backend/src/applications/webdav/filters/webdav.filter.ts index b7bf1afa..9e3e6a42 100644 --- a/backend/src/applications/webdav/filters/webdav.filter.ts +++ b/backend/src/applications/webdav/filters/webdav.filter.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { ArgumentsHost, Catch, ExceptionFilter, HttpException } from '@nestjs/common' import { SERVER_NAME } from '../../../common/shared' import { XML_CONTENT_TYPE } from '../constants/webdav' diff --git a/backend/src/applications/webdav/guards/webdav-protocol.guard.ts b/backend/src/applications/webdav/guards/webdav-protocol.guard.ts index 943711e1..a5c33e52 100644 --- a/backend/src/applications/webdav/guards/webdav-protocol.guard.ts +++ b/backend/src/applications/webdav/guards/webdav-protocol.guard.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { CanActivate, ExecutionContext, HttpException, HttpStatus, Injectable, Logger } from '@nestjs/common' import { ValidationError } from 'fast-xml-parser' import { FastifyReply } from 'fastify' diff --git a/backend/src/applications/webdav/interfaces/if-header.interface.ts b/backend/src/applications/webdav/interfaces/if-header.interface.ts index 6e615f0e..d109efd2 100644 --- a/backend/src/applications/webdav/interfaces/if-header.interface.ts +++ b/backend/src/applications/webdav/interfaces/if-header.interface.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - export interface IfHeader { path?: string token?: { mustMatch: boolean; value: string } diff --git a/backend/src/applications/webdav/interfaces/webdav.interface.ts b/backend/src/applications/webdav/interfaces/webdav.interface.ts index 41dca8a3..c2f84a9c 100644 --- a/backend/src/applications/webdav/interfaces/webdav.interface.ts +++ b/backend/src/applications/webdav/interfaces/webdav.interface.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { FastifyAuthenticatedRequest } from '../../../authentication/interfaces/auth-request.interface' import { SpaceEnv } from '../../spaces/models/space-env.model' import { DEPTH, LOCK_SCOPE, PROPSTAT } from '../constants/webdav' diff --git a/backend/src/applications/webdav/models/webdav-file.model.ts b/backend/src/applications/webdav/models/webdav-file.model.ts index 8ea92ec3..48154b13 100644 --- a/backend/src/applications/webdav/models/webdav-file.model.ts +++ b/backend/src/applications/webdav/models/webdav-file.model.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import path from 'node:path' import { encodeUrl } from '../../../common/shared' diff --git a/backend/src/applications/webdav/services/webdav-methods.service.spec.ts b/backend/src/applications/webdav/services/webdav-methods.service.spec.ts index 994d3446..2ccb604a 100644 --- a/backend/src/applications/webdav/services/webdav-methods.service.spec.ts +++ b/backend/src/applications/webdav/services/webdav-methods.service.spec.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { HttpException, HttpStatus } from '@nestjs/common' import { Test, TestingModule } from '@nestjs/testing' import { HTTP_VERSION } from '../../applications.constants' diff --git a/backend/src/applications/webdav/services/webdav-methods.service.ts b/backend/src/applications/webdav/services/webdav-methods.service.ts index 4a4229cb..dd976c63 100644 --- a/backend/src/applications/webdav/services/webdav-methods.service.ts +++ b/backend/src/applications/webdav/services/webdav-methods.service.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { HttpException, HttpStatus, Injectable, Logger, StreamableFile } from '@nestjs/common' import { FastifyReply } from 'fastify' import { currentTimeStamp, encodeUrl } from '../../../common/shared' diff --git a/backend/src/applications/webdav/services/webdav-spaces.service.spec.ts b/backend/src/applications/webdav/services/webdav-spaces.service.spec.ts index c457c380..e9a2412a 100644 --- a/backend/src/applications/webdav/services/webdav-spaces.service.spec.ts +++ b/backend/src/applications/webdav/services/webdav-spaces.service.spec.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { HttpException, HttpStatus } from '@nestjs/common' import { Test, TestingModule } from '@nestjs/testing' import { getProps, isPathExists, isPathIsDir } from '../../files/utils/files' diff --git a/backend/src/applications/webdav/services/webdav-spaces.service.ts b/backend/src/applications/webdav/services/webdav-spaces.service.ts index 245a5be9..1cf52831 100644 --- a/backend/src/applications/webdav/services/webdav-spaces.service.ts +++ b/backend/src/applications/webdav/services/webdav-spaces.service.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { HttpException, HttpStatus, Injectable, Logger } from '@nestjs/common' import { getProps, isPathExists, isPathIsDir } from '../../files/utils/files' import { SPACE_REPOSITORY } from '../../spaces/constants/spaces' diff --git a/backend/src/applications/webdav/utils/bootstrap.ts b/backend/src/applications/webdav/utils/bootstrap.ts index f39eb1d9..c9e1e2a6 100644 --- a/backend/src/applications/webdav/utils/bootstrap.ts +++ b/backend/src/applications/webdav/utils/bootstrap.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2026 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { NestFastifyApplication } from '@nestjs/platform-fastify' import { FastifyInstance } from 'fastify' import { HTTP_METHOD, HTTP_WEBDAV_METHOD } from '../../applications.constants' diff --git a/backend/src/applications/webdav/utils/if-header.ts b/backend/src/applications/webdav/utils/if-header.ts index d4a1a1af..d2d6edae 100644 --- a/backend/src/applications/webdav/utils/if-header.ts +++ b/backend/src/applications/webdav/utils/if-header.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { urlToPath } from '../../../common/functions' import { IfHeader } from '../interfaces/if-header.interface' diff --git a/backend/src/applications/webdav/utils/routes.ts b/backend/src/applications/webdav/utils/routes.ts index d32c4f37..4403d725 100644 --- a/backend/src/applications/webdav/utils/routes.ts +++ b/backend/src/applications/webdav/utils/routes.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { PATH_TO_SPACE_SEGMENTS } from '../../spaces/utils/routes' import { WEBDAV_BASE_PATH, WEBDAV_SPACES } from '../constants/routes' diff --git a/backend/src/applications/webdav/utils/webdav.ts b/backend/src/applications/webdav/utils/webdav.ts index 181c023d..f969e128 100644 --- a/backend/src/applications/webdav/utils/webdav.ts +++ b/backend/src/applications/webdav/utils/webdav.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { FastifyReply } from 'fastify' import http from 'node:http' import { currentTimeStamp, encodeUrl, SERVER_NAME } from '../../../common/shared' diff --git a/backend/src/applications/webdav/utils/xml.ts b/backend/src/applications/webdav/utils/xml.ts index 79bf3339..d1ea7293 100644 --- a/backend/src/applications/webdav/utils/xml.ts +++ b/backend/src/applications/webdav/utils/xml.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { XMLBuilder, XMLParser, XMLValidator } from 'fast-xml-parser' export const XML_NS_PREFIX = '@_' diff --git a/backend/src/applications/webdav/webdav.controller.spec.ts b/backend/src/applications/webdav/webdav.controller.spec.ts index 3deef035..4ccbb7ea 100644 --- a/backend/src/applications/webdav/webdav.controller.spec.ts +++ b/backend/src/applications/webdav/webdav.controller.spec.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Test, TestingModule } from '@nestjs/testing' import { SpacesManager } from '../spaces/services/spaces-manager.service' import { WebDAVMethods } from './services/webdav-methods.service' diff --git a/backend/src/applications/webdav/webdav.controller.ts b/backend/src/applications/webdav/webdav.controller.ts index 83d371bd..46b7e29a 100644 --- a/backend/src/applications/webdav/webdav.controller.ts +++ b/backend/src/applications/webdav/webdav.controller.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { All, Controller, HttpStatus, Options, Param, Propfind, Req, Res, StreamableFile, UseGuards } from '@nestjs/common' import { FastifyReply } from 'fastify' import { HTTP_METHOD } from '../applications.constants' diff --git a/backend/src/applications/webdav/webdav.e2e-spec.ts b/backend/src/applications/webdav/webdav.e2e-spec.ts index e169ffcb..af1e45fc 100644 --- a/backend/src/applications/webdav/webdav.e2e-spec.ts +++ b/backend/src/applications/webdav/webdav.e2e-spec.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { NestFastifyApplication } from '@nestjs/platform-fastify' import { appBootstrap } from '../../app.bootstrap' import { XML_CONTENT_TYPE } from './constants/webdav' diff --git a/backend/src/applications/webdav/webdav.module.ts b/backend/src/applications/webdav/webdav.module.ts index 57e5e04d..94a949f0 100644 --- a/backend/src/applications/webdav/webdav.module.ts +++ b/backend/src/applications/webdav/webdav.module.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Module } from '@nestjs/common' import { WebDAVProtocolGuard } from './guards/webdav-protocol.guard' import { WebDAVMethods } from './services/webdav-methods.service' diff --git a/backend/src/authentication/auth.config.ts b/backend/src/authentication/auth.config.ts index e1eb9402..f8c195d8 100644 --- a/backend/src/authentication/auth.config.ts +++ b/backend/src/authentication/auth.config.ts @@ -1,45 +1,10 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - -import { Exclude, Transform, Type } from 'class-transformer' -import { - ArrayNotEmpty, - IsArray, - IsBoolean, - IsDefined, - IsEnum, - IsIn, - IsNotEmpty, - IsNotEmptyObject, - IsObject, - IsOptional, - IsString, - ValidateIf, - ValidateNested -} from 'class-validator' -import { SERVER_NAME } from '../common/shared' +import { Exclude, Type } from 'class-transformer' +import { IsDefined, IsEnum, IsIn, IsNotEmpty, IsNotEmptyObject, IsObject, IsOptional, IsString, ValidateIf, ValidateNested } from 'class-validator' import { ACCESS_KEY, CSRF_KEY, REFRESH_KEY, WS_KEY } from './constants/auth' -import { LDAP_COMMON_ATTR, LDAP_LOGIN_ATTR } from './constants/auth-ldap' - -export class AuthMfaTotpConfig { - @IsBoolean() - enabled = true - - @IsString() - issuer = SERVER_NAME -} - -export class AuthMfaConfig { - @IsDefined() - @IsNotEmptyObject() - @IsObject() - @ValidateNested() - @Type(() => AuthMfaTotpConfig) - totp: AuthMfaTotpConfig = new AuthMfaTotpConfig() -} +import { AUTH_PROVIDER } from './providers/auth-providers.constants' +import { AuthProviderLDAPConfig } from './providers/ldap/auth-ldap.config' +import { AuthProviderOIDCConfig } from './providers/oidc/auth-oidc.config' +import { AuthMFAConfig } from './providers/two-fa/auth-two-fa.config' export class AuthTokenAccessConfig { @Exclude({ toClassOnly: true }) @@ -111,58 +76,10 @@ export class AuthTokenConfig { ws: AuthTokenWSConfig } -export class AuthMethodLdapAttributesConfig { - @IsOptional() - @IsString() - @Transform(({ value }) => value || LDAP_LOGIN_ATTR.UID) - @IsEnum(LDAP_LOGIN_ATTR) - login: LDAP_LOGIN_ATTR = LDAP_LOGIN_ATTR.UID - - @IsOptional() - @IsString() - @Transform(({ value }) => value || LDAP_COMMON_ATTR.MAIL) - email: string = LDAP_COMMON_ATTR.MAIL -} - -export class AuthMethodLdapConfig { - @Transform(({ value }) => (Array.isArray(value) ? value.filter((v: string) => Boolean(v)) : value)) - @ArrayNotEmpty() - @IsArray() - @IsString({ each: true }) - servers: string[] - - @IsString() - @IsNotEmpty() - baseDN: string - - @IsOptional() - @IsString() - filter?: string - - @IsDefined() - @IsNotEmptyObject() - @IsObject() - @ValidateNested() - @Type(() => AuthMethodLdapAttributesConfig) - attributes: AuthMethodLdapAttributesConfig = new AuthMethodLdapAttributesConfig() - - @IsOptional() - @IsString() - adminGroup?: string - - @IsOptional() - @IsString() - upnSuffix?: string - - @IsOptional() - @IsString() - netbiosName?: string -} - export class AuthConfig { @IsString() - @IsIn(['mysql', 'ldap']) - method: 'mysql' | 'ldap' = 'mysql' + @IsEnum(AUTH_PROVIDER) + provider: AUTH_PROVIDER = AUTH_PROVIDER.MYSQL @IsOptional() @IsString() @@ -172,8 +89,8 @@ export class AuthConfig { @IsNotEmptyObject() @IsObject() @ValidateNested() - @Type(() => AuthMfaConfig) - mfa: AuthMfaConfig = new AuthMfaConfig() + @Type(() => AuthMFAConfig) + mfa: AuthMFAConfig = new AuthMFAConfig() @IsString() @IsIn(['lax', 'strict']) @@ -186,10 +103,17 @@ export class AuthConfig { @Type(() => AuthTokenConfig) token: AuthTokenConfig - @ValidateIf((o: AuthConfig) => o.method === 'ldap') + @ValidateIf((o: AuthConfig) => o.provider === AUTH_PROVIDER.LDAP) + @IsDefined() + @IsObject() + @ValidateNested() + @Type(() => AuthProviderLDAPConfig) + ldap: AuthProviderLDAPConfig + + @ValidateIf((o: AuthConfig) => o.provider === AUTH_PROVIDER.OIDC) @IsDefined() @IsObject() @ValidateNested() - @Type(() => AuthMethodLdapConfig) - ldap: AuthMethodLdapConfig + @Type(() => AuthProviderOIDCConfig) + oidc: AuthProviderOIDCConfig } diff --git a/backend/src/authentication/auth.controller.spec.ts b/backend/src/authentication/auth.controller.spec.ts index d6a679a7..63341d81 100755 --- a/backend/src/authentication/auth.controller.spec.ts +++ b/backend/src/authentication/auth.controller.spec.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { ConfigModule, ConfigService } from '@nestjs/config' import { JwtService } from '@nestjs/jwt' import { PassportModule } from '@nestjs/passport' @@ -19,12 +13,12 @@ import { Cache } from '../infrastructure/cache/services/cache.service' import { DB_TOKEN_PROVIDER } from '../infrastructure/database/constants' import { AuthConfig } from './auth.config' import { AuthController } from './auth.controller' +import { AuthManager } from './auth.service' import { TOKEN_PATHS } from './constants/auth' import { LoginResponseDto } from './dto/login-response.dto' -import { AuthTwoFaGuard } from './guards/auth-two-fa-guard' import { TOKEN_TYPE } from './interfaces/token.interface' -import { AuthManager } from './services/auth-manager.service' -import { AuthMethod2FA } from './services/auth-methods/auth-method-two-fa.service' +import { AuthProvider2FA } from './providers/two-fa/auth-provider-two-fa.service' +import { AuthTwoFaGuard } from './providers/two-fa/auth-two-fa-guard' describe(AuthController.name, () => { let module: TestingModule @@ -40,7 +34,7 @@ describe(AuthController.name, () => { ConfigService, AuthManager, JwtService, - AuthMethod2FA, + AuthProvider2FA, AuthTwoFaGuard, { provide: DB_TOKEN_PROVIDER, useValue: {} }, { provide: Cache, useValue: {} }, diff --git a/backend/src/authentication/auth.controller.ts b/backend/src/authentication/auth.controller.ts index 98d8e8e7..546722b1 100755 --- a/backend/src/authentication/auth.controller.ts +++ b/backend/src/authentication/auth.controller.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Body, Controller, Get, Param, ParseIntPipe, Post, Req, Res, UseGuards } from '@nestjs/common' import { FastifyReply } from 'fastify' import { USER_ROLE } from '../applications/users/constants/user' @@ -11,26 +5,27 @@ import { UserHaveRole } from '../applications/users/decorators/roles.decorator' import { GetUser } from '../applications/users/decorators/user.decorator' import { UserRolesGuard } from '../applications/users/guards/roles.guard' import { UserModel } from '../applications/users/models/user.model' +import { AuthManager } from './auth.service' import { ACCESS_KEY, TOKEN_PATHS } from './constants/auth' import { AUTH_ROUTE } from './constants/routes' import { AuthTokenSkip } from './decorators/auth-token-skip.decorator' -import { LoginResponseDto, LoginVerify2FaDto, TwoFaResponseDto } from './dto/login-response.dto' +import { LoginResponseDto, LoginVerify2FaDto } from './dto/login-response.dto' import { TokenResponseDto } from './dto/token-response.dto' -import { TwoFaVerifyDto, TwoFaVerifyWithPasswordDto } from './dto/two-fa-verify.dto' import { AuthLocalGuard } from './guards/auth-local.guard' import { AuthTokenRefreshGuard } from './guards/auth-token-refresh.guard' -import { AuthTwoFaGuard } from './guards/auth-two-fa-guard' import { FastifyAuthenticatedRequest } from './interfaces/auth-request.interface' import { TOKEN_TYPE } from './interfaces/token.interface' -import { TwoFaSetup, TwoFaVerifyResult } from './interfaces/two-fa-setup.interface' -import { AuthManager } from './services/auth-manager.service' -import { AuthMethod2FA } from './services/auth-methods/auth-method-two-fa.service' +import type { AuthOIDCSettings } from './providers/oidc/auth-oidc.interfaces' +import { AuthProvider2FA } from './providers/two-fa/auth-provider-two-fa.service' +import { AuthTwoFaGuard } from './providers/two-fa/auth-two-fa-guard' +import { TwoFaResponseDto, TwoFaVerifyDto, TwoFaVerifyWithPasswordDto } from './providers/two-fa/auth-two-fa.dtos' +import { TwoFaSetup, TwoFaVerifyResult } from './providers/two-fa/auth-two-fa.interfaces' @Controller(AUTH_ROUTE.BASE) export class AuthController { constructor( private readonly authManager: AuthManager, - private readonly authMethod2FA: AuthMethod2FA + private readonly authProvider2FA: AuthProvider2FA ) {} @Post(AUTH_ROUTE.LOGIN) @@ -67,27 +62,33 @@ export class AuthController { return this.authManager.getTokens(user, true) } + @Get(AUTH_ROUTE.SETTINGS) + @AuthTokenSkip() + authSettings(): AuthOIDCSettings | false { + return this.authManager.authSettings() + } + /* TWO-FA Part */ @Get(`${AUTH_ROUTE.TWO_FA_BASE}/${AUTH_ROUTE.TWO_FA_ENABLE}`) @UseGuards(UserRolesGuard) @UserHaveRole(USER_ROLE.USER) twoFaInit(@GetUser() user: UserModel): Promise { - return this.authMethod2FA.initTwoFactor(user) + return this.authProvider2FA.initTwoFactor(user) } @Post(`${AUTH_ROUTE.TWO_FA_BASE}/${AUTH_ROUTE.TWO_FA_ENABLE}`) @UseGuards(UserRolesGuard) @UserHaveRole(USER_ROLE.USER) twoFaEnable(@Body() body: TwoFaVerifyWithPasswordDto, @Req() req: FastifyAuthenticatedRequest): Promise { - return this.authMethod2FA.enableTwoFactor(body, req) + return this.authProvider2FA.enableTwoFactor(body, req) } @Post(`${AUTH_ROUTE.TWO_FA_BASE}/${AUTH_ROUTE.TWO_FA_DISABLE}`) @UseGuards(UserRolesGuard) @UserHaveRole(USER_ROLE.USER) twoFaDisable(@Body() body: TwoFaVerifyWithPasswordDto, @Req() req: FastifyAuthenticatedRequest): Promise { - return this.authMethod2FA.disableTwoFactor(body, req) + return this.authProvider2FA.disableTwoFactor(body, req) } @Post(`${AUTH_ROUTE.TWO_FA_BASE}/${AUTH_ROUTE.TWO_FA_LOGIN_VERIFY}`) @@ -98,7 +99,7 @@ export class AuthController { @Req() req: FastifyAuthenticatedRequest, @Res({ passthrough: true }) res: FastifyReply ): Promise { - const [authStatus, user] = await this.authMethod2FA.verify(body, req, true) + const [authStatus, user] = await this.authProvider2FA.verify(body, req, true) if (authStatus.success) { const loginResponseDto = await this.authManager.setCookies(user, res) // clear the temporary 2FA cookie @@ -112,6 +113,6 @@ export class AuthController { @UseGuards(UserRolesGuard, AuthTwoFaGuard) @UserHaveRole(USER_ROLE.ADMINISTRATOR) twoFaReset(@Param('id', ParseIntPipe) userId: number): Promise { - return this.authMethod2FA.adminResetUserTwoFa(userId) + return this.authProvider2FA.adminResetUserTwoFa(userId) } } diff --git a/backend/src/authentication/auth.e2e-spec.ts b/backend/src/authentication/auth.e2e-spec.ts index 20509ffb..4f00412a 100644 --- a/backend/src/authentication/auth.e2e-spec.ts +++ b/backend/src/authentication/auth.e2e-spec.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { ConfigService } from '@nestjs/config' import { JwtService } from '@nestjs/jwt' import { NestFastifyApplication } from '@nestjs/platform-fastify' diff --git a/backend/src/authentication/auth.module.ts b/backend/src/authentication/auth.module.ts index d1afc9a7..4919e7cc 100755 --- a/backend/src/authentication/auth.module.ts +++ b/backend/src/authentication/auth.module.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Global, Module } from '@nestjs/common' import { APP_GUARD } from '@nestjs/core' import { JwtModule } from '@nestjs/jwt' @@ -11,6 +5,7 @@ import { PassportModule } from '@nestjs/passport' import { UsersModule } from '../applications/users/users.module' import { configuration } from '../configuration/config.environment' import { AuthController } from './auth.controller' +import { AuthManager } from './auth.service' import { AuthAnonymousGuard } from './guards/auth-anonymous.guard' import { AuthAnonymousStrategy } from './guards/auth-anonymous.strategy' import { AuthBasicGuard } from './guards/auth-basic.guard' @@ -21,15 +16,20 @@ import { AuthTokenAccessGuard } from './guards/auth-token-access.guard' import { AuthTokenAccessStrategy } from './guards/auth-token-access.strategy' import { AuthTokenRefreshGuard } from './guards/auth-token-refresh.guard' import { AuthTokenRefreshStrategy } from './guards/auth-token-refresh.strategy' -import { AuthMethod } from './models/auth-method' -import { AuthManager } from './services/auth-manager.service' -import { AuthMethodDatabase } from './services/auth-methods/auth-method-database.service' -import { AuthMethodLdapService } from './services/auth-methods/auth-method-ldap.service' -import { AuthMethod2FA } from './services/auth-methods/auth-method-two-fa.service' +import { AUTH_PROVIDER } from './providers/auth-providers.constants' +import { AuthProvider } from './providers/auth-providers.models' +import { selectAuthProvider } from './providers/auth-providers.utils' +import { AuthProviderOIDCModule } from './providers/oidc/auth-provider-oidc.module' +import { AuthProvider2FA } from './providers/two-fa/auth-provider-two-fa.service' @Global() @Module({ - imports: [JwtModule.register({ global: true }), UsersModule, PassportModule], + imports: [ + JwtModule.register({ global: true }), + UsersModule, + PassportModule, + ...(configuration.auth.provider === AUTH_PROVIDER.OIDC ? [AuthProviderOIDCModule] : []) + ], controllers: [AuthController], providers: [ { @@ -46,9 +46,9 @@ import { AuthMethod2FA } from './services/auth-methods/auth-method-two-fa.servic AuthBasicStrategy, AuthAnonymousStrategy, AuthManager, - AuthMethod2FA, - { provide: AuthMethod, useClass: configuration.auth.method === 'ldap' ? AuthMethodLdapService : AuthMethodDatabase } + AuthProvider2FA, + selectAuthProvider(configuration.auth.provider) ], - exports: [AuthManager, AuthMethod, AuthMethod2FA] + exports: [AuthManager, AuthProvider, AuthProvider2FA] }) export class AuthModule {} diff --git a/backend/src/authentication/services/auth-manager.service.spec.ts b/backend/src/authentication/auth.service.spec.ts similarity index 70% rename from backend/src/authentication/services/auth-manager.service.spec.ts rename to backend/src/authentication/auth.service.spec.ts index 9a717e86..18c183fd 100755 --- a/backend/src/authentication/services/auth-manager.service.spec.ts +++ b/backend/src/authentication/auth.service.spec.ts @@ -1,13 +1,7 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { ConfigService } from '@nestjs/config' import { JwtService } from '@nestjs/jwt' import { Test, TestingModule } from '@nestjs/testing' -import { AuthManager } from './auth-manager.service' +import { AuthManager } from './auth.service' describe(AuthManager.name, () => { let authManager: AuthManager diff --git a/backend/src/authentication/services/auth-manager.service.ts b/backend/src/authentication/auth.service.ts similarity index 86% rename from backend/src/authentication/services/auth-manager.service.ts rename to backend/src/authentication/auth.service.ts index 558b2dee..d30903b4 100755 --- a/backend/src/authentication/services/auth-manager.service.ts +++ b/backend/src/authentication/auth.service.ts @@ -1,23 +1,21 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ import { unsign, UnsignResult } from '@fastify/cookie' import { HttpException, HttpStatus, Injectable, Logger } from '@nestjs/common' import { JwtService } from '@nestjs/jwt' import { FastifyReply, FastifyRequest } from 'fastify' import crypto from 'node:crypto' -import { HTTP_CSRF_IGNORED_METHODS } from '../../applications/applications.constants' -import { UserModel } from '../../applications/users/models/user.model' -import { convertHumanTimeToSeconds } from '../../common/functions' -import { currentTimeStamp } from '../../common/shared' -import { configuration, serverConfig } from '../../configuration/config.environment' -import { CSRF_ERROR, CSRF_KEY, TOKEN_2FA_TYPES, TOKEN_PATHS, TOKEN_TYPES } from '../constants/auth' -import { LoginResponseDto, LoginVerify2FaDto } from '../dto/login-response.dto' -import { TokenResponseDto } from '../dto/token-response.dto' -import { JwtIdentity2FaPayload, JwtIdentityPayload, JwtPayload } from '../interfaces/jwt-payload.interface' -import { TOKEN_TYPE } from '../interfaces/token.interface' +import { HTTP_CSRF_IGNORED_METHODS } from '../applications/applications.constants' +import { UserModel } from '../applications/users/models/user.model' +import { convertHumanTimeToSeconds } from '../common/functions' +import { currentTimeStamp } from '../common/shared' +import { configuration, serverConfig } from '../configuration/config.environment' +import { CSRF_ERROR, CSRF_KEY, TOKEN_2FA_TYPES, TOKEN_PATHS, TOKEN_TYPES } from './constants/auth' +import { API_OIDC_LOGIN } from './constants/routes' +import { LoginResponseDto, LoginVerify2FaDto } from './dto/login-response.dto' +import { TokenResponseDto } from './dto/token-response.dto' +import { JwtIdentity2FaPayload, JwtIdentityPayload, JwtPayload } from './interfaces/jwt-payload.interface' +import { TOKEN_TYPE } from './interfaces/token.interface' +import { AUTH_PROVIDER } from './providers/auth-providers.constants' +import type { AuthOIDCSettings } from './providers/oidc/auth-oidc.interfaces' @Injectable() export class AuthManager { @@ -138,6 +136,17 @@ export class AuthManager { } } + authSettings(): AuthOIDCSettings | false { + if (configuration.auth.provider !== AUTH_PROVIDER.OIDC) { + return false + } + return { + loginUrl: API_OIDC_LOGIN, + autoRedirect: configuration.auth.oidc.options.autoRedirect, + buttonText: configuration.auth.oidc.options.buttonText + } + } + private jwtSign(user: UserModel, type: TOKEN_TYPE, expiration: number, csrfToken?: string): Promise { return this.jwt.signAsync( { diff --git a/backend/src/authentication/constants/auth.ts b/backend/src/authentication/constants/auth.ts index 93055652..8df68348 100644 --- a/backend/src/authentication/constants/auth.ts +++ b/backend/src/authentication/constants/auth.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { TOKEN_TYPE } from '../interfaces/token.interface' import { API_AUTH_REFRESH, API_AUTH_WS, API_TWO_FA_LOGIN_VERIFY } from './routes' diff --git a/backend/src/authentication/constants/routes.ts b/backend/src/authentication/constants/routes.ts index f5e6efe2..a5ab22aa 100644 --- a/backend/src/authentication/constants/routes.ts +++ b/backend/src/authentication/constants/routes.ts @@ -1,22 +1,20 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - -export enum AUTH_ROUTE { - BASE = '/api/auth', - LOGIN = 'login', - LOGOUT = 'logout', - REFRESH = 'refresh', - TOKEN = 'token', - TOKEN_REFRESH = `${AUTH_ROUTE.TOKEN}/refresh`, - WS = 'socket.io', - TWO_FA_BASE = '2fa', - TWO_FA_ENABLE = 'enable', - TWO_FA_DISABLE = 'disable', - TWO_FA_LOGIN_VERIFY = 'login/verify', - TWO_FA_ADMIN_RESET_USER = 'reset/user' +export const AUTH_ROUTE = { + BASE: '/api/auth', + LOGIN: 'login', + LOGOUT: 'logout', + REFRESH: 'refresh', + TOKEN: 'token', + TOKEN_REFRESH: 'token/refresh', + SETTINGS: 'settings', + WS: 'socket.io', + TWO_FA_BASE: '2fa', + TWO_FA_ENABLE: 'enable', + TWO_FA_DISABLE: 'disable', + TWO_FA_LOGIN_VERIFY: 'login/verify', + TWO_FA_ADMIN_RESET_USER: 'reset/user', + OIDC_LOGIN: 'oidc/login', + OIDC_CALLBACK: 'oidc/callback', + OIDC_SETTINGS: 'oidc/settings' } export const API_AUTH_LOGIN = `${AUTH_ROUTE.BASE}/${AUTH_ROUTE.LOGIN}` @@ -24,8 +22,11 @@ export const API_AUTH_LOGOUT = `${AUTH_ROUTE.BASE}/${AUTH_ROUTE.LOGOUT}` export const API_AUTH_REFRESH = `${AUTH_ROUTE.BASE}/${AUTH_ROUTE.REFRESH}` export const API_AUTH_TOKEN = `${AUTH_ROUTE.BASE}/${AUTH_ROUTE.TOKEN}` export const API_AUTH_TOKEN_REFRESH = `${AUTH_ROUTE.BASE}/${AUTH_ROUTE.TOKEN_REFRESH}` +export const API_AUTH_SETTINGS = `${AUTH_ROUTE.BASE}/${AUTH_ROUTE.SETTINGS}` export const API_AUTH_WS = `/${AUTH_ROUTE.WS}` export const API_TWO_FA_ENABLE = `${AUTH_ROUTE.BASE}/${AUTH_ROUTE.TWO_FA_BASE}/${AUTH_ROUTE.TWO_FA_ENABLE}` export const API_TWO_FA_DISABLE = `${AUTH_ROUTE.BASE}/${AUTH_ROUTE.TWO_FA_BASE}/${AUTH_ROUTE.TWO_FA_DISABLE}` export const API_TWO_FA_LOGIN_VERIFY = `${AUTH_ROUTE.BASE}/${AUTH_ROUTE.TWO_FA_BASE}/${AUTH_ROUTE.TWO_FA_LOGIN_VERIFY}` export const API_TWO_FA_ADMIN_RESET_USER = `${AUTH_ROUTE.BASE}/${AUTH_ROUTE.TWO_FA_BASE}/${AUTH_ROUTE.TWO_FA_ADMIN_RESET_USER}` +export const API_OIDC_LOGIN = `${AUTH_ROUTE.BASE}/${AUTH_ROUTE.OIDC_LOGIN}` +export const API_OIDC_CALLBACK = `${AUTH_ROUTE.BASE}/${AUTH_ROUTE.OIDC_CALLBACK}` diff --git a/backend/src/authentication/constants/scope.ts b/backend/src/authentication/constants/scope.ts index c9817ad3..38b43aae 100644 --- a/backend/src/authentication/constants/scope.ts +++ b/backend/src/authentication/constants/scope.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - export enum AUTH_SCOPE { WEBDAV = 'webdav' } diff --git a/backend/src/authentication/decorators/auth-token-optional.decorator.ts b/backend/src/authentication/decorators/auth-token-optional.decorator.ts index b6c417f1..1bb2cf98 100644 --- a/backend/src/authentication/decorators/auth-token-optional.decorator.ts +++ b/backend/src/authentication/decorators/auth-token-optional.decorator.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { applyDecorators, UseGuards } from '@nestjs/common' import { AuthGuard } from '@nestjs/passport' import { AuthTokenSkip } from './auth-token-skip.decorator' diff --git a/backend/src/authentication/decorators/auth-token-skip.decorator.ts b/backend/src/authentication/decorators/auth-token-skip.decorator.ts index cee7f372..e3060886 100755 --- a/backend/src/authentication/decorators/auth-token-skip.decorator.ts +++ b/backend/src/authentication/decorators/auth-token-skip.decorator.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { SetMetadata } from '@nestjs/common' export const AUTH_TOKEN_SKIP = 'authTokenSkip' diff --git a/backend/src/authentication/dto/login-response.dto.ts b/backend/src/authentication/dto/login-response.dto.ts index 631baa9d..31140c08 100644 --- a/backend/src/authentication/dto/login-response.dto.ts +++ b/backend/src/authentication/dto/login-response.dto.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { UserModel } from '../../applications/users/models/user.model' import { ServerConfig } from '../../configuration/config.interfaces' import { TokenResponseDto } from './token-response.dto' @@ -22,7 +16,7 @@ export class LoginResponseDto { export class LoginVerify2FaDto { server: ServerConfig - user = { twoFaEnabled: true } + user: { twoFaEnabled: boolean } = { twoFaEnabled: true } token: TokenResponseDto constructor(serverConfig: ServerConfig) { @@ -30,8 +24,3 @@ export class LoginVerify2FaDto { this.token = new TokenResponseDto() } } - -export class TwoFaResponseDto extends LoginResponseDto { - success: boolean - message: string -} diff --git a/backend/src/authentication/dto/token-response.dto.ts b/backend/src/authentication/dto/token-response.dto.ts index a2a89093..4160b3a1 100644 --- a/backend/src/authentication/dto/token-response.dto.ts +++ b/backend/src/authentication/dto/token-response.dto.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { IsInt, IsOptional, IsString } from 'class-validator' export class TokenResponseDto { diff --git a/backend/src/authentication/guards/auth-anonymous.guard.spec.ts b/backend/src/authentication/guards/auth-anonymous.guard.spec.ts index 270cda28..8f1f1e9f 100644 --- a/backend/src/authentication/guards/auth-anonymous.guard.spec.ts +++ b/backend/src/authentication/guards/auth-anonymous.guard.spec.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { createMock, DeepMocked } from '@golevelup/ts-jest' import { ExecutionContext } from '@nestjs/common' import { ConfigModule } from '@nestjs/config' diff --git a/backend/src/authentication/guards/auth-anonymous.guard.ts b/backend/src/authentication/guards/auth-anonymous.guard.ts index e50f47c5..894a1355 100644 --- a/backend/src/authentication/guards/auth-anonymous.guard.ts +++ b/backend/src/authentication/guards/auth-anonymous.guard.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Injectable } from '@nestjs/common' import { AuthGuard, IAuthGuard } from '@nestjs/passport' diff --git a/backend/src/authentication/guards/auth-anonymous.strategy.ts b/backend/src/authentication/guards/auth-anonymous.strategy.ts index 29c1be5c..25ca9ce2 100644 --- a/backend/src/authentication/guards/auth-anonymous.strategy.ts +++ b/backend/src/authentication/guards/auth-anonymous.strategy.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Injectable } from '@nestjs/common' import { PassportStrategy } from '@nestjs/passport' import { Strategy } from 'passport-strategy' diff --git a/backend/src/authentication/guards/auth-basic.guard.spec.ts b/backend/src/authentication/guards/auth-basic.guard.spec.ts index 6a3192b9..b54e4554 100644 --- a/backend/src/authentication/guards/auth-basic.guard.spec.ts +++ b/backend/src/authentication/guards/auth-basic.guard.spec.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { createMock, DeepMocked } from '@golevelup/ts-jest' import { ExecutionContext } from '@nestjs/common' import { Test, TestingModule } from '@nestjs/testing' @@ -12,14 +6,14 @@ import { UserModel } from '../../applications/users/models/user.model' import { generateUserTest } from '../../applications/users/utils/test' import { WEBDAV_BASE_PATH } from '../../applications/webdav/constants/routes' import { Cache } from '../../infrastructure/cache/services/cache.service' -import { AuthMethod } from '../models/auth-method' +import { AuthProvider } from '../providers/auth-providers.models' import { AuthBasicGuard } from './auth-basic.guard' import { AuthBasicStrategy } from './auth-basic.strategy' describe(AuthBasicGuard.name, () => { let authBasicGuard: AuthBasicGuard let authBasicStrategy: AuthBasicStrategy - let authMethod: AuthMethod + let authProvider: AuthProvider let cache: Cache let userTest: UserModel let encodedAuth: string @@ -31,7 +25,7 @@ describe(AuthBasicGuard.name, () => { AuthBasicGuard, AuthBasicStrategy, { - provide: AuthMethod, + provide: AuthProvider, useValue: { validateUser: async () => null } @@ -56,7 +50,7 @@ describe(AuthBasicGuard.name, () => { authBasicGuard = module.get(AuthBasicGuard) authBasicStrategy = module.get(AuthBasicStrategy) - authMethod = module.get(AuthMethod) + authProvider = module.get(AuthProvider) cache = module.get(Cache) userTest = new UserModel(generateUserTest(), false) encodedAuth = Buffer.from(`${userTest.login}:${userTest.password}`).toString('base64') @@ -66,7 +60,7 @@ describe(AuthBasicGuard.name, () => { it('should be defined', () => { expect(authBasicGuard).toBeDefined() expect(authBasicStrategy).toBeDefined() - expect(authMethod).toBeDefined() + expect(authProvider).toBeDefined() expect(cache).toBeDefined() expect(encodedAuth).toBeDefined() expect(userTest).toBeDefined() @@ -74,7 +68,7 @@ describe(AuthBasicGuard.name, () => { }) it('should validate the user authentication', async () => { - authMethod.validateUser = jest.fn().mockReturnValueOnce(userTest) + authProvider.validateUser = jest.fn().mockReturnValueOnce(userTest) context.switchToHttp().getRequest.mockReturnValue({ raw: { user: '' }, headers: { authorization: `Basic ${encodedAuth}` } @@ -88,7 +82,7 @@ describe(AuthBasicGuard.name, () => { const userWithColonPassword = new UserModel({ ...generateUserTest(), password: passwordWithColon }, false) const encodedAuthWithColon = Buffer.from(`${userWithColonPassword.login}:${passwordWithColon}`).toString('base64') - authMethod.validateUser = jest.fn().mockImplementation((login: string, password: string) => { + authProvider.validateUser = jest.fn().mockImplementation((login: string, password: string) => { expect(login).toBe(userWithColonPassword.login) expect(password).toBe(passwordWithColon) return userWithColonPassword @@ -121,7 +115,7 @@ describe(AuthBasicGuard.name, () => { it('should not validate the user authentication when cache returns undefined and database return null', async () => { cache.get = jest.fn().mockReturnValueOnce(undefined) - authMethod.validateUser = jest.fn().mockReturnValueOnce(null) + authProvider.validateUser = jest.fn().mockReturnValueOnce(null) jest.spyOn(cache, 'set').mockRejectedValueOnce(new Error('cache failed')) context.switchToHttp().getRequest.mockReturnValue({ raw: { user: '' }, diff --git a/backend/src/authentication/guards/auth-basic.guard.ts b/backend/src/authentication/guards/auth-basic.guard.ts index 2db8ed47..6d7df57b 100644 --- a/backend/src/authentication/guards/auth-basic.guard.ts +++ b/backend/src/authentication/guards/auth-basic.guard.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { ExecutionContext, Injectable, Logger } from '@nestjs/common' import { AuthGuard, IAuthGuard } from '@nestjs/passport' import { FastifyRequest } from 'fastify' diff --git a/backend/src/authentication/guards/auth-basic.strategy.ts b/backend/src/authentication/guards/auth-basic.strategy.ts index 98813ccb..53b789f9 100644 --- a/backend/src/authentication/guards/auth-basic.strategy.ts +++ b/backend/src/authentication/guards/auth-basic.strategy.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Injectable } from '@nestjs/common' import { AbstractStrategy, PassportStrategy } from '@nestjs/passport' import { instanceToPlain, plainToInstance } from 'class-transformer' @@ -13,7 +7,7 @@ import { UserModel } from '../../applications/users/models/user.model' import { SERVER_NAME } from '../../common/shared' import { Cache } from '../../infrastructure/cache/services/cache.service' import { AUTH_SCOPE } from '../constants/scope' -import { AuthMethod } from '../models/auth-method' +import { AuthProvider } from '../providers/auth-providers.models' import { HttpBasicStrategy } from './implementations/http-basic.strategy' @Injectable() @@ -22,7 +16,7 @@ export class AuthBasicStrategy extends PassportStrategy(HttpBasicStrategy, 'basi private readonly CACHE_KEY_PREFIX = 'auth-webdav' constructor( - private readonly authMethod: AuthMethod, + private readonly authProvider: AuthProvider, private readonly cache: Cache, private readonly logger: PinoLogger ) { @@ -44,7 +38,7 @@ export class AuthBasicStrategy extends PassportStrategy(HttpBasicStrategy, 'basi // warning: plainToInstance do not use constructor to instantiate the class return plainToInstance(UserModel, userFromCache) } - const userFromDB: UserModel = await this.authMethod.validateUser(loginOrEmail, password, req.ip, AUTH_SCOPE.WEBDAV) + const userFromDB: UserModel = await this.authProvider.validateUser(loginOrEmail, password, req.ip, AUTH_SCOPE.WEBDAV) if (userFromDB !== null) { userFromDB.removePassword() } diff --git a/backend/src/authentication/guards/auth-digest.guard.ts b/backend/src/authentication/guards/auth-digest.guard.ts index b8bfa837..f211167c 100644 --- a/backend/src/authentication/guards/auth-digest.guard.ts +++ b/backend/src/authentication/guards/auth-digest.guard.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - // import { ExecutionContext, Injectable, Logger } from '@nestjs/common' // import { AuthGuard, IAuthGuard } from '@nestjs/passport' // diff --git a/backend/src/authentication/guards/auth-digest.strategy.ts b/backend/src/authentication/guards/auth-digest.strategy.ts index 28872a40..7c9331ac 100644 --- a/backend/src/authentication/guards/auth-digest.strategy.ts +++ b/backend/src/authentication/guards/auth-digest.strategy.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - // import { Injectable } from '@nestjs/common' // import { PassportStrategy } from '@nestjs/passport' // import { PinoLogger } from 'nestjs-pino' diff --git a/backend/src/authentication/guards/auth-local.guard.spec.ts b/backend/src/authentication/guards/auth-local.guard.spec.ts index d126182c..2bdcf62a 100644 --- a/backend/src/authentication/guards/auth-local.guard.spec.ts +++ b/backend/src/authentication/guards/auth-local.guard.spec.ts @@ -1,22 +1,16 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { createMock, DeepMocked } from '@golevelup/ts-jest' import { ExecutionContext } from '@nestjs/common' import { Test, TestingModule } from '@nestjs/testing' import { PinoLogger } from 'nestjs-pino' import { UserModel } from '../../applications/users/models/user.model' import { generateUserTest } from '../../applications/users/utils/test' -import { AuthMethod } from '../models/auth-method' +import { AuthProvider } from '../providers/auth-providers.models' import { AuthLocalGuard } from './auth-local.guard' import { AuthLocalStrategy } from './auth-local.strategy' describe(AuthLocalGuard.name, () => { let authLocalGuard: AuthLocalGuard - let authMethod: AuthMethod + let authProvider: AuthProvider let userTest: UserModel let context: DeepMocked @@ -25,7 +19,7 @@ describe(AuthLocalGuard.name, () => { providers: [ AuthLocalGuard, AuthLocalStrategy, - { provide: AuthMethod, useValue: {} }, + { provide: AuthProvider, useValue: {} }, { provide: PinoLogger, useValue: { @@ -36,19 +30,19 @@ describe(AuthLocalGuard.name, () => { }).compile() authLocalGuard = module.get(AuthLocalGuard) - authMethod = module.get(AuthMethod) + authProvider = module.get(AuthProvider) userTest = new UserModel(generateUserTest(), false) context = createMock() }) it('should be defined', () => { expect(authLocalGuard).toBeDefined() - expect(authMethod).toBeDefined() + expect(authProvider).toBeDefined() expect(userTest).toBeDefined() }) it('should validate the user authentication', async () => { - authMethod.validateUser = jest.fn().mockReturnValueOnce(userTest) + authProvider.validateUser = jest.fn().mockReturnValueOnce(userTest) context.switchToHttp().getRequest.mockReturnValue({ raw: { user: '' }, body: { @@ -62,7 +56,7 @@ describe(AuthLocalGuard.name, () => { it('should not validate the user authentication', async () => { userTest.password = 'password' - authMethod.validateUser = jest.fn().mockReturnValueOnce(null) + authProvider.validateUser = jest.fn().mockReturnValueOnce(null) context.switchToHttp().getRequest.mockReturnValue({ raw: { user: '' }, body: { @@ -74,7 +68,7 @@ describe(AuthLocalGuard.name, () => { }) it('should throw error due to malformed body', async () => { - authMethod.validateUser = jest.fn().mockReturnValueOnce(null) + authProvider.validateUser = jest.fn().mockReturnValueOnce(null) context.switchToHttp().getRequest.mockReturnValue({ raw: { user: '' }, body: null diff --git a/backend/src/authentication/guards/auth-local.guard.ts b/backend/src/authentication/guards/auth-local.guard.ts index a7a6988b..10dbab99 100755 --- a/backend/src/authentication/guards/auth-local.guard.ts +++ b/backend/src/authentication/guards/auth-local.guard.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { ExecutionContext, Injectable, Logger } from '@nestjs/common' import { AuthGuard, IAuthGuard } from '@nestjs/passport' diff --git a/backend/src/authentication/guards/auth-local.strategy.ts b/backend/src/authentication/guards/auth-local.strategy.ts index e555cb4f..ccc98037 100755 --- a/backend/src/authentication/guards/auth-local.strategy.ts +++ b/backend/src/authentication/guards/auth-local.strategy.ts @@ -1,21 +1,15 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Injectable, UnauthorizedException } from '@nestjs/common' import { AbstractStrategy, PassportStrategy } from '@nestjs/passport' import type { FastifyRequest } from 'fastify' import { PinoLogger } from 'nestjs-pino' import { Strategy } from 'passport-local' import type { UserModel } from '../../applications/users/models/user.model' -import { AuthMethod } from '../models/auth-method' +import { AuthProvider } from '../providers/auth-providers.models' @Injectable() export class AuthLocalStrategy extends PassportStrategy(Strategy, 'local') implements AbstractStrategy { constructor( - private readonly authMethod: AuthMethod, + private readonly authProvider: AuthProvider, private readonly logger: PinoLogger ) { super({ usernameField: 'login', passwordField: 'password', passReqToCallback: true }) @@ -25,7 +19,7 @@ export class AuthLocalStrategy extends PassportStrategy(Strategy, 'local') imple loginOrEmail = loginOrEmail.trim() password = password.trim() this.logger.assign({ user: loginOrEmail }) - const user: UserModel = await this.authMethod.validateUser(loginOrEmail, password, req.ip) + const user: UserModel = await this.authProvider.validateUser(loginOrEmail, password, req.ip) if (user) { user.removePassword() return user diff --git a/backend/src/authentication/guards/auth-token-access.guard.spec.ts b/backend/src/authentication/guards/auth-token-access.guard.spec.ts index 7dc5df40..f4de9d1b 100644 --- a/backend/src/authentication/guards/auth-token-access.guard.spec.ts +++ b/backend/src/authentication/guards/auth-token-access.guard.spec.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { sign } from '@fastify/cookie' import { createMock, DeepMocked } from '@golevelup/ts-jest' import { ExecutionContext } from '@nestjs/common' @@ -18,12 +12,12 @@ import { UsersManager } from '../../applications/users/services/users-manager.se import { WEB_DAV_CONTEXT, WebDAVContext } from '../../applications/webdav/decorators/webdav-context.decorator' import { exportConfiguration } from '../../configuration/config.environment' import { AuthConfig } from '../auth.config' +import { AuthManager } from '../auth.service' import { CSRF_ERROR } from '../constants/auth' import { AuthTokenOptional } from '../decorators/auth-token-optional.decorator' import { AUTH_TOKEN_SKIP, AuthTokenSkip } from '../decorators/auth-token-skip.decorator' import { JwtPayload } from '../interfaces/jwt-payload.interface' import { TOKEN_TYPE } from '../interfaces/token.interface' -import { AuthManager } from '../services/auth-manager.service' import { AuthAnonymousGuard } from './auth-anonymous.guard' import { AuthAnonymousStrategy } from './auth-anonymous.strategy' import { AuthTokenAccessGuard } from './auth-token-access.guard' diff --git a/backend/src/authentication/guards/auth-token-access.guard.ts b/backend/src/authentication/guards/auth-token-access.guard.ts index f145f241..bd991ad1 100755 --- a/backend/src/authentication/guards/auth-token-access.guard.ts +++ b/backend/src/authentication/guards/auth-token-access.guard.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { ExecutionContext, Injectable, Logger } from '@nestjs/common' import { Reflector } from '@nestjs/core' import { AuthGuard, IAuthGuard } from '@nestjs/passport' diff --git a/backend/src/authentication/guards/auth-token-access.strategy.ts b/backend/src/authentication/guards/auth-token-access.strategy.ts index 2fc5b3a3..3abed9a7 100755 --- a/backend/src/authentication/guards/auth-token-access.strategy.ts +++ b/backend/src/authentication/guards/auth-token-access.strategy.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Injectable } from '@nestjs/common' import { AbstractStrategy, PassportStrategy } from '@nestjs/passport' import { FastifyRequest } from 'fastify' @@ -11,9 +5,9 @@ import { PinoLogger } from 'nestjs-pino' import { ExtractJwt, Strategy } from 'passport-jwt' import { UserModel } from '../../applications/users/models/user.model' import { configuration } from '../../configuration/config.environment' +import { AuthManager } from '../auth.service' import { JwtPayload } from '../interfaces/jwt-payload.interface' import { TOKEN_TYPE } from '../interfaces/token.interface' -import { AuthManager } from '../services/auth-manager.service' @Injectable() export class AuthTokenAccessStrategy extends PassportStrategy(Strategy, 'tokenAccess') implements AbstractStrategy { diff --git a/backend/src/authentication/guards/auth-token-refresh.guard.spec.ts b/backend/src/authentication/guards/auth-token-refresh.guard.spec.ts index 49b67f26..4660e06b 100644 --- a/backend/src/authentication/guards/auth-token-refresh.guard.spec.ts +++ b/backend/src/authentication/guards/auth-token-refresh.guard.spec.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { sign } from '@fastify/cookie' import { createMock, DeepMocked } from '@golevelup/ts-jest' import { ExecutionContext } from '@nestjs/common' @@ -16,10 +10,10 @@ import crypto from 'node:crypto' import { UsersManager } from '../../applications/users/services/users-manager.service' import { exportConfiguration } from '../../configuration/config.environment' import { AuthConfig } from '../auth.config' +import { AuthManager } from '../auth.service' import { CSRF_ERROR } from '../constants/auth' import { JwtPayload } from '../interfaces/jwt-payload.interface' import { TOKEN_TYPE } from '../interfaces/token.interface' -import { AuthManager } from '../services/auth-manager.service' import { AuthTokenRefreshGuard } from './auth-token-refresh.guard' import { AuthTokenRefreshStrategy } from './auth-token-refresh.strategy' diff --git a/backend/src/authentication/guards/auth-token-refresh.guard.ts b/backend/src/authentication/guards/auth-token-refresh.guard.ts index a89997b5..696430fd 100755 --- a/backend/src/authentication/guards/auth-token-refresh.guard.ts +++ b/backend/src/authentication/guards/auth-token-refresh.guard.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { ExecutionContext, Injectable, Logger } from '@nestjs/common' import { AuthGuard, IAuthGuard } from '@nestjs/passport' diff --git a/backend/src/authentication/guards/auth-token-refresh.strategy.ts b/backend/src/authentication/guards/auth-token-refresh.strategy.ts index ad2a291e..0184fb36 100755 --- a/backend/src/authentication/guards/auth-token-refresh.strategy.ts +++ b/backend/src/authentication/guards/auth-token-refresh.strategy.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Injectable } from '@nestjs/common' import { AbstractStrategy, PassportStrategy } from '@nestjs/passport' import { FastifyRequest } from 'fastify' @@ -11,9 +5,9 @@ import { PinoLogger } from 'nestjs-pino' import { ExtractJwt, Strategy } from 'passport-jwt' import { UserModel } from '../../applications/users/models/user.model' import { configuration } from '../../configuration/config.environment' +import { AuthManager } from '../auth.service' import { JwtPayload } from '../interfaces/jwt-payload.interface' import { TOKEN_TYPE } from '../interfaces/token.interface' -import { AuthManager } from '../services/auth-manager.service' @Injectable() export class AuthTokenRefreshStrategy extends PassportStrategy(Strategy, 'tokenRefresh') implements AbstractStrategy { diff --git a/backend/src/authentication/guards/implementations/http-basic.strategy.ts b/backend/src/authentication/guards/implementations/http-basic.strategy.ts index 1a9ab7b9..140d20f9 100644 --- a/backend/src/authentication/guards/implementations/http-basic.strategy.ts +++ b/backend/src/authentication/guards/implementations/http-basic.strategy.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2026 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Strategy as PassportStrategy } from 'passport-strategy' export type BasicVerifyCallback = (err?: any, user?: any) => void diff --git a/backend/src/authentication/guards/implementations/http-digest.strategy.ts b/backend/src/authentication/guards/implementations/http-digest.strategy.ts index ecc85cfd..6b3002b7 100644 --- a/backend/src/authentication/guards/implementations/http-digest.strategy.ts +++ b/backend/src/authentication/guards/implementations/http-digest.strategy.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2026 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import crypto from 'node:crypto' import { Strategy as PassportStrategy } from 'passport-strategy' diff --git a/backend/src/authentication/interfaces/auth-request.interface.ts b/backend/src/authentication/interfaces/auth-request.interface.ts index d5059681..f43b399c 100644 --- a/backend/src/authentication/interfaces/auth-request.interface.ts +++ b/backend/src/authentication/interfaces/auth-request.interface.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { FastifyRequest } from 'fastify' import { UserModel } from '../../applications/users/models/user.model' diff --git a/backend/src/authentication/interfaces/jwt-payload.interface.ts b/backend/src/authentication/interfaces/jwt-payload.interface.ts index 8e2b856a..46fe796e 100644 --- a/backend/src/authentication/interfaces/jwt-payload.interface.ts +++ b/backend/src/authentication/interfaces/jwt-payload.interface.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - export class JwtIdentityPayload { id: number login: string diff --git a/backend/src/authentication/interfaces/token.interface.ts b/backend/src/authentication/interfaces/token.interface.ts index b1c363ea..39cae539 100644 --- a/backend/src/authentication/interfaces/token.interface.ts +++ b/backend/src/authentication/interfaces/token.interface.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - export enum TOKEN_TYPE { ACCESS = 'access', ACCESS_2FA = 'access_2fa', diff --git a/backend/src/authentication/providers/auth-providers.constants.ts b/backend/src/authentication/providers/auth-providers.constants.ts new file mode 100644 index 00000000..7566e175 --- /dev/null +++ b/backend/src/authentication/providers/auth-providers.constants.ts @@ -0,0 +1,5 @@ +export enum AUTH_PROVIDER { + MYSQL = 'mysql', + LDAP = 'ldap', + OIDC = 'oidc' +} diff --git a/backend/src/authentication/models/auth-method.ts b/backend/src/authentication/providers/auth-providers.models.ts similarity index 51% rename from backend/src/authentication/models/auth-method.ts rename to backend/src/authentication/providers/auth-providers.models.ts index 1b6aed9f..e9b8a61f 100644 --- a/backend/src/authentication/models/auth-method.ts +++ b/backend/src/authentication/providers/auth-providers.models.ts @@ -1,12 +1,6 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import type { UserModel } from '../../applications/users/models/user.model' import type { AUTH_SCOPE } from '../constants/scope' -export abstract class AuthMethod { +export abstract class AuthProvider { abstract validateUser(loginOrEmail: string, password: string, ip?: string, scope?: AUTH_SCOPE): Promise } diff --git a/backend/src/authentication/providers/auth-providers.utils.ts b/backend/src/authentication/providers/auth-providers.utils.ts new file mode 100644 index 00000000..ea6398c2 --- /dev/null +++ b/backend/src/authentication/providers/auth-providers.utils.ts @@ -0,0 +1,24 @@ +import { Provider } from '@nestjs/common' +import { AUTH_PROVIDER } from './auth-providers.constants' + +import { AuthProvider } from './auth-providers.models' +import { AuthProviderLDAP } from './ldap/auth-provider-ldap.service' +import { AuthProviderMySQL } from './mysql/auth-provider-mysql.service' +import { AuthProviderOIDC } from './oidc/auth-provider-oidc.service' + +export function selectAuthProvider(provider: AUTH_PROVIDER): Provider { + switch (provider) { + case AUTH_PROVIDER.OIDC: + // `AuthProviderOIDC` is already provided by `AuthProviderOIDCModule` + return { provide: AuthProvider, useExisting: AuthProviderOIDC } + + case AUTH_PROVIDER.LDAP: + return { provide: AuthProvider, useClass: AuthProviderLDAP } + + case AUTH_PROVIDER.MYSQL: + return { provide: AuthProvider, useClass: AuthProviderMySQL } + + default: + return { provide: AuthProvider, useClass: AuthProviderMySQL } + } +} diff --git a/backend/src/authentication/providers/ldap/auth-ldap.config.ts b/backend/src/authentication/providers/ldap/auth-ldap.config.ts new file mode 100644 index 00000000..6a840aaa --- /dev/null +++ b/backend/src/authentication/providers/ldap/auth-ldap.config.ts @@ -0,0 +1,85 @@ +import { Transform, Type } from 'class-transformer' +import { + ArrayNotEmpty, + IsArray, + IsBoolean, + IsDefined, + IsEnum, + IsNotEmpty, + IsNotEmptyObject, + IsObject, + IsOptional, + IsString, + ValidateNested +} from 'class-validator' +import { USER_PERMISSION } from '../../../applications/users/constants/user' +import { LDAP_COMMON_ATTR, LDAP_LOGIN_ATTR } from './auth-ldap.constants' + +export class AuthProviderLDAPAttributesConfig { + @IsOptional() + @Transform(({ value }) => value || LDAP_LOGIN_ATTR.UID) + @IsEnum(LDAP_LOGIN_ATTR) + login: LDAP_LOGIN_ATTR = LDAP_LOGIN_ATTR.UID + + @IsOptional() + @IsString() + @Transform(({ value }) => value || LDAP_COMMON_ATTR.MAIL) + email: string = LDAP_COMMON_ATTR.MAIL +} + +export class AuthProviderLDAPOptionsConfig { + @IsOptional() + @IsString() + adminGroup?: string + + @IsOptional() + @IsBoolean() + autoCreateUser? = true + + @IsOptional() + @IsArray() + @IsEnum(USER_PERMISSION, { each: true }) + autoCreatePermissions?: USER_PERMISSION[] = [] + + @IsOptional() + @IsBoolean() + enablePasswordAuthFallback? = true +} + +export class AuthProviderLDAPConfig { + @Transform(({ value }) => (Array.isArray(value) ? value.filter((v: string) => Boolean(v)) : value)) + @ArrayNotEmpty() + @IsArray() + @IsString({ each: true }) + servers: string[] + + @IsString() + @IsNotEmpty() + baseDN: string + + @IsOptional() + @IsString() + filter?: string + + @IsDefined() + @IsNotEmptyObject() + @IsObject() + @ValidateNested() + @Type(() => AuthProviderLDAPAttributesConfig) + attributes: AuthProviderLDAPAttributesConfig = new AuthProviderLDAPAttributesConfig() + + @IsOptional() + @IsString() + upnSuffix?: string + + @IsOptional() + @IsString() + netbiosName?: string + + @IsDefined() + @IsNotEmptyObject() + @IsObject() + @ValidateNested() + @Type(() => AuthProviderLDAPOptionsConfig) + options: AuthProviderLDAPOptionsConfig = new AuthProviderLDAPOptionsConfig() +} diff --git a/backend/src/authentication/constants/auth-ldap.ts b/backend/src/authentication/providers/ldap/auth-ldap.constants.ts similarity index 66% rename from backend/src/authentication/constants/auth-ldap.ts rename to backend/src/authentication/providers/ldap/auth-ldap.constants.ts index aab414cd..112e1041 100644 --- a/backend/src/authentication/constants/auth-ldap.ts +++ b/backend/src/authentication/providers/ldap/auth-ldap.constants.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - export enum LDAP_LOGIN_ATTR { UID = 'uid', CN = 'cn', diff --git a/backend/src/authentication/providers/ldap/auth-provider-ldap.service.spec.ts b/backend/src/authentication/providers/ldap/auth-provider-ldap.service.spec.ts new file mode 100644 index 00000000..a3cadafe --- /dev/null +++ b/backend/src/authentication/providers/ldap/auth-provider-ldap.service.spec.ts @@ -0,0 +1,399 @@ +import { Test, TestingModule } from '@nestjs/testing' +import { Mocked } from 'jest-mock' +import { Client, InvalidCredentialsError } from 'ldapts' +import { CONNECT_ERROR_CODE } from '../../../app.constants' +import { USER_PERMISSION, USER_ROLE } from '../../../applications/users/constants/user' +import { UserModel } from '../../../applications/users/models/user.model' +import { AdminUsersManager } from '../../../applications/users/services/admin-users-manager.service' +import { UsersManager } from '../../../applications/users/services/users-manager.service' +import * as commonFunctions from '../../../common/functions' +import { configuration } from '../../../configuration/config.environment' +import type { AuthProviderLDAPConfig } from './auth-ldap.config' +import { LDAP_COMMON_ATTR, LDAP_LOGIN_ATTR } from './auth-ldap.constants' +import { AuthProviderLDAP } from './auth-provider-ldap.service' + +jest.mock('ldapts', () => { + const actual = jest.requireActual('ldapts') + const mockClientInstance = { + bind: jest.fn(), + search: jest.fn(), + unbind: jest.fn() + } + const Client = jest.fn().mockImplementation(() => mockClientInstance) + return { ...actual, Client } +}) + +const buildUser = (overrides: Partial = {}) => + ({ + id: 0, + login: 'john', + email: 'old@example.org', + password: 'hashed', + role: USER_ROLE.USER, + isGuest: false, + isActive: true, + isAdmin: false, + makePaths: jest.fn().mockResolvedValue(undefined), + setFullName: jest.fn(), + ...overrides + }) as any + +const ldapClient = { + bind: jest.fn(), + search: jest.fn(), + unbind: jest.fn() +} +;(Client as Mocked).mockImplementation(() => ldapClient) + +describe(AuthProviderLDAP.name, () => { + let authProviderLDAP: AuthProviderLDAP + let usersManager: Mocked + let adminUsersManager: Mocked + + type LdapConfigOverrides = Omit, 'attributes' | 'options'> & { + attributes?: Partial + options?: Partial + } + + const setLdapConfig = (overrides: LdapConfigOverrides = {}) => { + const base: AuthProviderLDAPConfig = { + servers: ['ldap://localhost:389'], + attributes: { login: LDAP_LOGIN_ATTR.UID, email: LDAP_COMMON_ATTR.MAIL }, + baseDN: 'ou=people,dc=example,dc=org', + filter: '', + options: { + autoCreateUser: true, + autoCreatePermissions: [], + enablePasswordAuthFallback: true + } + } + const next: AuthProviderLDAPConfig = { + ...base, + ...overrides, + attributes: { ...base.attributes, ...(overrides.attributes || {}) }, + options: { ...base.options, ...(overrides.options || {}) } + } + configuration.auth.ldap = next + ;(authProviderLDAP as any).ldapConfig = next + ;(authProviderLDAP as any).isAD = [LDAP_LOGIN_ATTR.SAM, LDAP_LOGIN_ATTR.UPN].includes(next.attributes.login) + } + + const mockBindResolve = () => { + ldapClient.bind.mockResolvedValue(undefined) + ldapClient.unbind.mockResolvedValue(undefined) + } + + const mockBindRejectInvalid = (message = 'invalid') => { + ldapClient.bind.mockRejectedValue(new InvalidCredentialsError(message)) + ldapClient.unbind.mockResolvedValue(undefined) + } + + const mockSearchEntries = (entries: any[]) => { + ldapClient.search.mockResolvedValue({ searchEntries: entries }) + } + + beforeAll(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [ + AuthProviderLDAP, + { + provide: UsersManager, + useValue: { + findUser: jest.fn(), + logUser: jest.fn(), + updateAccesses: jest.fn().mockResolvedValue(undefined), + validateAppPassword: jest.fn(), + fromUserId: jest.fn() + } + }, + { + provide: AdminUsersManager, + useValue: { + createUserOrGuest: jest.fn(), + updateUserOrGuest: jest.fn() + } + } + ] + }).compile() + + module.useLogger(['fatal']) + authProviderLDAP = module.get(AuthProviderLDAP) + adminUsersManager = module.get>(AdminUsersManager) + usersManager = module.get>(UsersManager) + }) + + beforeEach(() => { + jest.clearAllMocks() + setLdapConfig() + usersManager.updateAccesses.mockResolvedValue(undefined) + }) + + afterEach(() => { + jest.restoreAllMocks() + }) + + it('should be defined', () => { + expect(authProviderLDAP).toBeDefined() + expect(usersManager).toBeDefined() + expect(adminUsersManager).toBeDefined() + expect(ldapClient).toBeDefined() + }) + + it('should authenticate a guest user via database and bypass LDAP', async () => { + const guestUser: any = { id: 1, login: 'guest1', isGuest: true, isActive: true } + usersManager.findUser.mockResolvedValue(guestUser) + const dbAuthResult: any = { ...guestUser, token: 'jwt' } + usersManager.logUser.mockResolvedValue(dbAuthResult) + + const res = await authProviderLDAP.validateUser('guest1', 'pass', '127.0.0.1') + + expect(res).toEqual(dbAuthResult) + expect(usersManager.logUser).toHaveBeenCalledWith(guestUser, 'pass', '127.0.0.1', undefined) + expect(Client).not.toHaveBeenCalled() + }) + + it('should bypass LDAP when scope is provided', async () => { + const user = buildUser({ id: 12 }) + usersManager.findUser.mockResolvedValue(user) + usersManager.logUser.mockResolvedValue(user) + + const res = await authProviderLDAP.validateUser('john', 'app-password', '10.0.0.2', 'webdav' as any) + + expect(res).toBe(user) + expect(usersManager.logUser).toHaveBeenCalledWith(user, 'app-password', '10.0.0.2', 'webdav') + expect(Client).not.toHaveBeenCalled() + }) + + it('should throw FORBIDDEN for locked account', async () => { + usersManager.findUser.mockResolvedValue({ login: 'john', isGuest: false, isActive: false } as UserModel) + const loggerErrorSpy = jest.spyOn(authProviderLDAP['logger'], 'error').mockImplementation(() => undefined as any) + + await expect(authProviderLDAP.validateUser('john', 'pwd')).rejects.toThrow(/account locked/i) + expect(loggerErrorSpy).toHaveBeenCalled() + }) + + it('should return null on invalid LDAP credentials without fallback', async () => { + const existingUser: any = buildUser({ id: 1 }) + usersManager.findUser.mockResolvedValue(existingUser) + mockBindRejectInvalid('invalid credentials') + + const res = await authProviderLDAP.validateUser('john', 'badpwd', '10.0.0.1') + + expect(res).toBeNull() + expect(usersManager.logUser).not.toHaveBeenCalled() + expect(usersManager.updateAccesses).not.toHaveBeenCalled() + }) + + it('should return null when LDAP search yields no entries or throws', async () => { + const existingUser: any = buildUser({ id: 10 }) + usersManager.findUser.mockResolvedValue(existingUser) + mockBindResolve() + mockSearchEntries([]) + + const resA = await authProviderLDAP.validateUser('john', 'pwd') + + expect(resA).toBeNull() + + ldapClient.search.mockRejectedValue(new Error('search failed')) + const resB = await authProviderLDAP.validateUser('john', 'pwd') + + expect(resB).toBeNull() + expect(usersManager.updateAccesses).not.toHaveBeenCalled() + }) + + it('should fallback to local auth when LDAP is unavailable and fallback is enabled', async () => { + const existingUser: any = buildUser({ id: 2 }) + usersManager.findUser.mockResolvedValue(existingUser) + usersManager.logUser.mockResolvedValue(existingUser) + const err = Object.assign(new Error('connect ECONNREFUSED'), { code: Array.from(CONNECT_ERROR_CODE)[0] }) + ldapClient.bind.mockRejectedValue({ errors: [err] }) + ldapClient.unbind.mockResolvedValue(undefined) + + const res = await authProviderLDAP.validateUser('john', 'pwd', '10.0.0.3') + + expect(res).toBe(existingUser) + expect(usersManager.logUser).toHaveBeenCalledWith(existingUser, 'pwd', '10.0.0.3') + }) + + it('should throw SERVICE_UNAVAILABLE when LDAP is unavailable and fallback is disabled', async () => { + setLdapConfig({ options: { enablePasswordAuthFallback: false } }) + const existingUser: any = buildUser({ id: 3 }) + usersManager.findUser.mockResolvedValue(existingUser) + const err = Object.assign(new Error('connect ECONNREFUSED'), { code: Array.from(CONNECT_ERROR_CODE)[0] }) + ldapClient.bind.mockRejectedValue({ errors: [err] }) + ldapClient.unbind.mockResolvedValue(undefined) + + await expect(authProviderLDAP.validateUser('john', 'pwd')).rejects.toThrow(/authentication service error/i) + }) + + it('should allow admin local fallback when LDAP is unavailable even if fallback is disabled', async () => { + setLdapConfig({ options: { enablePasswordAuthFallback: false } }) + const existingUser: any = buildUser({ id: 4, isAdmin: true }) + usersManager.findUser.mockResolvedValue(existingUser) + usersManager.logUser.mockResolvedValue(existingUser) + const err = Object.assign(new Error('connect ECONNREFUSED'), { code: Array.from(CONNECT_ERROR_CODE)[0] }) + ldapClient.bind.mockRejectedValue({ errors: [err] }) + ldapClient.unbind.mockResolvedValue(undefined) + + const res = await authProviderLDAP.validateUser('john', 'pwd') + + expect(res).toBe(existingUser) + expect(usersManager.logUser).toHaveBeenCalledWith(existingUser, 'pwd', undefined) + }) + + it('should return null when LDAP entry lacks required fields', async () => { + usersManager.findUser.mockResolvedValue(null) + mockBindResolve() + mockSearchEntries([{ uid: 'jane', cn: 'Jane Doe', mail: undefined }]) + const loggerErrorSpy = jest.spyOn(authProviderLDAP['logger'], 'error').mockImplementation(() => undefined as any) + + const res = await authProviderLDAP.validateUser('jane', 'pwd') + + expect(res).toBeNull() + expect(adminUsersManager.createUserOrGuest).not.toHaveBeenCalled() + expect(loggerErrorSpy).toHaveBeenCalled() + }) + + it('should throw UNAUTHORIZED when autoCreateUser is disabled', async () => { + setLdapConfig({ options: { autoCreateUser: false } }) + usersManager.findUser.mockResolvedValue(null) + const checkAuthSpy = jest.spyOn(authProviderLDAP as any, 'checkAuth').mockResolvedValue({ + uid: 'john', + mail: 'john@example.org' + }) + + await expect(authProviderLDAP.validateUser('john', 'pwd')).rejects.toThrow(/user not found/i) + checkAuthSpy.mockRestore() + }) + + it('should create a new admin user with permissions and name parsed from LDAP', async () => { + setLdapConfig({ + options: { + adminGroup: 'Admins', + autoCreatePermissions: [USER_PERMISSION.PERSONAL_SPACE, USER_PERMISSION.WEBDAV] + } + }) + usersManager.findUser.mockResolvedValue(null) + mockBindResolve() + mockSearchEntries([ + { + uid: 'john', + givenName: 'John', + sn: 'Doe', + mail: 'john@example.org', + memberOf: ['CN=Admins,OU=Groups,DC=example,DC=org'] + } + ]) + const createdUser: any = { id: 2, login: 'john', isGuest: false, isActive: true, makePaths: jest.fn() } + adminUsersManager.createUserOrGuest.mockResolvedValue(createdUser) + usersManager.fromUserId.mockResolvedValue(createdUser) + + const res = await authProviderLDAP.validateUser('john', 'pwd', '192.168.1.10') + + expect(adminUsersManager.createUserOrGuest).toHaveBeenCalledWith( + { + login: 'john', + email: 'john@example.org', + password: 'pwd', + role: USER_ROLE.ADMINISTRATOR, + firstName: 'John', + lastName: 'Doe', + permissions: 'personal_space,webdav_access' + }, + USER_ROLE.ADMINISTRATOR + ) + expect(res).toBe(createdUser) + expect(usersManager.updateAccesses).toHaveBeenCalledWith(createdUser, '192.168.1.10', true) + }) + + it('should keep admin role when adminGroup is not configured', async () => { + setLdapConfig({ options: { adminGroup: undefined } }) + const existingUser: any = buildUser({ id: 5, role: USER_ROLE.ADMINISTRATOR }) + usersManager.findUser.mockResolvedValue(existingUser) + mockBindResolve() + mockSearchEntries([{ uid: 'john', cn: 'John Doe', mail: 'john@example.org' }]) + jest.spyOn(commonFunctions, 'comparePassword').mockResolvedValue(true) + + await authProviderLDAP.validateUser('john', 'pwd') + + expect(adminUsersManager.updateUserOrGuest).toHaveBeenCalled() + const updateArgs = adminUsersManager.updateUserOrGuest.mock.calls[0][1] + expect(updateArgs).toEqual(expect.objectContaining({ email: 'john@example.org' })) + expect(updateArgs).toEqual(expect.not.objectContaining({ role: expect.anything() })) + }) + + it('should update existing user and avoid reassigning password locally', async () => { + const existingUser: any = buildUser({ id: 6 }) + usersManager.findUser.mockResolvedValue(existingUser) + mockBindResolve() + mockSearchEntries([{ uid: 'john', displayName: 'Jane Doe', mail: 'john@example.org' }]) + const compareSpy = jest.spyOn(commonFunctions, 'comparePassword').mockResolvedValue(false) + const splitSpy = jest.spyOn(commonFunctions, 'splitFullName').mockReturnValue({ firstName: 'Jane', lastName: 'Doe' }) + + const res = await authProviderLDAP.validateUser('john', 'new-plain-password', '127.0.0.2') + + expect(adminUsersManager.updateUserOrGuest).toHaveBeenCalledWith( + 6, + expect.objectContaining({ + email: 'john@example.org', + firstName: 'Jane', + lastName: 'Doe' + }) + ) + expect(existingUser.password).toBe('hashed') + expect(existingUser).toMatchObject({ email: 'john@example.org', firstName: 'Jane', lastName: 'Doe' }) + expect(existingUser.setFullName).toHaveBeenCalledWith(true) + expect(usersManager.updateAccesses).toHaveBeenCalledWith(existingUser, '127.0.0.2', true) + expect(res).toBe(existingUser) + + compareSpy.mockRestore() + splitSpy.mockRestore() + }) + + it('should throw FORBIDDEN when LDAP login does not match user login', async () => { + const existingUser: any = buildUser({ id: 7, login: 'john' }) + usersManager.findUser.mockResolvedValue(existingUser) + mockBindResolve() + mockSearchEntries([{ uid: 'jane', cn: 'Jane Doe', mail: 'jane@example.org' }]) + + await expect(authProviderLDAP.validateUser('john', 'pwd')).rejects.toThrow(/account matching error/i) + }) + + it('should build LDAP logins and filters for AD and standard LDAP', () => { + setLdapConfig({ attributes: { login: LDAP_LOGIN_ATTR.UPN }, upnSuffix: 'sync-in.com', filter: '(memberOf=cn=staff)' }) + const adLogin = (authProviderLDAP as any).buildLdapLogin('john') + expect(adLogin).toBe('john@sync-in.com') + const adFilter = (authProviderLDAP as any).buildUserFilter('SYNC-IN\\john', '(memberOf=cn=staff)') + expect(adFilter).toContain('(sAMAccountName=john)') + expect(adFilter).toContain('(userPrincipalName=john)') + expect(adFilter).toContain('(mail=john)') + expect(adFilter).toContain('(memberOf=cn=staff)') + + setLdapConfig({ attributes: { login: LDAP_LOGIN_ATTR.UID }, filter: '(department=IT)' }) + const ldapFilter = (authProviderLDAP as any).buildUserFilter('john', '(department=IT)') + expect(ldapFilter).toContain('(uid=john)') + expect(ldapFilter).toContain('(cn=john)') + expect(ldapFilter).toContain('(mail=john)') + expect(ldapFilter).toContain('(department=IT)') + }) + + it('should normalize LDAP entries for memberOf and array attributes', () => { + const entry = { + uid: ['john'], + mail: ['john@example.org', 'john2@example.org'], + memberOf: ['CN=Admins,OU=Groups,DC=example,DC=org', 'CN=Staff,OU=Groups,DC=example,DC=org'] + } + + const normalized = (authProviderLDAP as any).convertToLdapUserEntry(entry) + + expect(normalized.uid).toBe('john') + expect(normalized.mail).toBe('john@example.org') + expect(normalized.memberOf).toEqual(['Admins', 'Staff']) + }) + + it('should build LDAP logins for SAM account name when netbiosName is set', () => { + setLdapConfig({ attributes: { login: LDAP_LOGIN_ATTR.SAM }, netbiosName: 'SYNC' }) + const samLogin = (authProviderLDAP as any).buildLdapLogin('john') + expect(samLogin).toBe('SYNC\\john') + }) +}) diff --git a/backend/src/authentication/services/auth-methods/auth-method-ldap.service.ts b/backend/src/authentication/providers/ldap/auth-provider-ldap.service.ts similarity index 81% rename from backend/src/authentication/services/auth-methods/auth-method-ldap.service.ts rename to backend/src/authentication/providers/ldap/auth-provider-ldap.service.ts index 2dc85bb9..69f7426d 100644 --- a/backend/src/authentication/services/auth-methods/auth-method-ldap.service.ts +++ b/backend/src/authentication/providers/ldap/auth-provider-ldap.service.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { HttpException, HttpStatus, Injectable, Logger } from '@nestjs/common' import { AndFilter, Client, ClientOptions, Entry, EqualityFilter, InvalidCredentialsError, OrFilter } from 'ldapts' import { CONNECT_ERROR_CODE } from '../../../app.constants' @@ -14,16 +8,17 @@ import { AdminUsersManager } from '../../../applications/users/services/admin-us import { UsersManager } from '../../../applications/users/services/users-manager.service' import { comparePassword, splitFullName } from '../../../common/functions' import { configuration } from '../../../configuration/config.environment' -import { ALL_LDAP_ATTRIBUTES, LDAP_COMMON_ATTR, LDAP_LOGIN_ATTR } from '../../constants/auth-ldap' import type { AUTH_SCOPE } from '../../constants/scope' -import { AuthMethod } from '../../models/auth-method' +import { AuthProvider } from '../auth-providers.models' +import type { AuthProviderLDAPConfig } from './auth-ldap.config' +import { ALL_LDAP_ATTRIBUTES, LDAP_COMMON_ATTR, LDAP_LOGIN_ATTR } from './auth-ldap.constants' type LdapUserEntry = Entry & Record @Injectable() -export class AuthMethodLdapService implements AuthMethod { - private readonly logger = new Logger(AuthMethodLdapService.name) - private readonly ldapConfig = configuration.auth.ldap +export class AuthProviderLDAP implements AuthProvider { + private readonly logger = new Logger(AuthProviderLDAP.name) + private readonly ldapConfig: AuthProviderLDAPConfig = configuration.auth.ldap private readonly isAD = this.ldapConfig.attributes.login === LDAP_LOGIN_ATTR.SAM || this.ldapConfig.attributes.login === LDAP_LOGIN_ATTR.UPN private clientOptions: ClientOptions = { timeout: 6000, connectTimeout: 6000, url: '' } @@ -36,38 +31,54 @@ export class AuthMethodLdapService implements AuthMethod { // Find user from his login or email let user: UserModel = await this.usersManager.findUser(this.dbLogin(login), false) if (user) { - if (user.isGuest) { - // Allow guests to be authenticated from db and check if the current user is defined as active - return this.usersManager.logUser(user, password, ip) + if (user.isGuest || scope) { + // Allow local password authentication for guest users and application scopes (app passwords) + return this.usersManager.logUser(user, password, ip, scope) } if (!user.isActive) { this.logger.error(`${this.validateUser.name} - user *${user.login}* is locked`) throw new HttpException('Account locked', HttpStatus.FORBIDDEN) } } - // If a user was found, use the stored login. This allows logging in with an email. - const entry: false | LdapUserEntry = await this.checkAuth(user?.login || login, password) + let ldapErrorMessage: string + let entry: false | LdapUserEntry = false + try { + // If a user was found, use the stored login. This allows logging in with an email. + entry = await this.checkAuth(user?.login || login, password) + } catch (e) { + ldapErrorMessage = e.message + } + + // LDAP auth failed or exception raised if (entry === false) { - // LDAP auth failed - if (user) { - let authSuccess = false - if (scope) { - // Try user app password - authSuccess = await this.usersManager.validateAppPassword(user, password, ip, scope) - } - this.usersManager.updateAccesses(user, ip, authSuccess).catch((e: Error) => this.logger.error(`${this.validateUser.name} : ${e}`)) - if (authSuccess) { - // Logged with app password - return user - } + // If LDAP is unavailable (connectivity/service error), allow local password fallback. + // Allow local password authentication for: + // - admin users (break-glass access) + // - regular users when password authentication fallback is enabled + if (user && Boolean(ldapErrorMessage) && (user.isAdmin || this.ldapConfig.options.enablePasswordAuthFallback)) { + const localUser = await this.usersManager.logUser(user, password, ip) + if (localUser) return localUser + } + + if (ldapErrorMessage) { + throw new HttpException(ldapErrorMessage, HttpStatus.SERVICE_UNAVAILABLE) } + return null - } else if (!entry[this.ldapConfig.attributes.login] || !entry[this.ldapConfig.attributes.email]) { + } + + if (!entry[this.ldapConfig.attributes.login] || !entry[this.ldapConfig.attributes.email]) { this.logger.error(`${this.validateUser.name} - required ldap fields are missing : [${this.ldapConfig.attributes.login}, ${this.ldapConfig.attributes.email}] => (${JSON.stringify(entry)})`) return null } + + if (!user && !this.ldapConfig.options.autoCreateUser) { + this.logger.warn(`${this.validateUser.name} - User not found and autoCreateUser is disabled`) + throw new HttpException('User not found', HttpStatus.UNAUTHORIZED) + } + const identity = this.createIdentity(entry, password) user = await this.updateOrCreateUser(identity, user) this.usersManager.updateAccesses(user, ip, true).catch((e: Error) => this.logger.error(`${this.validateUser.name} : ${e}`)) @@ -80,7 +91,7 @@ export class AuthMethodLdapService implements AuthMethod { // Generic LDAP: build DN from login attribute + baseDN const bindUserDN = this.isAD ? ldapLogin : `${this.ldapConfig.attributes.login}=${ldapLogin},${this.ldapConfig.baseDN}` let client: Client - let error: any + let error: InvalidCredentialsError | any for (const s of this.ldapConfig.servers) { client = new Client({ ...this.clientOptions, url: s }) try { @@ -89,12 +100,12 @@ export class AuthMethodLdapService implements AuthMethod { } catch (e) { if (e.errors?.length) { for (const err of e.errors) { - this.logger.warn(`${this.checkAuth.name} - ${ldapLogin} : ${err}`) + this.logger.warn(`${this.checkAuth.name} - ${bindUserDN} : ${err}`) error = err } } else { error = e - this.logger.warn(`${this.checkAuth.name} - ${ldapLogin} : ${e}`) + this.logger.warn(`${this.checkAuth.name} - ${bindUserDN} : ${e}`) } if (error instanceof InvalidCredentialsError) { return false @@ -103,8 +114,11 @@ export class AuthMethodLdapService implements AuthMethod { await client.unbind() } } - if (error && CONNECT_ERROR_CODE.has(error.code)) { - throw new HttpException('Authentication service error', HttpStatus.INTERNAL_SERVER_ERROR) + if (error) { + this.logger.error(`${this.checkAuth.name} - ${error}`) + if (CONNECT_ERROR_CODE.has(error.code)) { + throw new Error('Authentication service error') + } } return false } @@ -140,6 +154,7 @@ export class AuthMethodLdapService implements AuthMethod { private async updateOrCreateUser(identity: CreateUserDto, user: UserModel): Promise { if (user === null) { // Create + identity.permissions = this.ldapConfig.options.autoCreatePermissions.join(',') const createdUser = await this.adminUsersManager.createUserOrGuest(identity, identity.role) const freshUser = await this.usersManager.fromUserId(createdUser.id) if (!freshUser) { @@ -148,10 +163,12 @@ export class AuthMethodLdapService implements AuthMethod { } return freshUser } + if (identity.login !== user.login) { this.logger.error(`${this.updateOrCreateUser.name} - user login mismatch : ${identity.login} !== ${user.login}`) throw new HttpException('Account matching error', HttpStatus.FORBIDDEN) } + // Update: check if user information has changed const identityHasChanged: UpdateUserDto = Object.fromEntries( ( @@ -166,21 +183,26 @@ export class AuthMethodLdapService implements AuthMethod { ) ).filter(Boolean) ) + if (Object.keys(identityHasChanged).length > 0) { try { if (identityHasChanged?.role != null) { - if (user.role === USER_ROLE.ADMINISTRATOR && !this.ldapConfig.adminGroup) { + if (user.role === USER_ROLE.ADMINISTRATOR && !this.ldapConfig.options.adminGroup) { // Prevent removing the admin role when adminGroup was removed or not defined delete identityHasChanged.role } } + // Update user properties await this.adminUsersManager.updateUserOrGuest(user.id, identityHasChanged) + // Extra stuff if (identityHasChanged?.password) { delete identityHasChanged.password } + Object.assign(user, identityHasChanged) + if ('lastName' in identityHasChanged || 'firstName' in identityHasChanged) { // Force fullName update in the current user model user.setFullName(true) @@ -211,9 +233,9 @@ export class AuthMethodLdapService implements AuthMethod { private createIdentity(entry: LdapUserEntry, password: string): CreateUserDto { const isAdmin = - typeof this.ldapConfig.adminGroup === 'string' && - this.ldapConfig.adminGroup && - entry[LDAP_COMMON_ATTR.MEMBER_OF]?.includes(this.ldapConfig.adminGroup) + typeof this.ldapConfig.options.adminGroup === 'string' && + this.ldapConfig.options.adminGroup && + entry[LDAP_COMMON_ATTR.MEMBER_OF]?.includes(this.ldapConfig.options.adminGroup) return { login: this.dbLogin(entry[this.ldapConfig.attributes.login]), email: entry[this.ldapConfig.attributes.email] as string, diff --git a/backend/src/authentication/services/auth-methods/auth-method-database.service.spec.ts b/backend/src/authentication/providers/mysql/auth-provider-mysql.service.spec.ts similarity index 69% rename from backend/src/authentication/services/auth-methods/auth-method-database.service.spec.ts rename to backend/src/authentication/providers/mysql/auth-provider-mysql.service.spec.ts index 4778964d..967873de 100644 --- a/backend/src/authentication/services/auth-methods/auth-method-database.service.spec.ts +++ b/backend/src/authentication/providers/mysql/auth-provider-mysql.service.spec.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Test, TestingModule } from '@nestjs/testing' import { CONNECT_ERROR_CODE } from '../../../app.constants' import { NotificationsManager } from '../../../applications/notifications/services/notifications-manager.service' @@ -16,18 +10,18 @@ import { generateUserTest } from '../../../applications/users/utils/test' import { hashPassword } from '../../../common/functions' import { Cache } from '../../../infrastructure/cache/services/cache.service' import { DB_TOKEN_PROVIDER } from '../../../infrastructure/database/constants' -import { AuthManager } from '../auth-manager.service' -import { AuthMethodDatabase } from './auth-method-database.service' +import { AuthManager } from '../../auth.service' +import { AuthProviderMySQL } from './auth-provider-mysql.service' -describe(AuthMethodDatabase.name, () => { - let authMethodDatabase: AuthMethodDatabase +describe(AuthProviderMySQL.name, () => { + let authProviderMySQL: AuthProviderMySQL let usersManager: UsersManager let userTest: UserModel beforeAll(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [ - AuthMethodDatabase, + AuthProviderMySQL, UsersManager, UsersQueries, AdminUsersManager, @@ -39,7 +33,7 @@ describe(AuthMethodDatabase.name, () => { ] }).compile() - authMethodDatabase = module.get(AuthMethodDatabase) + authProviderMySQL = module.get(AuthProviderMySQL) usersManager = module.get(UsersManager) module.useLogger(['fatal']) // mocks @@ -48,7 +42,7 @@ describe(AuthMethodDatabase.name, () => { }) it('should be defined', () => { - expect(authMethodDatabase).toBeDefined() + expect(authProviderMySQL).toBeDefined() expect(usersManager).toBeDefined() expect(userTest).toBeDefined() }) @@ -56,7 +50,7 @@ describe(AuthMethodDatabase.name, () => { it('should validate the user', async () => { userTest.makePaths = jest.fn() usersManager.findUser = jest.fn().mockReturnValue({ ...userTest, password: await hashPassword(userTest.password) }) - expect(await authMethodDatabase.validateUser(userTest.login, userTest.password)).toBeDefined() + expect(await authProviderMySQL.validateUser(userTest.login, userTest.password)).toBeDefined() expect(userTest.makePaths).toHaveBeenCalled() }) @@ -71,9 +65,9 @@ describe(AuthMethodDatabase.name, () => { cause: { code: Array.from(CONNECT_ERROR_CODE)[0] } }) ) - expect(await authMethodDatabase.validateUser(userTest.login, userTest.password)).toBeNull() - expect(await authMethodDatabase.validateUser(userTest.login, userTest.password)).toBeNull() - await expect(authMethodDatabase.validateUser(userTest.login, userTest.password)).rejects.toThrow(/db error/i) - await expect(authMethodDatabase.validateUser(userTest.login, userTest.password)).rejects.toThrow(/authentication service/i) + expect(await authProviderMySQL.validateUser(userTest.login, userTest.password)).toBeNull() + expect(await authProviderMySQL.validateUser(userTest.login, userTest.password)).toBeNull() + await expect(authProviderMySQL.validateUser(userTest.login, userTest.password)).rejects.toThrow(/db error/i) + await expect(authProviderMySQL.validateUser(userTest.login, userTest.password)).rejects.toThrow(/authentication service/i) }) }) diff --git a/backend/src/authentication/services/auth-methods/auth-method-database.service.ts b/backend/src/authentication/providers/mysql/auth-provider-mysql.service.ts similarity index 75% rename from backend/src/authentication/services/auth-methods/auth-method-database.service.ts rename to backend/src/authentication/providers/mysql/auth-provider-mysql.service.ts index 0c374338..d47246ab 100644 --- a/backend/src/authentication/services/auth-methods/auth-method-database.service.ts +++ b/backend/src/authentication/providers/mysql/auth-provider-mysql.service.ts @@ -1,19 +1,13 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { HttpException, HttpStatus, Injectable, Logger } from '@nestjs/common' import { CONNECT_ERROR_CODE } from '../../../app.constants' import { UserModel } from '../../../applications/users/models/user.model' import { UsersManager } from '../../../applications/users/services/users-manager.service' import { AUTH_SCOPE } from '../../constants/scope' -import { AuthMethod } from '../../models/auth-method' +import { AuthProvider } from '../auth-providers.models' @Injectable() -export class AuthMethodDatabase implements AuthMethod { - private readonly logger = new Logger(AuthMethodDatabase.name) +export class AuthProviderMySQL implements AuthProvider { + private readonly logger = new Logger(AuthProviderMySQL.name) constructor(private readonly usersManager: UsersManager) {} diff --git a/backend/src/authentication/providers/oidc/auth-oidc-desktop.constants.ts b/backend/src/authentication/providers/oidc/auth-oidc-desktop.constants.ts new file mode 100644 index 00000000..726104a3 --- /dev/null +++ b/backend/src/authentication/providers/oidc/auth-oidc-desktop.constants.ts @@ -0,0 +1,3 @@ +export const OAuthDesktopPortParam = 'desktop_port' as const +export const OAuthDesktopCallBackURI = '/oidc/callback' as const +export const OAuthDesktopLoopbackPorts = new Set([49152, 49153, 49154]) diff --git a/backend/src/authentication/providers/oidc/auth-oidc.config.ts b/backend/src/authentication/providers/oidc/auth-oidc.config.ts new file mode 100644 index 00000000..9eafddfa --- /dev/null +++ b/backend/src/authentication/providers/oidc/auth-oidc.config.ts @@ -0,0 +1,97 @@ +import { Transform, Type } from 'class-transformer' +import { + IsArray, + IsBoolean, + IsDefined, + IsEnum, + IsNotEmpty, + IsNotEmptyObject, + IsObject, + IsOptional, + IsString, + Matches, + ValidateNested +} from 'class-validator' +import { USER_PERMISSION } from '../../../applications/users/constants/user' +import { OAuthTokenEndpoint } from './auth-oidc.constants' + +export class AuthProviderOIDCSecurityConfig { + @IsString() + @Matches(/\bopenid\b/, { message: 'OIDC scope must include "openid"' }) + scope = 'openid email profile' + + @Transform(({ value }) => value || OAuthTokenEndpoint.ClientSecretBasic) + @IsEnum(OAuthTokenEndpoint) + tokenEndpointAuthMethod: OAuthTokenEndpoint = OAuthTokenEndpoint.ClientSecretBasic + + @IsString() + @IsNotEmpty() + tokenSigningAlg = 'RS256' + + @IsOptional() + @IsString() + userInfoSigningAlg? = undefined + + @IsOptional() + @IsBoolean() + skipSubjectCheck? = false +} + +export class AuthProviderOIDCOptionsConfig { + @IsOptional() + @IsBoolean() + autoCreateUser? = true + + @IsOptional() + @IsArray() + @IsEnum(USER_PERMISSION, { each: true }) + autoCreatePermissions?: USER_PERMISSION[] = [] + + @IsOptional() + @IsBoolean() + autoRedirect? = false + + @IsOptional() + @IsBoolean() + enablePasswordAuth? = true + + @IsOptional() + @IsString() + adminRoleOrGroup?: string + + @IsString() + @IsNotEmpty() + buttonText: string = 'Continue with OpenID Connect' +} + +export class AuthProviderOIDCConfig { + @IsString() + @IsNotEmpty() + issuerUrl: string + + @IsString() + @IsNotEmpty() + clientId: string + + @IsString() + @IsNotEmpty() + clientSecret: string + + @IsString() + @IsNotEmpty() + redirectUri: string + + @IsDefined() + @IsNotEmptyObject() + @IsObject() + @ValidateNested() + @Type(() => AuthProviderOIDCOptionsConfig) + options: AuthProviderOIDCOptionsConfig = new AuthProviderOIDCOptionsConfig() + + @IsDefined() + @IsNotEmptyObject() + @IsObject() + @ValidateNested() + @Type(() => AuthProviderOIDCSecurityConfig) + security: AuthProviderOIDCSecurityConfig = new AuthProviderOIDCSecurityConfig() +} diff --git a/backend/src/authentication/providers/oidc/auth-oidc.constants.ts b/backend/src/authentication/providers/oidc/auth-oidc.constants.ts new file mode 100644 index 00000000..249c66de --- /dev/null +++ b/backend/src/authentication/providers/oidc/auth-oidc.constants.ts @@ -0,0 +1,12 @@ +export enum OAuthTokenEndpoint { + ClientSecretPost = 'client_secret_post', + ClientSecretBasic = 'client_secret_basic' +} + +export const OAuthCookie = { + State: 'oidc_state', + Nonce: 'oidc_nonce', + CodeVerifier: 'oidc_code_verifier' +} as const + +export const OAuthCookieSettings = { httpOnly: true, path: '/', maxAge: 600, sameSite: 'lax' } as const diff --git a/backend/src/authentication/providers/oidc/auth-oidc.controller.ts b/backend/src/authentication/providers/oidc/auth-oidc.controller.ts new file mode 100644 index 00000000..cb1c6a68 --- /dev/null +++ b/backend/src/authentication/providers/oidc/auth-oidc.controller.ts @@ -0,0 +1,33 @@ +import { Controller, Get, HttpStatus, Query, Req, Res } from '@nestjs/common' +import { FastifyReply, FastifyRequest } from 'fastify' +import type { UserModel } from '../../../applications/users/models/user.model' +import { AuthManager } from '../../auth.service' +import { AUTH_ROUTE } from '../../constants/routes' +import { AuthTokenSkip } from '../../decorators/auth-token-skip.decorator' +import type { LoginResponseDto } from '../../dto/login-response.dto' +import { OAuthDesktopPortParam } from './auth-oidc-desktop.constants' +import { AuthProviderOIDC } from './auth-provider-oidc.service' + +@Controller(AUTH_ROUTE.BASE) +export class AuthOIDCController { + constructor( + private readonly authManager: AuthManager, + private readonly authProviderOIDC: AuthProviderOIDC + ) {} + + @Get(AUTH_ROUTE.OIDC_LOGIN) + @AuthTokenSkip() + async oidcLogin(@Query(OAuthDesktopPortParam) desktopPort: number, @Res() res: FastifyReply): Promise { + const url = await this.authProviderOIDC.getAuthorizationUrl(res, desktopPort) + // Redirect to OIDC provider + return res.redirect(url, HttpStatus.FOUND) + } + + @Get(AUTH_ROUTE.OIDC_CALLBACK) + @AuthTokenSkip() + async oidcCallback(@Query() query: Record, @Req() req: FastifyRequest, @Res() res: FastifyReply): Promise { + const user: UserModel = await this.authProviderOIDC.handleCallback(req, res, query) + const r: LoginResponseDto = await this.authManager.setCookies(user, res, false) + return res.redirect(this.authProviderOIDC.getRedirectCallbackUrl(r.token.access_expiration, r.token.refresh_expiration), HttpStatus.FOUND) + } +} diff --git a/backend/src/authentication/providers/oidc/auth-oidc.interfaces.ts b/backend/src/authentication/providers/oidc/auth-oidc.interfaces.ts new file mode 100644 index 00000000..4cbf9092 --- /dev/null +++ b/backend/src/authentication/providers/oidc/auth-oidc.interfaces.ts @@ -0,0 +1,5 @@ +export interface AuthOIDCSettings { + loginUrl: string + autoRedirect: boolean + buttonText: string +} diff --git a/backend/src/authentication/providers/oidc/auth-provider-oidc.module.ts b/backend/src/authentication/providers/oidc/auth-provider-oidc.module.ts new file mode 100644 index 00000000..c971f5c2 --- /dev/null +++ b/backend/src/authentication/providers/oidc/auth-provider-oidc.module.ts @@ -0,0 +1,10 @@ +import { Module } from '@nestjs/common' +import { AuthOIDCController } from './auth-oidc.controller' +import { AuthProviderOIDC } from './auth-provider-oidc.service' + +@Module({ + controllers: [AuthOIDCController], + providers: [AuthProviderOIDC], + exports: [AuthProviderOIDC] +}) +export class AuthProviderOIDCModule {} diff --git a/backend/src/authentication/providers/oidc/auth-provider-oidc.service.spec.ts b/backend/src/authentication/providers/oidc/auth-provider-oidc.service.spec.ts new file mode 100644 index 00000000..430cac4d --- /dev/null +++ b/backend/src/authentication/providers/oidc/auth-provider-oidc.service.spec.ts @@ -0,0 +1,246 @@ +import { HttpStatus } from '@nestjs/common' +import { Test, TestingModule } from '@nestjs/testing' +import { + authorizationCodeGrant, + AuthorizationResponseError, + calculatePKCECodeChallenge, + fetchUserInfo, + randomNonce, + randomPKCECodeVerifier, + randomState +} from 'openid-client' +import { USER_ROLE } from '../../../applications/users/constants/user' +import { AdminUsersManager } from '../../../applications/users/services/admin-users-manager.service' +import { UsersManager } from '../../../applications/users/services/users-manager.service' +import { OAuthCookie } from './auth-oidc.constants' +import { AuthProviderOIDC } from './auth-provider-oidc.service' + +jest.mock('../../../configuration/config.environment', () => ({ + configuration: { + auth: { + oidc: { + issuerUrl: 'https://issuer.example.test', + clientId: 'client-id', + clientSecret: 'client-secret', + redirectUri: 'https://api.example.test/auth/oidc/callback', + security: { + scope: 'openid profile email', + tokenSigningAlg: 'RS256', + userInfoSigningAlg: 'RS256', + tokenEndpointAuthMethod: 'client_secret_basic', + skipSubjectCheck: false + }, + options: { + enablePasswordAuth: false, + autoCreateUser: true, + adminRoleOrGroup: 'admins', + autoCreatePermissions: ['read'] + } + } + } + } +})) + +jest.mock('openid-client', () => { + class AuthorizationResponseError extends Error { + code: string + error_description: string + constructor(message: string, options: { cause: URLSearchParams }) { + super(message) + this.code = 'authorization_response_error' + this.error_description = options?.cause?.get('error_description') ?? message + } + } + + return { + allowInsecureRequests: jest.fn(), + authorizationCodeGrant: jest.fn(), + AuthorizationResponseError, + calculatePKCECodeChallenge: jest.fn(), + ClientSecretBasic: jest.fn(), + ClientSecretPost: jest.fn(), + Configuration: class {}, + discovery: jest.fn(), + fetchUserInfo: jest.fn(), + IDToken: class {}, + None: jest.fn(), + randomNonce: jest.fn(), + randomPKCECodeVerifier: jest.fn(), + randomState: jest.fn(), + skipSubjectCheck: Symbol('skipSubjectCheck'), + UserInfoResponse: class {} + } +}) + +describe(AuthProviderOIDC.name, () => { + let service: AuthProviderOIDC + let usersManager: { + findUser: jest.Mock + logUser: jest.Mock + updateAccesses: jest.Mock + fromUserId: jest.Mock + } + let adminUsersManager: { + createUserOrGuest: jest.Mock + updateUserOrGuest: jest.Mock + } + + const makeConfig = (supportsPKCE = true) => ({ + serverMetadata: () => ({ + supportsPKCE: () => supportsPKCE, + authorization_endpoint: 'https://issuer.example.test/authorize' + }) + }) + + const makeReply = () => ({ + header: jest.fn().mockReturnThis(), + setCookie: jest.fn(), + clearCookie: jest.fn() + }) + + beforeAll(async () => { + usersManager = { + findUser: jest.fn(), + logUser: jest.fn(), + updateAccesses: jest.fn().mockResolvedValue(undefined), + fromUserId: jest.fn() + } + adminUsersManager = { + createUserOrGuest: jest.fn(), + updateUserOrGuest: jest.fn() + } + + const module: TestingModule = await Test.createTestingModule({ + providers: [{ provide: UsersManager, useValue: usersManager }, { provide: AdminUsersManager, useValue: adminUsersManager }, AuthProviderOIDC] + }).compile() + + module.useLogger(['fatal']) + service = module.get(AuthProviderOIDC) + }) + + beforeEach(() => { + jest.restoreAllMocks() + jest.clearAllMocks() + }) + + it('returns null when user is not found', async () => { + usersManager.findUser.mockResolvedValue(null) + + const result = await service.validateUser('john', 'secret') + + expect(result).toBeNull() + expect(usersManager.findUser).toHaveBeenCalledWith('john', false) + expect(usersManager.logUser).not.toHaveBeenCalled() + }) + + it('allows local password auth for guest users', async () => { + const guestUser = { id: 1, isGuest: true, isAdmin: false } as any + usersManager.findUser.mockResolvedValue(guestUser) + usersManager.logUser.mockResolvedValue(guestUser) + + const result = await service.validateUser('guest', 'secret') + + expect(usersManager.logUser).toHaveBeenCalledWith(guestUser, 'secret', undefined, undefined) + expect(result).toBe(guestUser) + }) + + it('builds the authorization url with PKCE data and cookies', async () => { + jest.spyOn(service, 'getConfig').mockResolvedValue(makeConfig(true) as any) + ;(randomState as jest.Mock).mockReturnValue('state-1') + ;(randomNonce as jest.Mock).mockReturnValue('nonce-1') + ;(randomPKCECodeVerifier as jest.Mock).mockReturnValue('verifier-1') + ;(calculatePKCECodeChallenge as jest.Mock).mockResolvedValue('challenge-1') + const reply = makeReply() + + const authUrl = await service.getAuthorizationUrl(reply as any) + + expect(reply.header).toHaveBeenCalled() + expect(reply.setCookie).toHaveBeenCalledWith(OAuthCookie.State, 'state-1', expect.any(Object)) + expect(reply.setCookie).toHaveBeenCalledWith(OAuthCookie.Nonce, 'nonce-1', expect.any(Object)) + expect(reply.setCookie).toHaveBeenCalledWith(OAuthCookie.CodeVerifier, 'verifier-1', expect.any(Object)) + const url = new URL(authUrl) + expect(url.searchParams.get('code_challenge')).toBe('challenge-1') + expect(url.searchParams.get('client_id')).toBe('client-id') + }) + + it('handles callback success and clears cookies', async () => { + const config = makeConfig(true) + jest.spyOn(service, 'getConfig').mockResolvedValue(config as any) + const processSpy = jest.spyOn(service as any, 'processUserInfo').mockResolvedValue({ id: 7 } as any) + ;(authorizationCodeGrant as jest.Mock).mockResolvedValue({ + claims: () => ({ sub: 'subject-1' }), + access_token: 'access-token' + }) + ;(fetchUserInfo as jest.Mock).mockResolvedValue({ sub: 'subject-1', email: 'a@b.c', preferred_username: 'alice' }) + const req = { + cookies: { + [OAuthCookie.State]: 'state-1', + [OAuthCookie.Nonce]: 'nonce-1', + [OAuthCookie.CodeVerifier]: 'verifier-1' + }, + ip: '127.0.0.1' + } + const reply = makeReply() + + const result = await service.handleCallback(req as any, reply as any, { code: 'abc' }) + + expect(result).toEqual({ id: 7 }) + expect(processSpy).toHaveBeenCalledWith({ sub: 'subject-1', email: 'a@b.c', preferred_username: 'alice' }, '127.0.0.1') + expect(reply.clearCookie).toHaveBeenCalledWith(OAuthCookie.State, { path: '/' }) + expect(reply.clearCookie).toHaveBeenCalledWith(OAuthCookie.Nonce, { path: '/' }) + expect(reply.clearCookie).toHaveBeenCalledWith(OAuthCookie.CodeVerifier, { path: '/' }) + }) + + it('rejects callback when state is missing', async () => { + jest.spyOn(service, 'getConfig').mockResolvedValue(makeConfig(false) as any) + const reply = makeReply() + const req = { cookies: {}, ip: '127.0.0.1' } + + await expect(service.handleCallback(req as any, reply as any, { code: 'abc' })).rejects.toMatchObject({ status: HttpStatus.BAD_REQUEST }) + expect(reply.clearCookie).toHaveBeenCalledWith(OAuthCookie.State, { path: '/' }) + }) + + it('maps AuthorizationResponseError to BAD_REQUEST', async () => { + jest.spyOn(service, 'getConfig').mockResolvedValue(makeConfig(false) as any) + ;(authorizationCodeGrant as jest.Mock).mockRejectedValue( + new AuthorizationResponseError('access_denied', { + cause: new URLSearchParams('error=access_denied&error_description=No access') + }) + ) + const req = { + cookies: { + [OAuthCookie.State]: 'state-1', + [OAuthCookie.Nonce]: 'nonce-1' + }, + ip: '127.0.0.1' + } + const reply = makeReply() + + await expect(service.handleCallback(req as any, reply as any, { code: 'abc' })).rejects.toMatchObject({ + status: HttpStatus.BAD_REQUEST, + message: 'No access' + }) + }) + + it('builds the redirect callback url with token expirations', () => { + const url = service.getRedirectCallbackUrl(10, 20) + const parsed = new URL(url) + expect(parsed.hash).toContain('access_expiration=10') + expect(parsed.hash).toContain('refresh_expiration=20') + }) + + it('creates identities with admin role when claims match', async () => { + usersManager.findUser.mockResolvedValue(null) + adminUsersManager.createUserOrGuest.mockResolvedValue({ id: 10, login: 'bob' }) + usersManager.fromUserId.mockResolvedValue({ id: 10, role: USER_ROLE.ADMINISTRATOR, login: 'bob', setFullName: jest.fn() } as any) + const userInfo = { sub: 'x', email: 'b@c.d', preferred_username: 'bob', groups: ['admins'] } + + const result = await (service as any).processUserInfo(userInfo, '127.0.0.1') + + expect(adminUsersManager.createUserOrGuest).toHaveBeenCalledWith( + expect.objectContaining({ role: USER_ROLE.ADMINISTRATOR }), + USER_ROLE.ADMINISTRATOR + ) + expect(result.role).toBe(USER_ROLE.ADMINISTRATOR) + }) +}) diff --git a/backend/src/authentication/providers/oidc/auth-provider-oidc.service.ts b/backend/src/authentication/providers/oidc/auth-provider-oidc.service.ts new file mode 100644 index 00000000..3f764cd2 --- /dev/null +++ b/backend/src/authentication/providers/oidc/auth-provider-oidc.service.ts @@ -0,0 +1,407 @@ +import { HttpException, HttpStatus, Injectable, Logger } from '@nestjs/common' +import { FastifyReply, FastifyRequest } from 'fastify' +import { + allowInsecureRequests, + authorizationCodeGrant, + AuthorizationResponseError, + calculatePKCECodeChallenge, + ClientSecretBasic, + ClientSecretPost, + Configuration, + discovery, + fetchUserInfo, + IDToken, + None, + randomNonce, + randomPKCECodeVerifier, + randomState, + skipSubjectCheck, + UserInfoResponse +} from 'openid-client' +import { USER_ROLE } from '../../../applications/users/constants/user' +import type { CreateUserDto, UpdateUserDto } from '../../../applications/users/dto/create-or-update-user.dto' +import { UserModel } from '../../../applications/users/models/user.model' +import { AdminUsersManager } from '../../../applications/users/services/admin-users-manager.service' +import { UsersManager } from '../../../applications/users/services/users-manager.service' +import { generateShortUUID, splitFullName } from '../../../common/functions' +import { configuration } from '../../../configuration/config.environment' +import { AUTH_ROUTE } from '../../constants/routes' +import type { AUTH_SCOPE } from '../../constants/scope' +import { TOKEN_TYPE } from '../../interfaces/token.interface' +import { AUTH_PROVIDER } from '../auth-providers.constants' +import { AuthProvider } from '../auth-providers.models' +import { OAuthDesktopCallBackURI, OAuthDesktopLoopbackPorts, OAuthDesktopPortParam } from './auth-oidc-desktop.constants' +import type { AuthProviderOIDCConfig } from './auth-oidc.config' +import { OAuthCookie, OAuthCookieSettings, OAuthTokenEndpoint } from './auth-oidc.constants' + +@Injectable() +export class AuthProviderOIDC implements AuthProvider { + private readonly logger = new Logger(AuthProviderOIDC.name) + private readonly oidcConfig: AuthProviderOIDCConfig = configuration.auth.oidc + private frontendBaseUrl: string + private config: Configuration = null + + constructor( + private readonly usersManager: UsersManager, + private readonly adminUsersManager: AdminUsersManager + ) {} + + async validateUser(login: string, password: string, ip?: string, scope?: AUTH_SCOPE): Promise { + // Local password authentication path (non-OIDC) + const user: UserModel = await this.usersManager.findUser(login, false) + + if (!user) { + return null + } + + if (user.isGuest || user.isAdmin || scope || this.oidcConfig.options.enablePasswordAuth) { + // Allow local password authentication for: + // - guest users + // - admin users (break-glass access) + // - application scopes (app passwords) + // - regular users when password authentication is enabled + return this.usersManager.logUser(user, password, ip, scope) + } + + return null + } + + async getConfig(): Promise { + if (!this.config) { + this.config = await this.initializeOIDCClient() + } + return this.config + } + + async getAuthorizationUrl(res: FastifyReply, desktopPort?: number): Promise { + const redirectURI = this.getRedirectURI(desktopPort) + const config = await this.getConfig() + + // state: CSRF protection, nonce: binds the ID Token to this auth request (replay protection) + const state = randomState() + const nonce = randomNonce() + + const supportsPKCE = config.serverMetadata().supportsPKCE() + const codeVerifier = supportsPKCE ? randomPKCECodeVerifier() : undefined + + const authUrl = new URL(config.serverMetadata().authorization_endpoint!) + authUrl.searchParams.set('client_id', this.oidcConfig.clientId!) + authUrl.searchParams.set('redirect_uri', redirectURI) + authUrl.searchParams.set('response_type', 'code') + authUrl.searchParams.set('scope', this.oidcConfig.security.scope) + authUrl.searchParams.set('state', state) + authUrl.searchParams.set('nonce', nonce) + if (supportsPKCE) { + const codeChallenge = await calculatePKCECodeChallenge(codeVerifier!) + authUrl.searchParams.set('code_challenge', codeChallenge) + authUrl.searchParams.set('code_challenge_method', 'S256') + } + + // Avoid cache + res + .header('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0') + .header('Pragma', 'no-cache') + .header('Expires', '0') + .header('X-Robots-Tag', 'noindex, nofollow') + .header('Referrer-Policy', 'no-referrer') + + // Store state, nonce, and codeVerifier in httpOnly cookies (expires in 10 minutes) + res.setCookie(OAuthCookie.State, state, OAuthCookieSettings) + res.setCookie(OAuthCookie.Nonce, nonce, OAuthCookieSettings) + if (supportsPKCE) { + res.setCookie(OAuthCookie.CodeVerifier, codeVerifier, OAuthCookieSettings) + } + return authUrl.toString() + } + + async handleCallback(req: FastifyRequest, res: FastifyReply, query: Record): Promise { + const config = await this.getConfig() + const supportsPKCE = config.serverMetadata().supportsPKCE() + const [expectedState, expectedNonce, codeVerifier] = [ + req.cookies[OAuthCookie.State], + req.cookies[OAuthCookie.Nonce], + req.cookies[OAuthCookie.CodeVerifier] + ] + + try { + if (!expectedState?.length) { + throw new HttpException('OAuth state is missing', HttpStatus.BAD_REQUEST) + } + + if (supportsPKCE && !codeVerifier?.length) { + throw new HttpException('OAuth code verifier is missing', HttpStatus.BAD_REQUEST) + } + + const pkceCodeVerifier = supportsPKCE ? codeVerifier : undefined + const callbackParams = new URLSearchParams(query) + + // Get Desktop Port if defined + const desktopPort: string | null = callbackParams.get(OAuthDesktopPortParam) + if (desktopPort) { + callbackParams.delete(OAuthDesktopPortParam) + } + + // Exchange authorization code for tokens + const callbackUrl = new URL(this.getRedirectURI(desktopPort)) + callbackUrl.search = callbackParams.toString() + const tokens = await authorizationCodeGrant(config, callbackUrl, { + expectedState, + pkceCodeVerifier, + expectedNonce + }) + + // Get validated ID token claims + const claims: IDToken = tokens.claims() + if (!claims) { + throw new HttpException('No ID token claims found', HttpStatus.BAD_REQUEST) + } + if (!claims.sub) { + throw new HttpException('Unexpected profile response, no `sub`', HttpStatus.BAD_REQUEST) + } + + // ID token claims may be minimal depending on the IdP; use the UserInfo endpoint to retrieve user details. + // Get user info from the userinfo endpoint (requires access token and subject from ID token). + const subject = this.oidcConfig.security.skipSubjectCheck ? skipSubjectCheck : claims.sub + const userInfo: UserInfoResponse = await fetchUserInfo(config, tokens.access_token, subject) + + if (!userInfo.sub) { + throw new Error('Unexpected profile response, no `sub`') + } + + // Process the user info and create/update the user + return await this.processUserInfo(userInfo, req.ip) + } catch (error: AuthorizationResponseError | HttpException | any) { + if (error instanceof AuthorizationResponseError) { + this.logger.error(`${this.handleCallback.name} - OIDC callback error: ${error.code} - ${error.error_description}`) + throw new HttpException(error.error_description, HttpStatus.BAD_REQUEST) + } else { + this.logger.error(`${this.handleCallback.name} - OIDC callback error: ${error}`) + throw new HttpException( + error.error_description ?? 'OIDC authentication failed', + error instanceof HttpException ? error.getStatus() : (error.status ?? HttpStatus.INTERNAL_SERVER_ERROR) + ) + } + } finally { + // Always clear temporary OIDC cookies (success or failure) + Object.values(OAuthCookie).forEach((value) => { + res.clearCookie(value, { path: '/' }) + }) + } + } + + getRedirectCallbackUrl(accessExpiration: number, refreshExpiration: number) { + if (!this.frontendBaseUrl) { + const url = new URL(this.oidcConfig.redirectUri) + const apiIndex = url.pathname.indexOf(AUTH_ROUTE.BASE) + this.frontendBaseUrl = apiIndex >= 0 ? `${url.origin}${url.pathname.slice(0, apiIndex)}` : url.origin + } + const url = new URL(this.frontendBaseUrl) + const params = new URLSearchParams({ + [AUTH_PROVIDER.OIDC]: 'true', + [`${TOKEN_TYPE.ACCESS}_expiration`]: `${accessExpiration}`, + [`${TOKEN_TYPE.REFRESH}_expiration`]: `${refreshExpiration}` + }) + url.hash = `/?${params.toString()}` + return url.toString() + } + + getRedirectURI(desktopPort?: number | string): string { + // web / default callback + if (!desktopPort) return this.oidcConfig.redirectUri + // desktop app callback + if (typeof desktopPort === 'string') { + desktopPort = Number(desktopPort) + } + if (!Number.isInteger(desktopPort) || !OAuthDesktopLoopbackPorts.has(desktopPort)) { + throw new HttpException('Invalid desktop_port', HttpStatus.BAD_REQUEST) + } + // The redirect url must be known from provider + return `http://127.0.0.1:${desktopPort}${OAuthDesktopCallBackURI}` + } + + private async initializeOIDCClient(): Promise { + try { + const issuerUrl = new URL(this.oidcConfig.issuerUrl) + const config: Configuration = await discovery( + issuerUrl, + this.oidcConfig.clientId, + { + client_secret: this.oidcConfig.clientSecret, + response_types: ['code'], + id_token_signed_response_alg: this.oidcConfig.security.tokenSigningAlg, + userinfo_signed_response_alg: this.oidcConfig.security.userInfoSigningAlg + }, + this.getTokenAuthMethod(this.oidcConfig.security.tokenEndpointAuthMethod, this.oidcConfig.clientSecret), + { + execute: [allowInsecureRequests], + timeout: 6000 + } + ) + this.logger.log(`${this.initializeOIDCClient.name} - OIDC client initialized successfully for issuer: ${this.oidcConfig.issuerUrl}`) + return config + } catch (error) { + this.logger.error(`${this.initializeOIDCClient.name} - OIDC client initialization failed: ${error?.cause || error}`) + switch (error.cause?.code) { + case 'ECONNREFUSED': + case 'ENOTFOUND': + throw new HttpException('OIDC provider unavailable', HttpStatus.SERVICE_UNAVAILABLE) + + case 'ETIMEDOUT': + throw new HttpException('OIDC provider timeout', HttpStatus.GATEWAY_TIMEOUT) + + default: + throw new HttpException('OIDC client initialization failed', HttpStatus.INTERNAL_SERVER_ERROR) + } + } + } + + private getTokenAuthMethod(tokenEndpointAuthMethod: OAuthTokenEndpoint, clientSecret?: string) { + if (!clientSecret) { + return None() + } + + switch (tokenEndpointAuthMethod) { + case OAuthTokenEndpoint.ClientSecretPost: { + return ClientSecretPost(clientSecret) + } + + case OAuthTokenEndpoint.ClientSecretBasic: { + return ClientSecretBasic(clientSecret) + } + + default: { + return None() + } + } + } + + private async processUserInfo(userInfo: UserInfoResponse, ip?: string): Promise { + // Extract user information + const { login, email } = this.extractLoginAndEmail(userInfo) + + // Check if user exists + let user: UserModel = await this.usersManager.findUser(email || login, false) + + if (!user && !this.oidcConfig.options.autoCreateUser) { + this.logger.warn(`${this.validateUser.name} - User not found and autoCreateUser is disabled`) + throw new HttpException('User not found', HttpStatus.UNAUTHORIZED) + } + + // Determine if user should be admin based on groups/roles + const isAdmin = this.checkAdminRole(userInfo) + + // Create identity + const identity = this.createIdentity(login, email, userInfo, isAdmin) + + // Create or update user + user = await this.updateOrCreateUser(identity, user) + + // Update user access log + this.usersManager.updateAccesses(user, ip, true).catch((e: Error) => this.logger.error(`${this.processUserInfo.name} : ${e}`)) + + return user + } + + private checkAdminRole(userInfo: UserInfoResponse): boolean { + if (!this.oidcConfig.options.adminRoleOrGroup) { + return false + } + + // Check claims + const claims = [...(Array.isArray(userInfo.groups) ? userInfo.groups : []), ...(Array.isArray(userInfo.roles) ? userInfo.roles : [])] + + return claims.includes(this.oidcConfig.options.adminRoleOrGroup) + } + + private createIdentity( + login: string, + email: string, + userInfo: UserInfoResponse, + isAdmin: boolean + ): Omit & { password?: string } { + // Get first name and last name + let firstName = userInfo.given_name || '' + let lastName = userInfo.family_name || '' + + // If structured names not available, try to split the full name + if (!firstName && !lastName && userInfo.name) { + const names = splitFullName(userInfo.name) + firstName = names.firstName + lastName = names.lastName + } + + return { + login, + email, + role: isAdmin ? USER_ROLE.ADMINISTRATOR : USER_ROLE.USER, + firstName, + lastName + } + } + + private async updateOrCreateUser(identity: Omit & { password?: string }, user: UserModel | null): Promise { + if (user === null) { + // Create new user with a random password (required by the system but not used for OIDC login) + const userWithPassword = { + ...identity, + password: generateShortUUID(24), + permissions: this.oidcConfig.options.autoCreatePermissions.join(',') + } as CreateUserDto + const createdUser = await this.adminUsersManager.createUserOrGuest(userWithPassword, identity.role) + const freshUser = await this.usersManager.fromUserId(createdUser.id) + if (!freshUser) { + this.logger.error(`${this.updateOrCreateUser.name} - user was not found : ${createdUser.login} (${createdUser.id})`) + throw new HttpException('User not found', HttpStatus.NOT_FOUND) + } + return freshUser + } + + // Check if user information has changed (excluding password) + const identityHasChanged: UpdateUserDto = Object.fromEntries( + Object.keys(identity) + .filter((key) => key !== 'password') + .map((key: string) => (identity[key] !== user[key] ? [key, identity[key]] : null)) + .filter(Boolean) + ) + + if (Object.keys(identityHasChanged).length > 0) { + try { + if (identityHasChanged?.role != null) { + if (user.role === USER_ROLE.ADMINISTRATOR && !this.oidcConfig.options.adminRoleOrGroup) { + // Prevent removing the admin role when adminGroup was removed or not defined + delete identityHasChanged.role + } + } + + // Update user properties + await this.adminUsersManager.updateUserOrGuest(user.id, identityHasChanged) + + // Update local user object + Object.assign(user, identityHasChanged) + + if ('lastName' in identityHasChanged || 'firstName' in identityHasChanged) { + // Force fullName update in the current user model + user.setFullName(true) + } + } catch (e) { + this.logger.warn(`${this.updateOrCreateUser.name} - unable to update user *${user.login}* : ${e}`) + } + } + + return user + } + + private extractLoginAndEmail(userInfo: UserInfoResponse) { + const email = userInfo.email ? userInfo.email.trim() : undefined + if (!email) { + throw new HttpException('No email address found in the OIDC profile', HttpStatus.BAD_REQUEST) + } + + const login = userInfo.preferred_username ?? (email ? email.split('@')[0] : undefined) ?? userInfo.sub + if (!login) { + throw new HttpException('Unable to determine the OIDC profile login', HttpStatus.BAD_REQUEST) + } + + return { login: login.trim().toLowerCase(), email } + } +} diff --git a/backend/src/authentication/services/auth-methods/auth-method-two-fa.service.spec.ts b/backend/src/authentication/providers/two-fa/auth-provider-two-fa.service.spec.ts similarity index 96% rename from backend/src/authentication/services/auth-methods/auth-method-two-fa.service.spec.ts rename to backend/src/authentication/providers/two-fa/auth-provider-two-fa.service.spec.ts index 484c680a..c77bee98 100644 --- a/backend/src/authentication/services/auth-methods/auth-method-two-fa.service.spec.ts +++ b/backend/src/authentication/providers/two-fa/auth-provider-two-fa.service.spec.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { HttpException, HttpStatus } from '@nestjs/common' import { Test, TestingModule } from '@nestjs/testing' import { Totp } from 'time2fa' @@ -11,16 +5,16 @@ import { NotificationsManager } from '../../../applications/notifications/servic import { UserModel } from '../../../applications/users/models/user.model' import { UsersManager } from '../../../applications/users/services/users-manager.service' import { Cache } from '../../../infrastructure/cache/services/cache.service' -import { TwoFaVerifyDto, TwoFaVerifyWithPasswordDto } from '../../dto/two-fa-verify.dto' import { FastifyAuthenticatedRequest } from '../../interfaces/auth-request.interface' import { decryptSecret, encryptSecret } from '../../utils/crypt-secret' -import { AuthMethod2FA } from './auth-method-two-fa.service' +import { AuthProvider2FA } from './auth-provider-two-fa.service' +import { TwoFaVerifyDto, TwoFaVerifyWithPasswordDto } from './auth-two-fa.dtos' jest.mock('../../utils/crypt-secret') jest.mock('../../../common/qrcode') -describe(AuthMethod2FA.name, () => { - let service: AuthMethod2FA +describe(AuthProvider2FA.name, () => { + let service: AuthProvider2FA let cache: jest.Mocked let usersManager: jest.Mocked let notificationsManager: jest.Mocked @@ -44,7 +38,7 @@ describe(AuthMethod2FA.name, () => { beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [ - AuthMethod2FA, + AuthProvider2FA, { provide: Cache, useValue: { @@ -72,7 +66,7 @@ describe(AuthMethod2FA.name, () => { }).compile() module.useLogger(['fatal']) - service = module.get(AuthMethod2FA) + service = module.get(AuthProvider2FA) cache = module.get(Cache) usersManager = module.get(UsersManager) notificationsManager = module.get(NotificationsManager) diff --git a/backend/src/authentication/services/auth-methods/auth-method-two-fa.service.ts b/backend/src/authentication/providers/two-fa/auth-provider-two-fa.service.ts similarity index 87% rename from backend/src/authentication/services/auth-methods/auth-method-two-fa.service.ts rename to backend/src/authentication/providers/two-fa/auth-provider-two-fa.service.ts index 3a3a7657..ed07a0f1 100644 --- a/backend/src/authentication/services/auth-methods/auth-method-two-fa.service.ts +++ b/backend/src/authentication/providers/two-fa/auth-provider-two-fa.service.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { HttpException, HttpStatus, Injectable, Logger } from '@nestjs/common' import { Totp } from 'time2fa' import { NOTIFICATION_APP, NOTIFICATION_APP_EVENT } from '../../../applications/notifications/constants/notifications' @@ -17,14 +11,14 @@ import { qrcodeToDataURL } from '../../../common/qrcode' import { configuration } from '../../../configuration/config.environment' import { Cache } from '../../../infrastructure/cache/services/cache.service' import { TWO_FA_CODE_LENGTH } from '../../constants/auth' -import { TwoFaVerifyDto, TwoFaVerifyWithPasswordDto } from '../../dto/two-fa-verify.dto' import { FastifyAuthenticatedRequest } from '../../interfaces/auth-request.interface' -import { TwoFaEnableResult, TwoFaSetup, TwoFaVerifyResult } from '../../interfaces/two-fa-setup.interface' import { decryptSecret, encryptSecret } from '../../utils/crypt-secret' +import { TwoFaVerifyDto, TwoFaVerifyWithPasswordDto } from './auth-two-fa.dtos' +import { TwoFaEnableResult, TwoFaSetup, TwoFaVerifyResult } from './auth-two-fa.interfaces' @Injectable() -export class AuthMethod2FA { - private readonly logger = new Logger(AuthMethod2FA.name) +export class AuthProvider2FA { + private readonly logger = new Logger(AuthProvider2FA.name) private readonly cacheKeyPrefix = 'auth-2fa-pending-user-' constructor( @@ -143,30 +137,31 @@ export class AuthMethod2FA { return auth } - private async validateRecoveryCode(userId: number, code: string, encryptedCodes: string[]): Promise { + async validateRecoveryCode(userId: number, code: string, encryptedCodes: string[]): Promise { const auth: TwoFaVerifyResult = { success: false, message: '' } if (!encryptedCodes || encryptedCodes.length === 0) { auth.message = 'Invalid code' - } else { - try { - for (const encCode of encryptedCodes) { - if (code === this.decryptSecret(encCode)) { - auth.success = true - // removed used code - encryptedCodes.splice(encryptedCodes.indexOf(encCode), 1) - break - } - } - if (auth.success) { - // update recovery codes - await this.usersManager.updateSecrets(userId, { recoveryCodes: encryptedCodes }) - } else { - auth.message = 'Invalid code' + return auth + } + try { + for (const encCode of encryptedCodes) { + const decryptedCode = this.decryptSecret(encCode) + if (code === decryptedCode) { + auth.success = true + // removed used code + encryptedCodes.splice(encryptedCodes.indexOf(encCode), 1) + break } - } catch (e) { - this.logger.error(`${this.validateRecoveryCode.name} - ${e}`) - auth.message = e.message } + if (auth.success) { + // update recovery codes + await this.usersManager.updateSecrets(userId, { recoveryCodes: encryptedCodes }) + } else { + auth.message = 'Invalid code' + } + } catch (e) { + this.logger.error(`${this.validateRecoveryCode.name} - ${e}`) + auth.message = e.message } return auth } diff --git a/backend/src/authentication/guards/auth-two-fa-guard.ts b/backend/src/authentication/providers/two-fa/auth-two-fa-guard.ts similarity index 58% rename from backend/src/authentication/guards/auth-two-fa-guard.ts rename to backend/src/authentication/providers/two-fa/auth-two-fa-guard.ts index ca893759..f36e57d3 100644 --- a/backend/src/authentication/guards/auth-two-fa-guard.ts +++ b/backend/src/authentication/providers/two-fa/auth-two-fa-guard.ts @@ -1,14 +1,8 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { CanActivate, ExecutionContext, HttpException, HttpStatus, Injectable, mixin, Type } from '@nestjs/common' -import { configuration } from '../../configuration/config.environment' -import { TWO_FA_HEADER_CODE, TWO_FA_HEADER_PASSWORD } from '../constants/auth' -import { FastifyAuthenticatedRequest } from '../interfaces/auth-request.interface' -import { AuthMethod2FA } from '../services/auth-methods/auth-method-two-fa.service' +import { configuration } from '../../../configuration/config.environment' +import { TWO_FA_HEADER_CODE, TWO_FA_HEADER_PASSWORD } from '../../constants/auth' +import { FastifyAuthenticatedRequest } from '../../interfaces/auth-request.interface' +import { AuthProvider2FA } from './auth-provider-two-fa.service' export const AuthTwoFaGuard = AuthTwoFaGuardFactory() export const AuthTwoFaGuardWithoutPassword = AuthTwoFaGuardFactory({ withPassword: false }) @@ -20,17 +14,17 @@ interface TwoFaGuardOptions { function AuthTwoFaGuardFactory(options: TwoFaGuardOptions = { withPassword: true }): Type { @Injectable() class MixinAuthTwoFaGuard implements CanActivate { - constructor(private readonly authMethod2FA: AuthMethod2FA) {} + constructor(private readonly authProvider2FA: AuthProvider2FA) {} async canActivate(ctx: ExecutionContext): Promise { const req: FastifyAuthenticatedRequest = ctx.switchToHttp().getRequest() - const user = await this.authMethod2FA.loadUser(req.user.id, req.ip) + const user = await this.authProvider2FA.loadUser(req.user.id, req.ip) if (options.withPassword) { if (!req.headers[TWO_FA_HEADER_PASSWORD]) { throw new HttpException('Missing TWO-FA password', HttpStatus.FORBIDDEN) } - await this.authMethod2FA.verifyUserPassword(user, req.headers[TWO_FA_HEADER_PASSWORD] as string, req.ip) + await this.authProvider2FA.verifyUserPassword(user, req.headers[TWO_FA_HEADER_PASSWORD] as string, req.ip) } if (!configuration.auth.mfa.totp.enabled || !user.twoFaEnabled) { @@ -41,7 +35,7 @@ function AuthTwoFaGuardFactory(options: TwoFaGuardOptions = { withPassword: true throw new HttpException('Missing TWO-FA code', HttpStatus.FORBIDDEN) } - const auth = await this.authMethod2FA.verify({ code: req.headers[TWO_FA_HEADER_CODE] as string }, req) + const auth = await this.authProvider2FA.verify({ code: req.headers[TWO_FA_HEADER_CODE] as string }, req) if (!auth.success) { throw new HttpException(auth.message, HttpStatus.FORBIDDEN) diff --git a/backend/src/authentication/providers/two-fa/auth-two-fa.config.ts b/backend/src/authentication/providers/two-fa/auth-two-fa.config.ts new file mode 100644 index 00000000..bc472ff7 --- /dev/null +++ b/backend/src/authentication/providers/two-fa/auth-two-fa.config.ts @@ -0,0 +1,20 @@ +import { Type } from 'class-transformer' +import { IsBoolean, IsDefined, IsNotEmptyObject, IsObject, IsString, ValidateNested } from 'class-validator' +import { SERVER_NAME } from '../../../common/shared' + +export class AuthMFATotpConfig { + @IsBoolean() + enabled = true + + @IsString() + issuer = SERVER_NAME +} + +export class AuthMFAConfig { + @IsDefined() + @IsNotEmptyObject() + @IsObject() + @ValidateNested() + @Type(() => AuthMFATotpConfig) + totp: AuthMFATotpConfig = new AuthMFATotpConfig() +} diff --git a/backend/src/authentication/dto/two-fa-verify.dto.ts b/backend/src/authentication/providers/two-fa/auth-two-fa.dtos.ts similarity index 62% rename from backend/src/authentication/dto/two-fa-verify.dto.ts rename to backend/src/authentication/providers/two-fa/auth-two-fa.dtos.ts index 2e65e45a..da8e30b0 100644 --- a/backend/src/authentication/dto/two-fa-verify.dto.ts +++ b/backend/src/authentication/providers/two-fa/auth-two-fa.dtos.ts @@ -1,10 +1,10 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { IsBoolean, IsNotEmpty, IsOptional, IsString } from 'class-validator' +import { LoginResponseDto } from '../../dto/login-response.dto' + +export class TwoFaResponseDto extends LoginResponseDto { + success: boolean + message: string +} export class TwoFaVerifyDto { @IsString() diff --git a/backend/src/authentication/interfaces/two-fa-setup.interface.ts b/backend/src/authentication/providers/two-fa/auth-two-fa.interfaces.ts similarity index 54% rename from backend/src/authentication/interfaces/two-fa-setup.interface.ts rename to backend/src/authentication/providers/two-fa/auth-two-fa.interfaces.ts index e2a3961d..9f73d3e0 100644 --- a/backend/src/authentication/interfaces/two-fa-setup.interface.ts +++ b/backend/src/authentication/providers/two-fa/auth-two-fa.interfaces.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - export interface TwoFaSetup { secret: string qrDataUrl: string diff --git a/backend/src/authentication/services/auth-methods/auth-method-ldap.service.spec.ts b/backend/src/authentication/services/auth-methods/auth-method-ldap.service.spec.ts deleted file mode 100644 index f452c87f..00000000 --- a/backend/src/authentication/services/auth-methods/auth-method-ldap.service.spec.ts +++ /dev/null @@ -1,393 +0,0 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - -import { Test, TestingModule } from '@nestjs/testing' -import { Mocked } from 'jest-mock' -import { Client, InvalidCredentialsError } from 'ldapts' -import { CONNECT_ERROR_CODE } from '../../../app.constants' -import { UserModel } from '../../../applications/users/models/user.model' -import { AdminUsersManager } from '../../../applications/users/services/admin-users-manager.service' -import { UsersManager } from '../../../applications/users/services/users-manager.service' -import * as commonFunctions from '../../../common/functions' -import { configuration } from '../../../configuration/config.environment' -import { LDAP_LOGIN_ATTR } from '../../constants/auth-ldap' -import { AuthMethodLdapService } from './auth-method-ldap.service' - -// Mock ldapts Client to simulate LDAP behaviors -jest.mock('ldapts', () => { - const actual = jest.requireActual('ldapts') - const mockClientInstance = { - bind: jest.fn(), - search: jest.fn(), - unbind: jest.fn() - } - const Client = jest.fn().mockImplementation(() => mockClientInstance) - // Conserver tous les autres exports réels (dont EqualityFilter, AndFilter, InvalidCredentialsError, etc.) - return { ...actual, Client } -}) - -// --- Test helpers (DRY) --- -// Reusable LDAP mocks -const mockBindResolve = (ldapClient: any) => { - ldapClient.bind.mockResolvedValue(undefined) - ldapClient.unbind.mockResolvedValue(undefined) -} -const mockBindRejectInvalid = (ldapClient: any, InvalidCredentialsErrorCtor: any, message = 'invalid') => { - ldapClient.bind.mockRejectedValue(new InvalidCredentialsErrorCtor(message)) - ldapClient.unbind.mockResolvedValue(undefined) -} -const mockSearchEntries = (ldapClient: any, entries: any[]) => { - ldapClient.search.mockResolvedValue({ searchEntries: entries }) -} -const mockSearchReject = (ldapClient: any, err: Error) => { - ldapClient.search.mockRejectedValue(err) -} -// User factory -const buildUser = (overrides: Partial = {}) => - ({ - id: 0, - login: 'john', - email: 'old@example.org', - password: 'hashed', - isGuest: false, - isActive: true, - makePaths: jest.fn().mockResolvedValue(undefined), - setFullName: jest.fn(), // needed when firstName/lastName change - ...overrides - }) as any - -// -------------------------- - -describe(AuthMethodLdapService.name, () => { - let authMethodLdapService: AuthMethodLdapService - let usersManager: Mocked - let adminUsersManager: Mocked - const ldapClient = { - bind: jest.fn(), - search: jest.fn(), - unbind: jest.fn() - } - ;(Client as Mocked).mockImplementation(() => ldapClient) - - // Local helpers (need access to authMethodLdapService and ldapClient in this scope) - const setupLdapSuccess = (entries: any[]) => { - mockBindResolve(ldapClient) - mockSearchEntries(ldapClient, entries) - } - const spyLoggerError = () => jest.spyOn(authMethodLdapService['logger'], 'error').mockImplementation(() => undefined as any) - - beforeAll(async () => { - configuration.auth.ldap = { - servers: ['ldap://localhost:389'], - attributes: { login: LDAP_LOGIN_ATTR.UID, email: 'mail' }, - baseDN: 'ou=people,dc=example,dc=org', - filter: '' - } - - const module: TestingModule = await Test.createTestingModule({ - providers: [ - AuthMethodLdapService, - { - provide: UsersManager, - useValue: { - findUser: jest.fn(), - logUser: jest.fn(), - updateAccesses: jest.fn().mockResolvedValue(undefined), - validateAppPassword: jest.fn(), - fromUserId: jest.fn() - } - }, - { - provide: AdminUsersManager, - useValue: { - createUserOrGuest: jest.fn(), - updateUserOrGuest: jest.fn() - } - } - ] - }).compile() - - module.useLogger(['fatal']) - authMethodLdapService = module.get(AuthMethodLdapService) - adminUsersManager = module.get>(AdminUsersManager) - usersManager = module.get>(UsersManager) - }) - - it('should be defined', () => { - expect(authMethodLdapService).toBeDefined() - expect(usersManager).toBeDefined() - expect(adminUsersManager).toBeDefined() - expect(ldapClient).toBeDefined() - }) - - it('should authenticate a guest user via database and bypass LDAP', async () => { - // Arrange - const guestUser: any = { id: 1, login: 'guest1', isGuest: true, isActive: true } - usersManager.findUser.mockResolvedValue(guestUser) - const dbAuthResult: any = { ...guestUser, token: 'jwt' } - usersManager.logUser.mockResolvedValue(dbAuthResult) - const res = await authMethodLdapService.validateUser('guest1', 'pass', '127.0.0.1') - expect(res).toEqual(dbAuthResult) - expect(usersManager.logUser).toHaveBeenCalledWith(guestUser, 'pass', '127.0.0.1') - expect(Client).not.toHaveBeenCalled() // client should not be constructed - }) - - it('should throw FORBIDDEN for locked account and resolve null for LDAP login mismatch', async () => { - // Phase 1: locked account - usersManager.findUser.mockResolvedValue({ login: 'john', isGuest: false, isActive: false } as UserModel) - const loggerErrorSpy1 = jest.spyOn(authMethodLdapService['logger'], 'error').mockImplementation(() => undefined as any) - await expect(authMethodLdapService.validateUser('john', 'pwd')).rejects.toThrow(/account locked/i) - expect(loggerErrorSpy1).toHaveBeenCalled() - - // Phase 2: mismatch between requested login and LDAP returned login -> service renvoie null - const existingUser: any = buildUser({ id: 8 }) - usersManager.findUser.mockResolvedValue(existingUser) - mockBindResolve(ldapClient) - mockSearchEntries(ldapClient, [{ uid: 'jane', cn: 'john', mail: 'jane@example.org' }]) - await expect(authMethodLdapService.validateUser('john', 'pwd')).rejects.toThrow(/account matching error/i) - }) - - it('should handle invalid LDAP credentials for both existing and unknown users', async () => { - // Phase 1: existing user -> updateAccesses invoked with success=false and logger.error intercepted - const existingUser: any = buildUser({ id: 1 }) - usersManager.findUser.mockResolvedValue(existingUser) - // Make LDAP bind throw InvalidCredentialsError - mockBindRejectInvalid(ldapClient, InvalidCredentialsError, 'invalid credentials') - // Force updateAccesses to reject to hit the catch and logger.error - const loggerErrorSpy = jest.spyOn(authMethodLdapService['logger'], 'error').mockImplementation(() => undefined as any) - usersManager.updateAccesses.mockRejectedValueOnce(new Error('updateAccesses boom')) - const res1 = await authMethodLdapService.validateUser('john', 'badpwd', '10.0.0.1') - expect(res1).toBeNull() - expect(usersManager.updateAccesses).toHaveBeenCalledWith(existingUser, '10.0.0.1', false) - expect(loggerErrorSpy).toHaveBeenCalled() - - // Phase 2: unknown user → no access update - usersManager.updateAccesses.mockClear() - usersManager.findUser.mockResolvedValue(null) - ldapClient.bind.mockRejectedValue(new InvalidCredentialsError('invalid')) - ldapClient.unbind.mockResolvedValue(undefined) - const res2 = await authMethodLdapService.validateUser('jane', 'badpwd') - expect(res2).toBeNull() - expect(usersManager.updateAccesses).not.toHaveBeenCalled() - }) - - it('should handle LDAP new-user flow: missing fields, creation success, and multi-email selection', async () => { - // Phase 1: incomplete LDAP entry -> null + error log, no creation - usersManager.findUser.mockResolvedValue(null) - mockBindResolve(ldapClient) - // Simulate an entry with missing mail - mockSearchEntries(ldapClient, [{ uid: 'jane', cn: 'Jane Doe', mail: undefined }]) - const loggerErrorSpy = jest.spyOn(authMethodLdapService['logger'], 'error').mockImplementation(() => undefined as any) - const resA = await authMethodLdapService.validateUser('jane', 'pwd') - expect(resA).toBeNull() - expect(adminUsersManager.createUserOrGuest).not.toHaveBeenCalled() - expect(loggerErrorSpy).toHaveBeenCalled() - - // Phase 2: create a new user (success, single email) - // Stub directement checkAuth pour retourner une entrée LDAP valide - const checkAuthSpy = jest.spyOn(authMethodLdapService as any, 'checkAuth') - checkAuthSpy.mockResolvedValueOnce({ uid: 'john', cn: 'John Doe', mail: 'john@example.org' } as any) - adminUsersManager.createUserOrGuest.mockClear() - usersManager.findUser.mockResolvedValue(null) - const createdUser: any = { id: 2, login: 'john', isGuest: false, isActive: true, makePaths: jest.fn() } - adminUsersManager.createUserOrGuest.mockResolvedValue(createdUser) - // If the service reloads the user via fromUserId after creation - usersManager.fromUserId.mockResolvedValue(createdUser) - // Cover the success-flow catch branch - const loggerErrorSpy2 = spyLoggerError() - usersManager.updateAccesses.mockRejectedValueOnce(new Error('updateAccesses success flow boom')) - const resB = await authMethodLdapService.validateUser('john', 'pwd', '192.168.1.10') - expect(adminUsersManager.createUserOrGuest).toHaveBeenCalledWith( - { login: 'john', email: 'john@example.org', password: 'pwd', firstName: 'John', lastName: 'Doe', role: 1 }, - expect.anything() // USER_ROLE.USER - ) - expect(resB).toBe(createdUser) - expect(usersManager.updateAccesses).toHaveBeenCalledWith(createdUser, '192.168.1.10', true) - expect(loggerErrorSpy2).toHaveBeenCalled() - // Phase 3: multiple emails -> keep the first - adminUsersManager.createUserOrGuest.mockClear() - usersManager.findUser.mockResolvedValue(null) - setupLdapSuccess([{ uid: 'multi', cn: 'Multi Mail', mail: ['first@example.org', 'second@example.org'] }]) - const createdUser2: any = { id: 9, login: 'multi', makePaths: jest.fn() } - adminUsersManager.createUserOrGuest.mockResolvedValue(createdUser2) - usersManager.fromUserId.mockResolvedValue(createdUser2) - const resC = await authMethodLdapService.validateUser('multi', 'pwd') - expect(adminUsersManager.createUserOrGuest).toHaveBeenCalledWith(expect.objectContaining({ email: 'first@example.org' }), expect.anything()) - expect(resC).toBe(createdUser2) - }) - - it('should update existing user profile when LDAP identity changed (except password assigned back)', async () => { - // Arrange: existing user with different profile and an old password - const existingUser: any = buildUser({ id: 5 }) - usersManager.findUser.mockResolvedValue(existingUser) - // LDAP succeeds and returns different email and same uid - setupLdapSuccess([{ uid: 'john', cn: 'John Doe', mail: 'john@example.org' }]) - // Admin manager successfully updates a user - adminUsersManager.updateUserOrGuest.mockResolvedValue(undefined) - // Ensure password is considered changed so the update payload includes it, - // which then triggers the deletion and local assignment branches after update - const compareSpy = jest.spyOn(commonFunctions, 'comparePassword').mockResolvedValue(false) - const res = await authMethodLdapService.validateUser('john', 'new-plain-password', '127.0.0.2') - expect(adminUsersManager.updateUserOrGuest).toHaveBeenCalledWith( - 5, - expect.objectContaining({ - email: 'john@example.org', - firstName: 'John', - lastName: 'Doe' - }) - ) - // Password should not be assigned back onto the user object (it is deleted before Object.assign) - expect(existingUser.password).toBe('hashed') - // Other fields should be updated locally - expect(existingUser.email).toBe('john@example.org') - expect(existingUser).toMatchObject({ firstName: 'John', lastName: 'Doe' }) - // Accesses updated as success - expect(usersManager.updateAccesses).toHaveBeenCalledWith(existingUser, '127.0.0.2', true) - // Returned user is the same instance - expect(res).toBe(existingUser) - - // Second run: password unchanged (comparePassword => true) to cover the null branch for password - adminUsersManager.updateUserOrGuest.mockClear() - usersManager.updateAccesses.mockClear() - // Force another non-password change so an update occurs - existingUser.email = 'old@example.org' - compareSpy.mockResolvedValue(true) - const res2 = await authMethodLdapService.validateUser('john', 'same-plain-password', '127.0.0.3') - // Update should be called without password, only with changed fields - expect(adminUsersManager.updateUserOrGuest).toHaveBeenCalled() - const updateArgs = adminUsersManager.updateUserOrGuest.mock.calls[0] - expect(updateArgs[0]).toBe(5) - expect(updateArgs[1]).toEqual( - expect.objectContaining({ - email: 'john@example.org' - }) - ) - expect(updateArgs[1]).toEqual(expect.not.objectContaining({ password: expect.anything() })) - // Password remains unchanged locally - expect(existingUser.password).toBe('hashed') - // Accesses updated as success - expect(usersManager.updateAccesses).toHaveBeenCalledWith(existingUser, '127.0.0.3', true) - // Returned user is the same instance - expect(res2).toBe(existingUser) - // Third run: no changes at all (identityHasChanged is empty) to cover the else branch - adminUsersManager.updateUserOrGuest.mockClear() - usersManager.updateAccesses.mockClear() - compareSpy.mockResolvedValue(true) - // Local user already matches LDAP identity; call again - const res3 = await authMethodLdapService.validateUser('john', 'same-plain-password', '127.0.0.4') - // No update should be triggered - expect(adminUsersManager.updateUserOrGuest).not.toHaveBeenCalled() - // Access should still be updated as success - expect(usersManager.updateAccesses).toHaveBeenCalledWith(existingUser, '127.0.0.4', true) - // Returned user is the same instance - expect(res3).toBe(existingUser) - }) - - it('should log failed access when LDAP search returns no entry or throws after bind', async () => { - // Phase 1: no entry found after a successful bind -> failed access - const existingUser: any = { id: 7, login: 'ghost', isGuest: false, isActive: true } - usersManager.findUser.mockResolvedValue(existingUser) - setupLdapSuccess([]) - const resA = await authMethodLdapService.validateUser('ghost', 'pwd', '10.10.0.1') - expect(resA).toBeNull() - expect(usersManager.updateAccesses).toHaveBeenCalledWith(existingUser, '10.10.0.1', false) - - // Phase 2: exception during search after a bind -> failed access - jest.clearAllMocks() - const existingUser2: any = { id: 10, login: 'john', isGuest: false, isActive: true } - usersManager.findUser.mockResolvedValue(existingUser2) - mockBindResolve(ldapClient) - mockSearchReject(ldapClient, new Error('search failed')) - const resB = await authMethodLdapService.validateUser('john', 'pwd', '1.1.1.1') - expect(resB).toBeNull() - expect(usersManager.updateAccesses).toHaveBeenCalledWith(existingUser2, '1.1.1.1', false) - }) - - it('should allow app password when LDAP fails and scope is provided', async () => { - const existingUser: any = buildUser({ id: 42 }) - usersManager.findUser.mockResolvedValue(existingUser) - // LDAP invalid credentials - mockBindRejectInvalid(ldapClient, InvalidCredentialsError, 'invalid credentials') - // App password success - usersManager.validateAppPassword.mockResolvedValue(true) - const res = await authMethodLdapService.validateUser('john', 'app-password', '10.0.0.2', 'webdav' as any) - expect(res).toBe(existingUser) - expect(usersManager.validateAppPassword).toHaveBeenCalledWith(existingUser, 'app-password', '10.0.0.2', 'webdav') - expect(usersManager.updateAccesses).toHaveBeenCalledWith(existingUser, '10.0.0.2', true) - }) - - it('should throw 500 when LDAP connection error occurs during bind', async () => { - // Arrange: no existing user to reach checkAuth flow - usersManager.findUser.mockResolvedValue(null) - const err1 = new Error('socket hang up') - const err2 = Object.assign(new Error('connect ECONNREFUSED'), { code: Array.from(CONNECT_ERROR_CODE)[0] }) - ldapClient.bind.mockRejectedValue({ errors: [err1, err2] }) - ldapClient.unbind.mockResolvedValue(undefined) - - // First scenario: recognized connection error -> throws 500 - await expect(authMethodLdapService.validateUser('john', 'pwd')).rejects.toThrow(/authentication service/i) - - // Second scenario: generic error (no code, not InvalidCredentialsError) -> resolves to null and no access update - ldapClient.bind.mockReset() - ldapClient.unbind.mockReset() - usersManager.updateAccesses.mockClear() - usersManager.findUser.mockResolvedValue(null as any) - ldapClient.bind.mockRejectedValue(new Error('unexpected failure')) - ldapClient.unbind.mockResolvedValue(undefined) - - const res = await authMethodLdapService.validateUser('john', 'pwd') - expect(res).toBeNull() - expect(usersManager.updateAccesses).not.toHaveBeenCalled() - }) - - it('should log update failure when updating existing user', async () => { - // Arrange: existing user with changed identity - const existingUser: any = buildUser({ id: 11, email: 'old@ex.org' }) - usersManager.findUser.mockResolvedValue(existingUser) - // Ensure LDAP loginAttribute matches uid for this test (a previous test sets it to 'cn') - setupLdapSuccess([{ uid: 'john', cn: 'John Doe', mail: 'john@example.org' }]) - adminUsersManager.updateUserOrGuest.mockRejectedValue(new Error('db error')) - // Force identity to be considered changed only for this test - jest.spyOn(commonFunctions, 'comparePassword').mockResolvedValue(false) - jest.spyOn(commonFunctions, 'splitFullName').mockReturnValue({ firstName: 'John', lastName: 'Doe' }) - const res = await authMethodLdapService.validateUser('john', 'pwd') - expect(adminUsersManager.updateUserOrGuest).toHaveBeenCalled() - // Local fields unchanged since update failed - expect(existingUser.email).toBe('old@ex.org') - expect(res).toBe(existingUser) - }) - - it('should skip non-matching LDAP entries then update user with changed password without reassigning it', async () => { - // Phase A: LDAP returns an entry but loginAttribute value does not match -> checkAccess returns false (covers return after loop) - const userA: any = { id: 20, login: 'john', isGuest: false, isActive: true } - usersManager.findUser.mockResolvedValue(userA) - ldapClient.bind.mockResolvedValue(undefined) - - // Phase B: Matching entry + password considered changed -> updateUserOrGuest called, password not reassigned locally - jest.clearAllMocks() - const userB: any = buildUser({ id: 21, email: 'old@ex.org' }) - usersManager.findUser.mockResolvedValue(userB) - setupLdapSuccess([{ uid: 'john', cn: 'John Doe', mail: 'john@example.org' }]) - adminUsersManager.updateUserOrGuest.mockResolvedValue(undefined) - - // Force password to be considered changed to execute deletion + Object.assign branch - jest.spyOn(commonFunctions, 'comparePassword').mockResolvedValue(false) - jest.spyOn(commonFunctions, 'splitFullName').mockReturnValue({ firstName: 'John', lastName: 'Doe' }) - const resB = await authMethodLdapService.validateUser('john', 'newpwd', '4.4.4.4') - - // Line 132: updateUserOrGuest call - expect(adminUsersManager.updateUserOrGuest).toHaveBeenCalledWith( - 21, - expect.objectContaining({ email: 'john@example.org', firstName: 'John', lastName: 'Doe' }) - ) - - // Lines 139-142: password removed from local assign, other fields assigned - expect(userB.password).toBe('hashed') - expect(userB.email).toBe('john@example.org') - expect(userB).toMatchObject({ firstName: 'John', lastName: 'Doe' }) - expect(resB).toBe(userB) - }) -}) diff --git a/backend/src/authentication/utils/crypt-secret.ts b/backend/src/authentication/utils/crypt-secret.ts index 81479c2f..ab1ea666 100644 --- a/backend/src/authentication/utils/crypt-secret.ts +++ b/backend/src/authentication/utils/crypt-secret.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import crypto from 'crypto' // Encrypt a plaintext string with a passphrase. Returns a compact string. diff --git a/backend/src/common/constants.ts b/backend/src/common/constants.ts index 8e5518cc..4b843e99 100644 --- a/backend/src/common/constants.ts +++ b/backend/src/common/constants.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - export enum ACTION { ADD = 'add', UPDATE = 'update', diff --git a/backend/src/common/decorators.ts b/backend/src/common/decorators.ts index 46aaaf33..235fa7bc 100644 --- a/backend/src/common/decorators.ts +++ b/backend/src/common/decorators.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { registerDecorator, ValidationArguments, ValidationOptions } from 'class-validator' export function RejectIfMatch(regex: RegExp, validationOptions?: ValidationOptions) { diff --git a/backend/src/common/functions.ts b/backend/src/common/functions.ts index 7058aaba..12900fce 100755 --- a/backend/src/common/functions.ts +++ b/backend/src/common/functions.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { parse as parseMs } from '@lukeed/ms' import bcrypt from 'bcryptjs' import { ClassTransformOptions, plainToInstance } from 'class-transformer' diff --git a/backend/src/common/i18n.ts b/backend/src/common/i18n.ts index 3d0144cd..d1ee16ce 100644 --- a/backend/src/common/i18n.ts +++ b/backend/src/common/i18n.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - export const LANG_DEFAULT = 'en' as const export type i18nLocaleSupported = 'de' | 'en' | 'es' | 'fr' | 'hi' | 'it' | 'ja' | 'ko' | 'pl' | 'pt' | 'pt-BR' | 'ru' | 'tr' | 'zh' export const LANG_SUPPORTED = new Set(['de', 'en', 'es', 'fr', 'hi', 'it', 'ja', 'ko', 'pl', 'pt', 'pt-BR', 'ru', 'tr', 'zh']) diff --git a/backend/src/common/image.ts b/backend/src/common/image.ts index 850784b5..0a4adf7f 100644 --- a/backend/src/common/image.ts +++ b/backend/src/common/image.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import fs from 'node:fs/promises' import os from 'node:os' import path from 'node:path' diff --git a/backend/src/common/interfaces.ts b/backend/src/common/interfaces.ts index 4cd45594..411291a2 100644 --- a/backend/src/common/interfaces.ts +++ b/backend/src/common/interfaces.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - export type Entries = { [K in keyof T]: [K, T[K]] }[keyof T][] export interface StorageQuota { diff --git a/backend/src/common/qrcode.ts b/backend/src/common/qrcode.ts index c8828fb5..501222cb 100644 --- a/backend/src/common/qrcode.ts +++ b/backend/src/common/qrcode.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import qrcode from 'qrcode-generator' export function qrcodeToDataURL(text: string) { diff --git a/backend/src/common/shared.ts b/backend/src/common/shared.ts index 11cbed8b..806bbf89 100644 --- a/backend/src/common/shared.ts +++ b/backend/src/common/shared.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - /* THIS FILE IS SHARED WITH THE FRONTEND PACKAGE */ import { SPACE_PERMS_SEP } from '../applications/spaces/constants/spaces' diff --git a/backend/src/configuration/config.constants.ts b/backend/src/configuration/config.constants.ts index 4bbe9ad4..b5b9705b 100644 --- a/backend/src/configuration/config.constants.ts +++ b/backend/src/configuration/config.constants.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import path from 'node:path' import process from 'node:process' diff --git a/backend/src/configuration/config.environment.ts b/backend/src/configuration/config.environment.ts index 4c9a535e..143a96f3 100755 --- a/backend/src/configuration/config.environment.ts +++ b/backend/src/configuration/config.environment.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { join } from 'node:path' import { AuthTokenAccessConfig, AuthTokenRefreshConfig } from '../authentication/auth.config' import { ACCESS_KEY, CSRF_KEY, TWO_FA_VERIFY_EXPIRATION, WS_KEY } from '../authentication/constants/auth' diff --git a/backend/src/configuration/config.interfaces.ts b/backend/src/configuration/config.interfaces.ts index b8fe4326..a2f778a0 100644 --- a/backend/src/configuration/config.interfaces.ts +++ b/backend/src/configuration/config.interfaces.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - export interface FileEditorProvider { collabora: boolean onlyoffice: boolean diff --git a/backend/src/configuration/config.loader.ts b/backend/src/configuration/config.loader.ts index e46ba535..a8b8aad5 100644 --- a/backend/src/configuration/config.loader.ts +++ b/backend/src/configuration/config.loader.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import deepmerge from 'deepmerge' import * as yaml from 'js-yaml' import fs from 'node:fs' diff --git a/backend/src/configuration/config.logger.ts b/backend/src/configuration/config.logger.ts index 0ece6aeb..bad055f4 100644 --- a/backend/src/configuration/config.logger.ts +++ b/backend/src/configuration/config.logger.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import type { Options } from 'pino-http' import type { LoggerConfig } from './config.validation' diff --git a/backend/src/configuration/config.validation.ts b/backend/src/configuration/config.validation.ts index c137bf1b..0f477f01 100644 --- a/backend/src/configuration/config.validation.ts +++ b/backend/src/configuration/config.validation.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Transform, Type } from 'class-transformer' import { IsBoolean, diff --git a/backend/src/infrastructure/cache/adapters/mysql-cache.adapter.ts b/backend/src/infrastructure/cache/adapters/mysql-cache.adapter.ts index dc1e5adc..66788e8a 100644 --- a/backend/src/infrastructure/cache/adapters/mysql-cache.adapter.ts +++ b/backend/src/infrastructure/cache/adapters/mysql-cache.adapter.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Inject, Injectable, Logger } from '@nestjs/common' import { SchedulerRegistry } from '@nestjs/schedule' import { CronJob } from 'cron' diff --git a/backend/src/infrastructure/cache/adapters/redis-cache.adapter.ts b/backend/src/infrastructure/cache/adapters/redis-cache.adapter.ts index 8c71d43b..3a3b39fa 100644 --- a/backend/src/infrastructure/cache/adapters/redis-cache.adapter.ts +++ b/backend/src/infrastructure/cache/adapters/redis-cache.adapter.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Injectable, Logger } from '@nestjs/common' import { RedisClientOptions } from '@redis/client' import { createClient, RedisClientType } from 'redis' diff --git a/backend/src/infrastructure/cache/cache.config.ts b/backend/src/infrastructure/cache/cache.config.ts index 60f18b8c..f6c45653 100644 --- a/backend/src/infrastructure/cache/cache.config.ts +++ b/backend/src/infrastructure/cache/cache.config.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { IsIn, IsInt, IsNotEmpty, IsString, Min, ValidateIf } from 'class-validator' export class CacheConfig { diff --git a/backend/src/infrastructure/cache/cache.decorator.ts b/backend/src/infrastructure/cache/cache.decorator.ts index 7f8c448f..0204d5bf 100644 --- a/backend/src/infrastructure/cache/cache.decorator.ts +++ b/backend/src/infrastructure/cache/cache.decorator.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Inject } from '@nestjs/common' import { Cache } from './services/cache.service' diff --git a/backend/src/infrastructure/cache/cache.e2e-spec.ts b/backend/src/infrastructure/cache/cache.e2e-spec.ts index e713d3ad..ed97333c 100644 --- a/backend/src/infrastructure/cache/cache.e2e-spec.ts +++ b/backend/src/infrastructure/cache/cache.e2e-spec.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Test, TestingModule } from '@nestjs/testing' import { LoggerModule } from 'nestjs-pino' import { setTimeout } from 'node:timers/promises' diff --git a/backend/src/infrastructure/cache/cache.module.ts b/backend/src/infrastructure/cache/cache.module.ts index aa68e739..293a2067 100644 --- a/backend/src/infrastructure/cache/cache.module.ts +++ b/backend/src/infrastructure/cache/cache.module.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Global, Module } from '@nestjs/common' import { SchedulerRegistry } from '@nestjs/schedule' import { configuration } from '../../configuration/config.environment' diff --git a/backend/src/infrastructure/cache/schemas/mysql-cache.interface.ts b/backend/src/infrastructure/cache/schemas/mysql-cache.interface.ts index 3ceab2da..c21a698a 100644 --- a/backend/src/infrastructure/cache/schemas/mysql-cache.interface.ts +++ b/backend/src/infrastructure/cache/schemas/mysql-cache.interface.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { cache } from './mysql-cache.schema' type MysqlCacheSchema = typeof cache.$inferSelect diff --git a/backend/src/infrastructure/cache/schemas/mysql-cache.schema.ts b/backend/src/infrastructure/cache/schemas/mysql-cache.schema.ts index 538914a0..4d4d3178 100644 --- a/backend/src/infrastructure/cache/schemas/mysql-cache.schema.ts +++ b/backend/src/infrastructure/cache/schemas/mysql-cache.schema.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { index, int, mysqlTable, varchar } from 'drizzle-orm/mysql-core' import { jsonColumn } from '../../database/columns' diff --git a/backend/src/infrastructure/cache/services/cache.service.ts b/backend/src/infrastructure/cache/services/cache.service.ts index 53116fc8..747fb90b 100644 --- a/backend/src/infrastructure/cache/services/cache.service.ts +++ b/backend/src/infrastructure/cache/services/cache.service.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { OnModuleDestroy, OnModuleInit } from '@nestjs/common' export abstract class Cache implements OnModuleInit, OnModuleDestroy { diff --git a/backend/src/infrastructure/context/context.module.ts b/backend/src/infrastructure/context/context.module.ts index 4ec4a1fe..71039462 100644 --- a/backend/src/infrastructure/context/context.module.ts +++ b/backend/src/infrastructure/context/context.module.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Global, Module } from '@nestjs/common' import { ContextInterceptor } from './interceptors/context.interceptor' import { ContextManager } from './services/context-manager.service' diff --git a/backend/src/infrastructure/context/interceptors/context.interceptor.spec.ts b/backend/src/infrastructure/context/interceptors/context.interceptor.spec.ts index c3b4b065..7d65d324 100644 --- a/backend/src/infrastructure/context/interceptors/context.interceptor.spec.ts +++ b/backend/src/infrastructure/context/interceptors/context.interceptor.spec.ts @@ -1,14 +1,8 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - -import { Test, TestingModule } from '@nestjs/testing' import { CallHandler, ExecutionContext } from '@nestjs/common' +import { Test, TestingModule } from '@nestjs/testing' import { of } from 'rxjs' -import { ContextInterceptor } from './context.interceptor' import { ContextManager } from '../services/context-manager.service' +import { ContextInterceptor } from './context.interceptor' // Helper to create a minimal ExecutionContext with Fastify-like request function createHttpExecutionContext(request: any): ExecutionContext { diff --git a/backend/src/infrastructure/context/interceptors/context.interceptor.ts b/backend/src/infrastructure/context/interceptors/context.interceptor.ts index 0ae3a1cc..cbf351a1 100644 --- a/backend/src/infrastructure/context/interceptors/context.interceptor.ts +++ b/backend/src/infrastructure/context/interceptors/context.interceptor.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common' import { FastifyRequest } from 'fastify' import type { Observable } from 'rxjs' diff --git a/backend/src/infrastructure/context/interfaces/context-store.interface.ts b/backend/src/infrastructure/context/interfaces/context-store.interface.ts index 444d0adc..e494843d 100644 --- a/backend/src/infrastructure/context/interfaces/context-store.interface.ts +++ b/backend/src/infrastructure/context/interfaces/context-store.interface.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - export interface ContextStore { headerOriginUrl: string } diff --git a/backend/src/infrastructure/context/services/context-manager.service.spec.ts b/backend/src/infrastructure/context/services/context-manager.service.spec.ts index 40642067..400931dd 100644 --- a/backend/src/infrastructure/context/services/context-manager.service.spec.ts +++ b/backend/src/infrastructure/context/services/context-manager.service.spec.ts @@ -1,12 +1,6 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Test, TestingModule } from '@nestjs/testing' -import { ContextManager } from './context-manager.service' import type { ContextStore } from '../interfaces/context-store.interface' +import { ContextManager } from './context-manager.service' describe(ContextManager.name, () => { let contextManager: ContextManager diff --git a/backend/src/infrastructure/context/services/context-manager.service.ts b/backend/src/infrastructure/context/services/context-manager.service.ts index d7098833..c552a7dc 100644 --- a/backend/src/infrastructure/context/services/context-manager.service.ts +++ b/backend/src/infrastructure/context/services/context-manager.service.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Injectable } from '@nestjs/common' import { AsyncLocalStorage } from 'node:async_hooks' import type { Observable } from 'rxjs' diff --git a/backend/src/infrastructure/database/columns.ts b/backend/src/infrastructure/database/columns.ts index 24142a2b..8be76259 100644 --- a/backend/src/infrastructure/database/columns.ts +++ b/backend/src/infrastructure/database/columns.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { customType } from 'drizzle-orm/mysql-core' export const jsonColumn = () => diff --git a/backend/src/infrastructure/database/configuration.ts b/backend/src/infrastructure/database/configuration.ts index 4e7c5683..f26f0a51 100644 --- a/backend/src/infrastructure/database/configuration.ts +++ b/backend/src/infrastructure/database/configuration.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Config, defineConfig } from 'drizzle-kit' import { configLoader } from '../../configuration/config.loader' import { getSchemaPath, MIGRATIONS_PATH } from './constants' diff --git a/backend/src/infrastructure/database/constants.ts b/backend/src/infrastructure/database/constants.ts index 00c95c83..3dae7400 100644 --- a/backend/src/infrastructure/database/constants.ts +++ b/backend/src/infrastructure/database/constants.ts @@ -1,11 +1,5 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - -import path from 'node:path' import fs from 'node:fs' +import path from 'node:path' export const DB_CHARSET = 'utf8mb4' export const DB_TOKEN_PROVIDER = 'DB' diff --git a/backend/src/infrastructure/database/database.config.ts b/backend/src/infrastructure/database/database.config.ts index 81d61b83..753d3aeb 100644 --- a/backend/src/infrastructure/database/database.config.ts +++ b/backend/src/infrastructure/database/database.config.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Transform } from 'class-transformer' import { IsBoolean, IsNotEmpty, IsString } from 'class-validator' import { DB_CHARSET } from './constants' diff --git a/backend/src/infrastructure/database/database.logger.ts b/backend/src/infrastructure/database/database.logger.ts index 443f5768..390ea94c 100644 --- a/backend/src/infrastructure/database/database.logger.ts +++ b/backend/src/infrastructure/database/database.logger.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Logger } from '@nestjs/common' import { Logger as DrizzleLogger } from 'drizzle-orm/logger.js' diff --git a/backend/src/infrastructure/database/database.module.ts b/backend/src/infrastructure/database/database.module.ts index 1217e7f4..e48ea11f 100644 --- a/backend/src/infrastructure/database/database.module.ts +++ b/backend/src/infrastructure/database/database.module.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { DrizzleMySqlConfig, DrizzleMySqlModule } from '@knaadh/nestjs-drizzle-mysql2' import { BeforeApplicationShutdown, Global, Inject, Module } from '@nestjs/common' import { MySql2Client } from 'drizzle-orm/mysql2' diff --git a/backend/src/infrastructure/database/interfaces/database.interface.ts b/backend/src/infrastructure/database/interfaces/database.interface.ts index 92c87a65..c82619ed 100644 --- a/backend/src/infrastructure/database/interfaces/database.interface.ts +++ b/backend/src/infrastructure/database/interfaces/database.interface.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { MySql2Database } from 'drizzle-orm/mysql2' import * as schema from '../schema' diff --git a/backend/src/infrastructure/database/schema.ts b/backend/src/infrastructure/database/schema.ts index dbbdab7d..95cc1599 100644 --- a/backend/src/infrastructure/database/schema.ts +++ b/backend/src/infrastructure/database/schema.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - export * from '../cache/schemas/mysql-cache.schema' export * from '../../applications/users/schemas/users.schema' export * from '../../applications/users/schemas/groups.schema' diff --git a/backend/src/infrastructure/database/scripts/create-user.ts b/backend/src/infrastructure/database/scripts/create-user.ts index 827de917..dc6afbcc 100644 --- a/backend/src/infrastructure/database/scripts/create-user.ts +++ b/backend/src/infrastructure/database/scripts/create-user.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { count, eq, or } from 'drizzle-orm' import { USER_PERMISSION, USER_ROLE } from '../../../applications/users/constants/user' import { User } from '../../../applications/users/schemas/user.interface' diff --git a/backend/src/infrastructure/database/scripts/db.ts b/backend/src/infrastructure/database/scripts/db.ts index 413df79c..9d75c741 100644 --- a/backend/src/infrastructure/database/scripts/db.ts +++ b/backend/src/infrastructure/database/scripts/db.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { drizzle } from 'drizzle-orm/mysql2' import { configLoader } from '../../../configuration/config.loader' import * as schema from '../schema' diff --git a/backend/src/infrastructure/database/scripts/seed/main.ts b/backend/src/infrastructure/database/scripts/seed/main.ts index b98a5520..36aca441 100644 --- a/backend/src/infrastructure/database/scripts/seed/main.ts +++ b/backend/src/infrastructure/database/scripts/seed/main.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { usersAndGroups } from './usersgroups' async function main() { diff --git a/backend/src/infrastructure/database/scripts/seed/usersgroups.ts b/backend/src/infrastructure/database/scripts/seed/usersgroups.ts index 552c4289..a1b1246a 100644 --- a/backend/src/infrastructure/database/scripts/seed/usersgroups.ts +++ b/backend/src/infrastructure/database/scripts/seed/usersgroups.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { faker } from '@faker-js/faker' import { ResultSetHeader } from 'mysql2/promise' import { USER_PERMISSION, USER_ROLE } from '../../../../applications/users/constants/user' diff --git a/backend/src/infrastructure/database/utils.ts b/backend/src/infrastructure/database/utils.ts index ff20ad15..8b7400e2 100644 --- a/backend/src/infrastructure/database/utils.ts +++ b/backend/src/infrastructure/database/utils.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { NestFastifyApplication } from '@nestjs/platform-fastify' import { TestingModule } from '@nestjs/testing' import { Column, eq, inArray, isNull, SQL, sql } from 'drizzle-orm' diff --git a/backend/src/infrastructure/mailer/interfaces/mail.interface.ts b/backend/src/infrastructure/mailer/interfaces/mail.interface.ts index b716c669..3ab6e066 100644 --- a/backend/src/infrastructure/mailer/interfaces/mail.interface.ts +++ b/backend/src/infrastructure/mailer/interfaces/mail.interface.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - export interface MailTransport { host: string port: number diff --git a/backend/src/infrastructure/mailer/mailer.config.ts b/backend/src/infrastructure/mailer/mailer.config.ts index 13a06454..0c72c902 100644 --- a/backend/src/infrastructure/mailer/mailer.config.ts +++ b/backend/src/infrastructure/mailer/mailer.config.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Type } from 'class-transformer' import { IsBoolean, IsInt, IsNotEmpty, IsObject, IsOptional, IsString, Max, Min, ValidateNested } from 'class-validator' diff --git a/backend/src/infrastructure/mailer/mailer.module.ts b/backend/src/infrastructure/mailer/mailer.module.ts index 30a2670e..61d1c94b 100644 --- a/backend/src/infrastructure/mailer/mailer.module.ts +++ b/backend/src/infrastructure/mailer/mailer.module.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Global, Module } from '@nestjs/common' import { Mailer } from './mailer.service' diff --git a/backend/src/infrastructure/mailer/mailer.service.spec.ts b/backend/src/infrastructure/mailer/mailer.service.spec.ts index 42a4d1b1..2897d854 100644 --- a/backend/src/infrastructure/mailer/mailer.service.spec.ts +++ b/backend/src/infrastructure/mailer/mailer.service.spec.ts @@ -1,15 +1,9 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - -import { Test, TestingModule } from '@nestjs/testing' import { ConfigService } from '@nestjs/config' +import { Test, TestingModule } from '@nestjs/testing' import { PinoLogger } from 'nestjs-pino' import nodemailer from 'nodemailer' -import { Mailer } from './mailer.service' import { MailerConfig } from './mailer.config' +import { Mailer } from './mailer.service' // Mocks jest.mock('nodemailer') diff --git a/backend/src/infrastructure/mailer/mailer.service.ts b/backend/src/infrastructure/mailer/mailer.service.ts index 53d1ebaa..6db06e97 100644 --- a/backend/src/infrastructure/mailer/mailer.service.ts +++ b/backend/src/infrastructure/mailer/mailer.service.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Injectable } from '@nestjs/common' import { ConfigService } from '@nestjs/config' import { PinoLogger } from 'nestjs-pino' diff --git a/backend/src/infrastructure/scheduler/scheduler.constants.ts b/backend/src/infrastructure/scheduler/scheduler.constants.ts index 035e3141..ce63a698 100644 --- a/backend/src/infrastructure/scheduler/scheduler.constants.ts +++ b/backend/src/infrastructure/scheduler/scheduler.constants.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - export const SCHEDULER_ENV = 'SCHEDULER' export enum SCHEDULER_STATE { diff --git a/backend/src/infrastructure/scheduler/scheduler.module.ts b/backend/src/infrastructure/scheduler/scheduler.module.ts index 8978fd8a..0081f060 100644 --- a/backend/src/infrastructure/scheduler/scheduler.module.ts +++ b/backend/src/infrastructure/scheduler/scheduler.module.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { DynamicModule, Module } from '@nestjs/common' import { ScheduleModule as NestScheduleModule } from '@nestjs/schedule' import cluster from 'node:cluster' diff --git a/backend/src/infrastructure/websocket/adapters/cluster.adapter.ts b/backend/src/infrastructure/websocket/adapters/cluster.adapter.ts index d450b15c..f55e236c 100644 --- a/backend/src/infrastructure/websocket/adapters/cluster.adapter.ts +++ b/backend/src/infrastructure/websocket/adapters/cluster.adapter.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Injectable } from '@nestjs/common' import { IoAdapter } from '@nestjs/platform-socket.io' import { createAdapter } from '@socket.io/cluster-adapter' diff --git a/backend/src/infrastructure/websocket/adapters/redis.adapter.ts b/backend/src/infrastructure/websocket/adapters/redis.adapter.ts index 0cd0ce1a..2bd46578 100644 --- a/backend/src/infrastructure/websocket/adapters/redis.adapter.ts +++ b/backend/src/infrastructure/websocket/adapters/redis.adapter.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Logger } from '@nestjs/common' import { IoAdapter } from '@nestjs/platform-socket.io' import { ServerOptions } from 'socket.io' diff --git a/backend/src/infrastructure/websocket/adapters/web-socket.adapter.ts b/backend/src/infrastructure/websocket/adapters/web-socket.adapter.ts index 9e5a0f02..5c67289e 100644 --- a/backend/src/infrastructure/websocket/adapters/web-socket.adapter.ts +++ b/backend/src/infrastructure/websocket/adapters/web-socket.adapter.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Injectable, Logger } from '@nestjs/common' import { JwtService } from '@nestjs/jwt' import { NestFastifyApplication } from '@nestjs/platform-fastify' diff --git a/backend/src/infrastructure/websocket/decorators/web-socket-user.decorator.ts b/backend/src/infrastructure/websocket/decorators/web-socket-user.decorator.ts index 3fad55ec..cd496bed 100644 --- a/backend/src/infrastructure/websocket/decorators/web-socket-user.decorator.ts +++ b/backend/src/infrastructure/websocket/decorators/web-socket-user.decorator.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { createParamDecorator, ExecutionContext } from '@nestjs/common' import type { JwtIdentityPayload } from '../../../authentication/interfaces/jwt-payload.interface' diff --git a/backend/src/infrastructure/websocket/interfaces/auth-socket-io.interface.ts b/backend/src/infrastructure/websocket/interfaces/auth-socket-io.interface.ts index fbe28abf..ca8556f5 100644 --- a/backend/src/infrastructure/websocket/interfaces/auth-socket-io.interface.ts +++ b/backend/src/infrastructure/websocket/interfaces/auth-socket-io.interface.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Socket } from 'socket.io' import { JwtIdentityPayload } from '../../../authentication/interfaces/jwt-payload.interface' diff --git a/backend/src/infrastructure/websocket/utils.ts b/backend/src/infrastructure/websocket/utils.ts index ac581362..57df1ff0 100644 --- a/backend/src/infrastructure/websocket/utils.ts +++ b/backend/src/infrastructure/websocket/utils.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Socket } from 'socket.io' import { configuration } from '../../configuration/config.environment' diff --git a/backend/src/infrastructure/websocket/web-socket.config.ts b/backend/src/infrastructure/websocket/web-socket.config.ts index 580089c8..091b45b8 100644 --- a/backend/src/infrastructure/websocket/web-socket.config.ts +++ b/backend/src/infrastructure/websocket/web-socket.config.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { IsIn, IsNotEmpty, IsString, ValidateIf } from 'class-validator' export class WebSocketConfig { diff --git a/backend/src/main.ts b/backend/src/main.ts index 2671202a..66443472 100755 --- a/backend/src/main.ts +++ b/backend/src/main.ts @@ -1,10 +1,4 @@ #!/usr/bin/env node -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { NestFastifyApplication } from '@nestjs/platform-fastify' import { Logger } from 'nestjs-pino' import { appBootstrap } from './app.bootstrap' @@ -20,6 +14,11 @@ async function bootstrap() { port: configuration.server.port }, (error, address) => { + if (configuration.server.host === '0.0.0.0') { + const url = new URL(address) + url.hostname = '0.0.0.0' + address = url.toString() + } if (error) { logger.error(`Server listening error at ${address} : ${error}`, 'HTTP') process.exit(1) diff --git a/docker/config/sync-in-desktop-releases/docker-compose.sync-in-desktop-releases.yaml b/docker/config/sync-in-desktop-releases/docker-compose.sync-in-desktop-releases.yaml index a252f439..97c2cb1e 100644 --- a/docker/config/sync-in-desktop-releases/docker-compose.sync-in-desktop-releases.yaml +++ b/docker/config/sync-in-desktop-releases/docker-compose.sync-in-desktop-releases.yaml @@ -1,7 +1,7 @@ services: sync_in_desktop_releases: profiles: [ "releases" ] - image: syncin/desktop-releases:1 + image: syncin/desktop-releases:2 container_name: sync-in-desktop-releases user: "${PUID:-8888}:${PGID:-8888}" volumes: diff --git a/docker/docker-compose.yaml b/docker/docker-compose.yaml index 42c2bd33..f8d9eda3 100644 --- a/docker/docker-compose.yaml +++ b/docker/docker-compose.yaml @@ -7,7 +7,7 @@ name: sync-in services: sync_in: - image: syncin/server:1 + image: syncin/server:2 container_name: sync-in restart: always environment: diff --git a/environment/environment.dist.min.yaml b/environment/environment.dist.min.yaml index d106553f..e12332b4 100644 --- a/environment/environment.dist.min.yaml +++ b/environment/environment.dist.min.yaml @@ -1,5 +1,5 @@ mysql: - url: mysql://user:MySQLRootPassword@localhost:3306/database + url: mysql://user:MySQLPassword@localhost:3306/database auth: encryptionKey: changeEncryptionKeyWithStrongKey token: diff --git a/environment/environment.dist.yaml b/environment/environment.dist.yaml index 7cf20140..d0a5a01b 100755 --- a/environment/environment.dist.yaml +++ b/environment/environment.dist.yaml @@ -26,7 +26,7 @@ logger: filePath: mysql: # required - url: mysql://user:MySQLRootPassword@localhost:3306/database + url: mysql://user:MySQLPassword@localhost:3306/database # default: `false` logQueries: false cache: @@ -77,23 +77,13 @@ mail: # default: `false` debug: false auth: - # adapter : `mysql` | `ldap` + # provider : `mysql` | `ldap` | `oidc` # default: `mysql` - method: mysql + provider: mysql # Key used to encrypt user secret keys in the database # Optional but strongly recommended # Warning: do not change or remove the encryption key after MFA activation, or the codes will become invalid encryptionKey: changeEncryptionKeyWithStrongKey - # Multifactor authentication - mfa: - # TOTP configuration - totp: - # Enable TOTP authentication - # default: true - enabled: true - # Name displayed in the authentication app (FreeOTP, Proton Authenticator, Aegis Authenticator etc.) - # default: Sync-in - issuer: Sync-in # cookie sameSite setting: `lax` | `strict` # default: `strict` cookieSameSite: strict @@ -112,13 +102,33 @@ auth: # token expiration = cookie maxAge # default: `4h` expiration: 4h + # Multifactor authentication + mfa: + # TOTP configuration + totp: + # Enable TOTP authentication + # default: true + enabled: true + # Name displayed in the authentication app (FreeOTP, Proton Authenticator, Aegis Authenticator etc.) + # default: Sync-in + issuer: Sync-in + # LDAP authentication ldap: # e.g: [ldap://localhost:389, ldaps://localhost:636] (array required) + # required servers: [] # baseDN: distinguished name (e.g., ou=people,dc=ldap,dc=sync-in,dc=com) - baseDN: + # required + baseDN: ou=people,dc=ldap,dc=sync-in,dc=com # filter, e.g: (acl=admin) + # optional filter: + # upnSuffix: AD domain suffix used with `userPrincipalName` to build UPN-style logins (e.g., user@`sync-in.com`) + # optional + upnSuffix: + # netbiosName: NetBIOS domain name used with `sAMAccountName` to build legacy logins (e.g., `SYNC_IN`\user) + # optional + netbiosName: attributes: # Login attribute used to construct the user's DN for binding. # The value of this attribute is used as the naming attribute (first RDN) when forming the Distinguished Name (DN) during authentication @@ -129,12 +139,126 @@ auth: # email: `mail` or `email` # default: `mail` email: mail - # adminGroup: The CN of a group containing Sync-in administrators (e.g., administrators) - adminGroup: - # upnSuffix: AD domain suffix used with `userPrincipalName` to build UPN-style logins (e.g., user@`sync-in.com`) - upnSuffix: - # netbiosName: NetBIOS domain name used with `sAMAccountName` to build legacy logins (e.g., `SYNC_IN`\user) - netbiosName: + options: + # autoCreateUser: Automatically create a local user on first successful LDAP authentication. + # The local account is created from LDAP attributes: + # - login: from the configured LDAP login attribute (e.g. uid, cn, sAMAccountName, userPrincipalName) + # - email: from the configured email attribute (required) + # - firstName / lastName: from givenName+sn, or displayName, or cn (fallback) + # When disabled, only existing users can authenticate via LDAP. + # default: true + autoCreateUser: true + # autoCreatePermissions: Permissions assigned to users automatically created via LDAP. + # Applied only at user creation time when autoCreateUser is enabled. + # Has no effect on existing users. + # A complete list of permissions is available in the documentation: https://sync-in.com/docs/admin-guide/permissions + # e.g.: [personal_space, spaces_access] (array required) + # default: [] + autoCreatePermissions: [] + # adminGroup: Name of the LDAP group (CN) that grants Sync-in administrator privileges. + # If set, users whose LDAP `memberOf` contains this group name are assigned the administrator role. + # If not set, existing administrator users keep their role and it cannot be removed via LDAP. + # optional + adminGroup: + # enablePasswordAuthFallback: Allow local password authentication when LDAP authentication fails. + # When enabled, users can authenticate with their local password if the LDAP service is unavailable. + # Always allowed for administrator users (break-glass access). + # default: true + enablePasswordAuthFallback: true + oidc: + # issuerUrl: The URL of the OIDC provider's discovery endpoint + # Examples: + # - Keycloak: https://auth.example.com/realms/my-realm + # - Authentik: https://auth.example.com/application/o/my-app/ + # - Google: https://accounts.google.com + # - Microsoft: https://login.microsoftonline.com//v2.0 + # The server will automatically discover the authorization, token, and userinfo endpoints. + # required + issuerUrl: + # clientId: OAuth 2.0 Client ID obtained from your OIDC provider + # required + clientId: + # clientSecret: OAuth 2.0 Client Secret obtained from your OIDC provider + # required + clientSecret: changeOIDCClientSecret + # redirectUri: The callback URL where users are redirected after authentication + # This URL must be registered in your OIDC provider's allowed redirect URIs + # Example (API callback): https://sync-in.domain.com/api/auth/oidc/callback + # + # To allow authentication from the desktop application, the following redirect URLs must also be registered in your OIDC provider: + # - http://127.0.0.1:49152/oidc/callback + # - http://127.0.0.1:49153/oidc/callback + # - http://127.0.0.1:49154/oidc/callback + # + # If your OIDC provider supports wildcards or regular expressions, you may instead register a single entry such as: + # - http://127.0.0.1/* + # + # required + redirectUri: https://sync-in.domain.com/api/auth/oidc/callback + options: + # autoCreateUser: Automatically create a local user account on first successful OIDC login. + # When enabled, the user `login` is derived from OIDC claims: preferred_username, then the email local-part, with `sub` as a last-resort fallback. + # When disabled, only existing users are allowed to authenticate via OIDC. + # default: true + autoCreateUser: true + # autoCreatePermissions: Permissions assigned to users automatically created via OIDC. + # Applied only when autoCreateUser is enabled and only applied at user creation time. + # This option has no effect on existing users. + # A complete list of permissions is available in the documentation: https://sync-in.com/docs/admin-guide/permissions + # e.g.: [personal_space, spaces_access] (array required) + # default: [] + autoCreatePermissions: [] + # adminRoleOrGroup: Name of the role or group that grants Sync-in administrator access + # Users with this value will be granted administrator privileges. + # The value is matched against `roles` or `groups` claims provided by the IdP. + # Note: depending on the provider (e.g., Keycloak), roles/groups may be exposed only in tokens + # and require proper IdP mappers to be included in the ID token or UserInfo response. + # optional + adminRoleOrGroup: + # enablePasswordAuth: Allow local password-based authentication when using OIDC. + # When enabled, users may authenticate with their Sync-in password instead of OIDC. + # Local password authentication is always allowed for: + # - guest users + # - administrator users (break-glass access) + # - application scopes (app passwords) + # Regular users are allowed only when this option is enabled. + # Users must already exist locally and have a password set. + # default: true + enablePasswordAuth: true + # autoRedirect: Automatically redirect users to the OIDC login flow. + # When enabled, the login page is skipped and users are sent directly to the OIDC provider. + # default: false + autoRedirect: false + # buttonText: Label displayed on the OIDC login button. + # default: Continue with OpenID Connect + buttonText: Continue with OpenID Connect + security: + # scope: OAuth 2.0 scopes to request (space-separated string) + # Common scopes: openid (required), email, profile, groups, roles + # default: `openid email profile` + scope: openid email profile + # OAuth 2.0 / OIDC client authentication method used at the token endpoint. + # Possible values: + # - client_secret_basic (DEFAULT): HTTP Basic auth using client_id and client_secret. + # Recommended for backend (confidential) clients. + # - client_secret_post: client_id and client_secret sent in the request body. + # - none (or undefined): no client authentication (public clients: mobile / SPA with PKCE). + # default: `client_secret_basic` + tokenEndpointAuthMethod: client_secret_basic + # tokenSigningAlg: Algorithm used to verify the signature of ID tokens (JWT) returned by the OpenID Connect provider. + # Common values: RS256, RS384, RS512, ES256, ES384, ES512 + # default: `RS256` + tokenSigningAlg: RS256 + # userInfoSigningAlg: Algorithm used to request a signed UserInfo response from the OpenID Connect provider. + # When not set, the UserInfo endpoint returns a standard JSON response (not signed). This is the most common and recommended configuration. + # Common values: (empty), RS256, RS384, RS512, ES256, ES384, ES512 + # default: empty + userInfoSigningAlg: + # skipSubjectCheck: Disable verification that the `sub` claim returned by the UserInfo endpoint + # matches the `sub` claim from the ID token. + # Set to true only for non-compliant or legacy OIDC providers. + # default: false + skipSubjectCheck: false applications: files: # required diff --git a/frontend/eslint.config.js b/frontend/eslint.config.js index 813818ae..1cab7966 100644 --- a/frontend/eslint.config.js +++ b/frontend/eslint.config.js @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - // @ts-check const eslint = require('@eslint/js') const tseslint = require('typescript-eslint') diff --git a/frontend/scripts/build-assets.mjs b/frontend/scripts/build-assets.mjs index 6b8bd502..bbab8326 100644 --- a/frontend/scripts/build-assets.mjs +++ b/frontend/scripts/build-assets.mjs @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { checkPdfjs } from './pdfjs.mjs' if (process.env.NODE_ENV !== 'development') { diff --git a/frontend/scripts/pdfjs.mjs b/frontend/scripts/pdfjs.mjs index 8e5f6790..04e0f947 100644 --- a/frontend/scripts/pdfjs.mjs +++ b/frontend/scripts/pdfjs.mjs @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { fileURLToPath } from 'url' import fs from 'node:fs/promises' import path from 'node:path' diff --git a/frontend/src/app/app.component.ts b/frontend/src/app/app.component.ts index 9c3db6a6..563533c3 100644 --- a/frontend/src/app/app.component.ts +++ b/frontend/src/app/app.component.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Component } from '@angular/core' import { RouterOutlet } from '@angular/router' diff --git a/frontend/src/app/app.config.ts b/frontend/src/app/app.config.ts index c9fbac6a..0c5fc18c 100644 --- a/frontend/src/app/app.config.ts +++ b/frontend/src/app/app.config.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { HashLocationStrategy, LocationStrategy } from '@angular/common' import { HTTP_INTERCEPTORS, provideHttpClient, withInterceptorsFromDi, withXsrfConfiguration } from '@angular/common/http' import { ApplicationConfig, importProvidersFrom, isDevMode, provideZoneChangeDetection } from '@angular/core' diff --git a/frontend/src/app/app.constants.ts b/frontend/src/app/app.constants.ts index 992a16b3..2ca72dd4 100644 --- a/frontend/src/app/app.constants.ts +++ b/frontend/src/app/app.constants.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { faCircleHalfStroke } from '@fortawesome/free-solid-svg-icons' import { productName, version } from '../../../package.json' import { AppMenu } from './layout/layout.interfaces' diff --git a/frontend/src/app/app.routes.ts b/frontend/src/app/app.routes.ts index dbdebfd9..7a0ead30 100644 --- a/frontend/src/app/app.routes.ts +++ b/frontend/src/app/app.routes.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Routes } from '@angular/router' import { APP_PATH } from './app.constants' import { adminRoutes } from './applications/admin/admin.routes' @@ -23,7 +17,6 @@ export const routes: Routes = [ path: APP_PATH.BASE, component: LayoutComponent, canActivate: [authGuard], - canActivateChild: [authGuard], children: [...recentsRoutes, ...searchRoutes, ...spacesRoutes, ...userRoutes, ...syncRoutes, ...adminRoutes] }, ...authRoutes, diff --git a/frontend/src/app/applications/admin/admin.constants.ts b/frontend/src/app/applications/admin/admin.constants.ts index 60a99431..8cef5c1b 100644 --- a/frontend/src/app/applications/admin/admin.constants.ts +++ b/frontend/src/app/applications/admin/admin.constants.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { faGear, faUserGear, faUsersGear } from '@fortawesome/free-solid-svg-icons' import { AppMenu } from '../../layout/layout.interfaces' diff --git a/frontend/src/app/applications/admin/admin.guard.ts b/frontend/src/app/applications/admin/admin.guard.ts index bea81f6d..fec3f0e5 100644 --- a/frontend/src/app/applications/admin/admin.guard.ts +++ b/frontend/src/app/applications/admin/admin.guard.ts @@ -1,8 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ import { inject } from '@angular/core' import { CanActivateFn } from '@angular/router' import { UserService } from '../users/user.service' diff --git a/frontend/src/app/applications/admin/admin.routes.ts b/frontend/src/app/applications/admin/admin.routes.ts index 7f24cd3a..ecf12b14 100644 --- a/frontend/src/app/applications/admin/admin.routes.ts +++ b/frontend/src/app/applications/admin/admin.routes.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Routes } from '@angular/router' import { GROUP_TYPE } from '@sync-in-server/backend/src/applications/users/constants/group' import { USER_ROLE } from '@sync-in-server/backend/src/applications/users/constants/user' diff --git a/frontend/src/app/applications/admin/admin.service.ts b/frontend/src/app/applications/admin/admin.service.ts index 65074c8e..9c0aacaf 100644 --- a/frontend/src/app/applications/admin/admin.service.ts +++ b/frontend/src/app/applications/admin/admin.service.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { HttpClient, HttpHeaders } from '@angular/common/http' import { inject, Injectable } from '@angular/core' import { Router } from '@angular/router' diff --git a/frontend/src/app/applications/admin/components/admin-groups.component.html b/frontend/src/app/applications/admin/components/admin-groups.component.html index 113b8f8a..23418bf4 100644 --- a/frontend/src/app/applications/admin/components/admin-groups.component.html +++ b/frontend/src/app/applications/admin/components/admin-groups.component.html @@ -1,9 +1,3 @@ - -
+ @if (oidcSettings) { +
+
+ +
+
+ } } diff --git a/frontend/src/app/auth/auth.component.ts b/frontend/src/app/auth/auth.component.ts index 5e2dfde6..edede69a 100644 --- a/frontend/src/app/auth/auth.component.ts +++ b/frontend/src/app/auth/auth.component.ts @@ -1,23 +1,20 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Component, inject } from '@angular/core' import { FormGroup, ReactiveFormsModule, UntypedFormBuilder, Validators } from '@angular/forms' -import { Router } from '@angular/router' +import { ActivatedRoute, Router } from '@angular/router' import { FaIconComponent } from '@fortawesome/angular-fontawesome' import { faKey, faLock, faQrcode, faUserAlt } from '@fortawesome/free-solid-svg-icons' import { USER_PASSWORD_MIN_LENGTH } from '@sync-in-server/backend/src/applications/users/constants/user' import { TWO_FA_CODE_LENGTH } from '@sync-in-server/backend/src/authentication/constants/auth' -import { TwoFaResponseDto } from '@sync-in-server/backend/src/authentication/dto/login-response.dto' -import { TwoFaVerifyDto } from '@sync-in-server/backend/src/authentication/dto/two-fa-verify.dto' +import { API_OIDC_CALLBACK } from '@sync-in-server/backend/src/authentication/constants/routes' +import { OAuthDesktopPortParam } from '@sync-in-server/backend/src/authentication/providers/oidc/auth-oidc-desktop.constants' +import type { AuthOIDCSettings } from '@sync-in-server/backend/src/authentication/providers/oidc/auth-oidc.interfaces' +import type { TwoFaResponseDto, TwoFaVerifyDto } from '@sync-in-server/backend/src/authentication/providers/two-fa/auth-two-fa.dtos' import { L10N_LOCALE, L10nLocale, L10nTranslateDirective, L10nTranslatePipe } from 'angular-l10n' import { finalize } from 'rxjs/operators' import { logoDarkUrl } from '../applications/files/files.constants' import { RECENTS_PATH } from '../applications/recents/recents.constants' import { AutofocusDirective } from '../common/directives/auto-focus.directive' +import type { AuthResult } from './auth.interface' import { AuthService } from './auth.service' @Component({ @@ -32,7 +29,12 @@ export class AuthComponent { protected logoUrl = logoDarkUrl protected hasError: any = null protected submitted = false + protected OIDCSubmitted = false protected twoFaVerify = false + private route = inject(ActivatedRoute) + protected oidcSettings: AuthOIDCSettings | false = this.route.snapshot.data.authSettings + private readonly router = inject(Router) + private readonly auth = inject(AuthService) private readonly fb = inject(UntypedFormBuilder) protected loginForm: FormGroup = this.fb.group({ username: this.fb.control('', [Validators.required]), @@ -43,8 +45,12 @@ export class AuthComponent { recoveryCode: this.fb.control('', [Validators.required, Validators.minLength(USER_PASSWORD_MIN_LENGTH)]), isRecoveryCode: this.fb.control(false) }) - private readonly router = inject(Router) - private readonly auth = inject(AuthService) + + constructor() { + if (this.oidcSettings && this.oidcSettings.autoRedirect) { + void this.loginWithOIDC() + } + } onSubmit() { this.submitted = true @@ -52,21 +58,26 @@ export class AuthComponent { .login(this.loginForm.value.username, this.loginForm.value.password) .pipe(finalize(() => setTimeout(() => (this.submitted = false), 1500))) .subscribe({ - next: (res: { success: boolean; message: any; twoFaEnabled?: boolean }) => this.isLogged(res), + next: (res: AuthResult) => this.isLogged(res), error: (e) => this.isLogged({ success: false, message: e.error ? e.error.message : e }) }) } - onSubmit2Fa() { + async onSubmit2Fa() { this.submitted = true - const verifyCode: TwoFaVerifyDto = { - code: this.twoFaForm.value.isRecoveryCode ? this.twoFaForm.value.recoveryCode : this.twoFaForm.value.totpCode, - isRecoveryCode: this.twoFaForm.value.isRecoveryCode + const code = this.twoFaForm.value.isRecoveryCode ? this.twoFaForm.value.recoveryCode : this.twoFaForm.value.totpCode + + if (this.auth.electron.enabled) { + this.auth.electron.register(this.loginForm.value.username, this.loginForm.value.password, code).subscribe({ + next: (res: AuthResult) => this.is2FaVerified(res as TwoFaResponseDto), + error: (e) => this.is2FaVerified({ success: false, message: e.error ? e.error.message : e } as TwoFaResponseDto) + }) + } else { + this.auth.loginWith2Fa({ code: code, isRecoveryCode: this.twoFaForm.value.isRecoveryCode } satisfies TwoFaVerifyDto).subscribe({ + next: (res: TwoFaResponseDto) => this.is2FaVerified(res), + error: (e) => this.is2FaVerified({ success: false, message: e.error ? e.error.message : e } as TwoFaResponseDto) + }) } - this.auth.loginWith2Fa(verifyCode).subscribe({ - next: (res: TwoFaResponseDto) => this.is2FaVerified(res), - error: (e) => this.is2FaVerified({ success: false, message: e.error ? e.error.message : e } as TwoFaResponseDto) - }) } onCancel2Fa() { @@ -77,10 +88,54 @@ export class AuthComponent { this.hasError = null } - is2FaVerified(res: TwoFaResponseDto) { + async loginWithOIDC() { + if (!this.oidcSettings) return + if (!this.auth.electron.enabled) { + window.location.assign(this.oidcSettings.loginUrl) + return + } + this.OIDCSubmitted = true + try { + const desktopPort = await this.auth.electron.startOIDCDesktopAuth() + + if (!desktopPort) { + this.OIDCSubmitted = false + console.error('OIDC desktop auth failed') + return + } + + // Called when the OIDC provider redirects to desktop app + this.auth.electron + .waitOIDCDesktopCallbackParams() + .then((callbackParams: Record) => { + // Receive callback params from desktop app and send it to backend to be authenticated. + const params = new URLSearchParams(callbackParams) + // Indicates the desktop app port used to reconstruct the redirect URI expected by the backend + params.set(OAuthDesktopPortParam, String(desktopPort)) + window.location.assign(`${API_OIDC_CALLBACK}?${params.toString()}`) + // Show desktop app window + this.auth.electron.setActiveAndShow() + }) + .catch((e: Error) => { + this.OIDCSubmitted = false + console.error('Unavailable OIDC desktop callback params:', e) + }) + + // With the desktop app, navigation to the OIDC provider is intercepted and opened in the user's browser. + // The backend sends the oidc cookies to desktop app before the redirection. + window.location.assign(`${this.oidcSettings.loginUrl}?${this.auth.electron.genParamOIDCDesktopPort(desktopPort)}`) + } catch (e) { + this.OIDCSubmitted = false + console.error(e) + } + } + + private is2FaVerified(res: TwoFaResponseDto) { if (res.success) { - // In this case, the user and tokens are provided - this.auth.initUserFromResponse(res) + if (!this.auth.electron.enabled) { + // Web: in this case, the user and tokens are provided + this.auth.initUserFromResponse(res) + } this.isLogged({ success: true, message: res.message }) } else { this.hasError = res.message || 'Unable to verify code' @@ -89,11 +144,15 @@ export class AuthComponent { this.twoFaForm.patchValue({ totpCode: '', recoveryCode: '' }) } - isLogged(res: { success: boolean; message: any; twoFaEnabled?: boolean }) { + private isLogged(res: AuthResult) { if (res.success) { this.hasError = null if (res.twoFaEnabled) { this.twoFaVerify = true + if (this.auth.electron.enabled) { + // Do not clear the password; it will be used to register the client. + return + } } else if (this.auth.returnUrl) { this.router.navigateByUrl(this.auth.returnUrl).then(() => { this.auth.returnUrl = null diff --git a/frontend/src/app/auth/auth.constants.ts b/frontend/src/app/auth/auth.constants.ts index 7f718310..4a93e74e 100644 --- a/frontend/src/app/auth/auth.constants.ts +++ b/frontend/src/app/auth/auth.constants.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - export const AUTH_PATHS = { BASE: 'auth', LOGIN: 'login' diff --git a/frontend/src/app/auth/auth.guards.ts b/frontend/src/app/auth/auth.guards.ts index 38827d36..76242dc9 100644 --- a/frontend/src/app/auth/auth.guards.ts +++ b/frontend/src/app/auth/auth.guards.ts @@ -1,16 +1,13 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { inject } from '@angular/core' import { ActivatedRouteSnapshot, CanActivateFn, Router, RouterStateSnapshot } from '@angular/router' import { Observable } from 'rxjs' +import { AuthOIDCQueryParams } from './auth.interface' import { AuthService } from './auth.service' -export const authGuard: CanActivateFn = (_route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable => { - return inject(AuthService).checkUserAuthAndLoad(state.url) +export const authGuard: CanActivateFn = (route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable => { + // Authentication initiated via OIDC callback + const authFromOIDC = route.queryParams?.oidc ? (route.queryParams as AuthOIDCQueryParams) : undefined + return inject(AuthService).checkUserAuthAndLoad(state.url, authFromOIDC) } export const noAuthGuard: CanActivateFn = (): boolean => { diff --git a/frontend/src/app/auth/auth.interceptor.ts b/frontend/src/app/auth/auth.interceptor.ts index fb0a29be..eb78831d 100644 --- a/frontend/src/app/auth/auth.interceptor.ts +++ b/frontend/src/app/auth/auth.interceptor.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { HttpErrorResponse, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http' import { inject, Injectable, Injector } from '@angular/core' import { API_AUTH_LOGIN, API_AUTH_LOGOUT, API_AUTH_REFRESH } from '@sync-in-server/backend/src/authentication/constants/routes' diff --git a/frontend/src/app/auth/auth.interface.ts b/frontend/src/app/auth/auth.interface.ts new file mode 100644 index 00000000..20b8818a --- /dev/null +++ b/frontend/src/app/auth/auth.interface.ts @@ -0,0 +1,11 @@ +export interface AuthResult { + success: boolean + message: any + twoFaEnabled?: boolean +} + +export interface AuthOIDCQueryParams { + oidc: string + access_expiration: string + refresh_expiration: string +} diff --git a/frontend/src/app/auth/auth.routes.ts b/frontend/src/app/auth/auth.routes.ts index 4ceb85f3..60d7f4b8 100644 --- a/frontend/src/app/auth/auth.routes.ts +++ b/frontend/src/app/auth/auth.routes.ts @@ -1,10 +1,5 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Routes } from '@angular/router' +import { authResolver } from './auth-resolvers' import { AuthComponent } from './auth.component' import { AUTH_PATHS } from './auth.constants' import { noAuthGuard } from './auth.guards' @@ -13,6 +8,7 @@ export const authRoutes: Routes = [ { path: AUTH_PATHS.BASE, canActivate: [noAuthGuard], + resolve: { authSettings: authResolver }, children: [{ path: AUTH_PATHS.LOGIN, component: AuthComponent }] } ] diff --git a/frontend/src/app/auth/auth.service.ts b/frontend/src/app/auth/auth.service.ts index a60646e8..a8e66421 100644 --- a/frontend/src/app/auth/auth.service.ts +++ b/frontend/src/app/auth/auth.service.ts @@ -1,27 +1,23 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { HttpClient, HttpErrorResponse, HttpRequest } from '@angular/common/http' import { inject, Injectable } from '@angular/core' import { Router } from '@angular/router' import { CLIENT_TOKEN_EXPIRED_ERROR } from '@sync-in-server/backend/src/applications/sync/constants/auth' -import { API_SYNC_AUTH_COOKIE } from '@sync-in-server/backend/src/applications/sync/constants/routes' +import { API_SYNC_AUTH_COOKIE, API_SYNC_REGISTER_AUTH } from '@sync-in-server/backend/src/applications/sync/constants/routes' import type { SyncClientAuthDto } from '@sync-in-server/backend/src/applications/sync/dtos/sync-client-auth.dto' -import type { ClientAuthCookieDto } from '@sync-in-server/backend/src/applications/sync/interfaces/sync-client-auth.interface' +import { SyncClientAuthCookie, SyncClientAuthRegistration } from '@sync-in-server/backend/src/applications/sync/interfaces/sync-client-auth.interface' import { API_ADMIN_IMPERSONATE_LOGOUT, API_USERS_ME } from '@sync-in-server/backend/src/applications/users/constants/routes' import { CSRF_KEY } from '@sync-in-server/backend/src/authentication/constants/auth' import { API_AUTH_LOGIN, API_AUTH_LOGOUT, API_AUTH_REFRESH, + API_AUTH_SETTINGS, API_TWO_FA_LOGIN_VERIFY } from '@sync-in-server/backend/src/authentication/constants/routes' -import { LoginResponseDto, TwoFaResponseDto } from '@sync-in-server/backend/src/authentication/dto/login-response.dto' +import type { LoginResponseDto } from '@sync-in-server/backend/src/authentication/dto/login-response.dto' import type { TokenResponseDto } from '@sync-in-server/backend/src/authentication/dto/token-response.dto' -import { TwoFaVerifyDto } from '@sync-in-server/backend/src/authentication/dto/two-fa-verify.dto' +import type { AuthOIDCSettings } from '@sync-in-server/backend/src/authentication/providers/oidc/auth-oidc.interfaces' +import type { TwoFaResponseDto, TwoFaVerifyDto } from '@sync-in-server/backend/src/authentication/providers/two-fa/auth-two-fa.dtos' import { currentTimeStamp } from '@sync-in-server/backend/src/common/shared' import { catchError, finalize, map, Observable, of, throwError } from 'rxjs' import { switchMap, tap } from 'rxjs/operators' @@ -33,18 +29,20 @@ import { Electron } from '../electron/electron.service' import { LayoutService } from '../layout/layout.service' import { StoreService } from '../store/store.service' import { AUTH_PATHS } from './auth.constants' +import type { AuthOIDCQueryParams, AuthResult } from './auth.interface' @Injectable({ providedIn: 'root' }) export class AuthService { public returnUrl: string + public electron = inject(Electron) + private authSettings: AuthOIDCSettings | false = null private readonly http = inject(HttpClient) private readonly router = inject(Router) private readonly store = inject(StoreService) private readonly userService = inject(UserService) private readonly layout = inject(LayoutService) - private readonly electron = inject(Electron) private _refreshExpiration = parseInt(localStorage.getItem('refresh_expiration') || '0', 10) || 0 @@ -70,50 +68,26 @@ export class AuthService { localStorage.setItem('access_expiration', value.toString()) } - login(login: string, password: string): Observable<{ success: boolean; message: any; twoFaEnabled?: boolean }> { + login(login: string, password: string): Observable { return this.http.post(API_AUTH_LOGIN, { login, password }).pipe( - map((r: LoginResponseDto) => { + switchMap((r: LoginResponseDto) => { + // 2FA - first step (code page) if (r.server.twoFaEnabled && r.user.twoFaEnabled) { - // check 2FA before logging in the user this.accessExpiration = r.token.access_2fa_expiration this.refreshExpiration = this.accessExpiration - return { success: true, twoFaEnabled: true, message: null } - } else { - this.initUserFromResponse(r) + return of({ success: true, twoFaEnabled: true, message: null }) + } + // Desktop Client Login + if (this.electron.enabled) { + return this.electron.register(login, password) } - return { success: true, message: null } + // Web Login + this.initUserFromResponse(r) + return of({ success: true, message: null }) }), catchError((e) => { - console.warn(e) - return of({ success: false, message: e.error.message || e.message }) - }) - ) - } - - loginElectron(): Observable { - return this.electron.authenticate().pipe( - switchMap((auth: SyncClientAuthDto) => { - return this.http.post(API_SYNC_AUTH_COOKIE, auth).pipe( - map((r: ClientAuthCookieDto) => { - this.accessExpiration = r.token.access_expiration - this.refreshExpiration = r.token.refresh_expiration - this.initUser(r) - if (r?.client_token_update) { - // update client token - this.electron.send(EVENT.SERVER.AUTHENTICATION_TOKEN_UPDATE, r.client_token_update) - } - return true - }), - catchError((e: HttpErrorResponse) => { - console.warn(e) - if (e.error.message === CLIENT_TOKEN_EXPIRED_ERROR) { - this.electron.send(EVENT.SERVER.AUTHENTICATION_TOKEN_EXPIRED) - } else { - this.electron.send(EVENT.SERVER.AUTHENTICATION_FAILED) - } - return of(false) - }) - ) + console.error(e) + return of({ success: false, message: e?.error?.message ?? e?.message }) }) ) } @@ -142,20 +116,6 @@ export class AuthService { .subscribe() } - logoutImpersonateUser() { - this.http.post(API_ADMIN_IMPERSONATE_LOGOUT, null).subscribe({ - next: (r: LoginResponseDto) => { - this.userService.disconnectWebSocket() - this.initUserFromResponse(r) - this.router.navigate([USER_PATH.BASE, USER_PATH.ACCOUNT]).catch(console.error) - }, - error: (e: HttpErrorResponse) => { - console.error(e) - this.layout.sendNotification('error', 'Impersonate identity', 'logout', e) - } - }) - } - initUserFromResponse(r: LoginResponseDto, impersonate = false) { if (r !== null) { this.accessExpiration = r.token.access_expiration @@ -176,10 +136,8 @@ export class AuthService { return true }), catchError((e: HttpErrorResponse) => { - console.debug('token has expired') if (this.electron.enabled) { - console.debug('login with app') - return this.loginElectron() + return this.authDesktopClient() } this.logout(true, true) return throwError(() => e) @@ -187,17 +145,30 @@ export class AuthService { ) } - checkUserAuthAndLoad(returnUrl: string): Observable { + checkUserAuthAndLoad(returnUrl: string, authFromOIDC?: AuthOIDCQueryParams): Observable { + if (authFromOIDC) { + // At this point, the auth cookies are already stored in the session. + this.accessExpiration = parseInt(authFromOIDC.access_expiration) + this.refreshExpiration = parseInt(authFromOIDC.refresh_expiration) + if (this.electron.enabled) { + return this.authOIDCDesktopClient() + } + } if (this.refreshTokenHasExpired()) { if (this.electron.enabled) { - return this.loginElectron() + return this.authDesktopClient() } this.returnUrl = returnUrl.length > 1 ? returnUrl : null this.logout() return of(false) } else if (!this.store.user.getValue()) { return this.http.get>(API_USERS_ME).pipe( - tap((r: Omit) => this.initUser(r)), + tap((r: Omit) => { + this.initUser(r) + if (authFromOIDC) { + this.router.navigate([]).catch(console.error) + } + }), map(() => true), catchError((e: HttpErrorResponse) => { if (e.status === 401) { @@ -231,6 +202,108 @@ export class AuthService { } } + getAuthSettings(): Observable { + // If OIDC authentication is not enabled, the route should return a 404. + if (this.authSettings !== null) return of(this.authSettings) + return this.http.get(API_AUTH_SETTINGS).pipe( + map((r): AuthOIDCSettings => { + this.authSettings = r + return r + }), + catchError((e: HttpErrorResponse) => { + console.error(e) + this.authSettings = false + return of(false as const) + }) + ) + } + + private logoutImpersonateUser() { + this.http.post(API_ADMIN_IMPERSONATE_LOGOUT, null).subscribe({ + next: (r: LoginResponseDto) => { + this.userService.disconnectWebSocket() + this.initUserFromResponse(r) + this.router.navigate([USER_PATH.BASE, USER_PATH.ACCOUNT]).catch(console.error) + }, + error: (e: HttpErrorResponse) => { + console.error(e) + this.layout.sendNotification('error', 'Impersonate identity', 'logout', e) + } + }) + } + + private authDesktopClient(): Observable { + return this.electron.authenticate().pipe( + switchMap((auth: SyncClientAuthDto) => { + if (!auth.clientId) { + // No auth was provided, the Sync-in desktop app must be registered + console.debug(`${this.authDesktopClient.name} - client must be registered`) + this.logout(true) + return of(false) + } + return this.http.post(API_SYNC_AUTH_COOKIE, auth).pipe( + map((r: SyncClientAuthCookie) => { + this.accessExpiration = r.token.access_expiration + this.refreshExpiration = r.token.refresh_expiration + this.initUser(r) + if (r?.client_token_update) { + // Update the client token + this.electron.send(EVENT.SERVER.AUTHENTICATION_TOKEN_UPDATE, r.client_token_update) + } + return true + }), + catchError((e: HttpErrorResponse) => { + console.debug(`${this.authDesktopClient.name} - ${e.error.message}`) + if (e.error.message === CLIENT_TOKEN_EXPIRED_ERROR) { + this.electron.send(EVENT.SERVER.AUTHENTICATION_TOKEN_EXPIRED) + } else { + // In other cases, we consider the server unavailable + this.electron.send(EVENT.SERVER.AUTHENTICATION_FAILED) + } + this.logout(true, e.error.message === CLIENT_TOKEN_EXPIRED_ERROR) + return of(false) + }) + ) + }) + ) + } + + private authOIDCDesktopClient(): Observable { + // Retrieve authentication info from the desktop app + return this.electron.authenticate().pipe( + switchMap((auth: SyncClientAuthDto) => { + if (!auth.clientId || auth.tokenHasExpired) { + // The client must be registered, or the token must be renewed + return this.http.post(API_SYNC_REGISTER_AUTH, auth).pipe( + switchMap((externalAuth: SyncClientAuthRegistration) => { + // Store the clientId and the clientToken on the desktop app + return this.electron.externalRegister(externalAuth).pipe( + switchMap((success: boolean) => { + if (success) { + console.debug(`${this.authOIDCDesktopClient.name} - ${auth.clientId ? 'client was registered' : 'client token renewed'}`) + // Starts authentication + return this.authDesktopClient() + } else { + this.logout(true, true) + return of(false) + } + }) + ) + }), + catchError((e: HttpErrorResponse) => { + console.error(`${this.authOIDCDesktopClient.name} - ${e}`) + this.logout(true) + return of(false) + }) + ) + } else { + // The client must be (re)authenticated + return this.authDesktopClient() + } + }) + ) + } + private refreshTokenHasExpired(): boolean { return this.refreshExpiration === 0 || currentTimeStamp() >= this.refreshExpiration } diff --git a/frontend/src/app/common/components/filter.component.ts b/frontend/src/app/common/components/filter.component.ts index c1f1b087..2b4ee040 100644 --- a/frontend/src/app/common/components/filter.component.ts +++ b/frontend/src/app/common/components/filter.component.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Component, ElementRef, HostListener, inject, OnDestroy, signal, ViewChild } from '@angular/core' import { FormBuilder, FormControl, ReactiveFormsModule } from '@angular/forms' import { FaIconComponent } from '@fortawesome/angular-fontawesome' diff --git a/frontend/src/app/common/components/input-password.component.ts b/frontend/src/app/common/components/input-password.component.ts index 1aab58f0..7f029b36 100644 --- a/frontend/src/app/common/components/input-password.component.ts +++ b/frontend/src/app/common/components/input-password.component.ts @@ -1,9 +1,3 @@ -/* - * Copyright (C) 2012-2025 Johan Legrand - * This file is part of Sync-in | The open source file sync and share solution - * See the LICENSE file for licensing details - */ - import { Component, ElementRef, EventEmitter, inject, Input, OnInit, Output, ViewChild } from '@angular/core' import { FormsModule, ReactiveFormsModule } from '@angular/forms' import { FaIconComponent } from '@fortawesome/angular-fontawesome' diff --git a/frontend/src/app/common/components/navigation-view/navigation-view.component.html b/frontend/src/app/common/components/navigation-view/navigation-view.component.html index 08e7660b..ec10638b 100644 --- a/frontend/src/app/common/components/navigation-view/navigation-view.component.html +++ b/frontend/src/app/common/components/navigation-view/navigation-view.component.html @@ -1,9 +1,3 @@ - -