Sharely — Technical Documentation

Version: 1.0.0 · License: MIT · Node.js: ≥ 18

Table of Contents

1. Project Overview

2. Architecture

3. Tech Stack

4. Directory Structure

5. Configuration & Environment Variables

6. Data Models

7. REST API

8. WebSocket API

9. File Serving Routes

10. Authentication & Security

11. Upload System

12. Thumbnail Generation

13. Email & SMTP

14. Internationalization

15. Frontend (React SPA)

16. Admin Dashboard

17. GDPR / Privacy

18. Background Jobs

19. Deployment

20. Development Environment

21. Migrations & Scripts

22. End-to-End Tests


1. Project Overview

Sharely is a self-hosted file sharing platform with a clean web interface, ShareX integration, and API access. Users upload screenshots, files, and media and share them instantly via short links.

Core Features

FeatureDescription
Web UploadDrag-and-drop, up to 500 files at once
Chunked UploadFiles up to 2 GB via parallel multi-part upload
ShareX Integration.sxcu configuration file downloadable with one click
API UploadBearer token authentication, compatible with curl/wget
File ViewerZoom images, stream videos/audio (HTTP Range), PDFs inline, code syntax-highlighted
Embed Modes*embed* (OG/Twitter Card HTML) or *raw* (direct redirect)
ThumbnailsAutomatic JPEG previews for videos (ffmpeg) and PDFs (ghostscript)
CollectionsGroup collections of files with optional password and expiry date
Share LinksPer-file links with password, expiry, and download limit
Real-Time UIWebSocket-based live updates (upload, delete, view counter, admin stats)
Multilingual8 languages: EN, DE, FR, ES, IT, PT, JA, ZH
Admin DashboardStatistics, user management, file management, audit log (CSV export)
GDPR CompliancePrivacy features compliant with EU GDPR (Art. 17, 20, 32 et al.)
XBackBone ImportMigration of existing XBackBone installations
Docker-readydocker compose up -d starts the complete environment

2. Architecture

┌─────────────────────────────────────────────────────────────┐
│                        Browser / Client                      │
│            React 18 SPA  ·  Vite  ·  Tailwind CSS           │
│   ┌──────────────────────────────────────────────────────┐   │
│   │  HTTP REST (/api/*)            WebSocket (/ws)       │   │
│   └──────────────────────────────────────────────────────┘   │
└───────────────────────────┬────────────────────┬─────────────┘
                            │ HTTP               │ WS
┌───────────────────────────▼────────────────────▼─────────────┐
│                    Express.js (app.js)                        │
│                                                               │
│  ┌──────────────┐  ┌───────────┐  ┌──────────┐  ┌────────┐  │
│  │  /api/auth   │  │  /api/*   │  │   /f/*   │  │  /s/*  │  │
│  │  /api/install│  │  routes   │  │  files   │  │ shares │  │
│  └──────────────┘  └───────────┘  └──────────┘  └────────┘  │
│                                                               │
│  Middleware: session · rate-limit · CSRF · CSP · auth        │
│                                                               │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐  ┌────────────┐  │
│  │  multer  │  │  ws.js   │  │  mailer  │  │ retention  │  │
│  │  upload  │  │ WebSocket│  │ nodemailer│  │  cleanup   │  │
│  └──────────┘  └──────────┘  └──────────┘  └────────────┘  │
└───────────────────────────┬───────────────────────────────────┘
                            │
┌───────────────────────────▼───────────────────────────────────┐
│                      MongoDB (Mongoose)                        │
│  User · File · Collection · ShareLink · SiteSettings          │
│  AuditLog                                                      │
└───────────────────────────────────────────────────────────────┘
                            │
              ┌─────────────▼───────────────┐
              │     Dateisystem (uploads/)   │
              │  {folderName}/  ·  .chunks/  │
              │  .thumbnails/   ·  .avatars/ │
              └─────────────────────────────┘

Data Flow: Standard Upload

Browser → POST /api/web-upload (multipart/form-data)
       → multer: Datei in uploads/{folderName}/
       → File.createUnique() → MongoDB
       → generateThumbnail() (async, non-blocking)
       → logAudit()
       → broadcast('file:uploaded') via WebSocket
       ← JSON: { files: [...] }

Data Flow: ShareX Upload

ShareX → POST /upload (token im Formular-Body)
       → multer: Datei temporär in uploads/
       → requireApiKey: Token-Lookup → User
       → fs.renameSync → uploads/{folderName}/
       → File.create() → MongoDB
       ← JSON: { url, delete_url }

3. Tech Stack

LayerTechnologyVersion
RuntimeNode.js≥ 18
Backend FrameworkExpress.js4.x
DatabaseMongoDB + MongooseMongo 7, Mongoose 8.x
Sessionsexpress-session + connect-mongo
Real-TimeWebSocket (ws)8.x
File UploadMulter1.x
EmailNodemailer8.x
Password Hashingbcryptjs2.x (12 Rounds)
API Key HashingSHA-256 (Node Crypto)
Rate Limitingexpress-rate-limit8.x
XBackBone Importsql.js1.x
Frontend FrameworkReact 1818.3.x
Routing (Frontend)React Router v66.x
Build ToolVite6.x
StylingTailwind CSS + Radix UI3.x
UI Componentsshadcn/ui (Radix primitives)
IconsFontAwesome6.x
i18ni18next + react-i18next
Syntax Highlightinghighlight.js11.x
ContainerDocker + Docker Compose
TestsPlaywright (E2E)1.60.x

4. Directory Structure

sharely/
├── app.js                          # Express entry point, startup sequence
├── package.json
├── .env.example                    # Template for all environment variables
├── Dockerfile
├── docker-compose.yml
│
├── src/
│   ├── config/
│   │   └── db.js                   # MongoDB connection (Mongoose)
│   ├── middleware/
│   │   ├── auth.js                 # requireLogin / requireAdmin / requireApiKey
│   │   └── upload.js               # Multer configuration, blocklist
│   ├── models/
│   │   ├── AuditLog.js             # Audit events (TTL 90 days)
│   │   ├── Collection.js           # File collections
│   │   ├── File.js                 # File metadata
│   │   ├── ShareLink.js            # Per-file share links
│   │   ├── SiteSettings.js         # Singleton: operator settings
│   │   └── User.js                 # User accounts + API keys
│   ├── routes/
│   │   ├── api.js                  # Main API (upload, gallery, admin, ...)
│   │   ├── auth.js                 # Login / Register / Password reset
│   │   ├── files.js                # File serving, OG embeds, range requests
│   │   ├── import.js               # XBackBone migration
│   │   ├── install.js              # Initial installation endpoint
│   │   └── shares.js               # Share link file serving
│   ├── jobs/
│   │   └── retentionCleanup.js     # Daily deletion of expired files
│   ├── migrations/
│   │   ├── migrateApiKeyHashes.js  # One-time: plaintexts → SHA-256 hashes
│   │   └── migrateUserFolders.js   # One-time: move files into user folders
│   ├── utils/
│   │   ├── audit.js                # logAudit() helper function
│   │   ├── generateThumbnail.js    # ffmpeg / ghostscript integration
│   │   ├── mailer.js               # Nodemailer wrapper + i18n email templates
│   │   └── sanitizeFilename.js     # Sanitize filename
│   └── ws.js                       # WebSocket server + action dispatcher
│
├── client/                         # React frontend
│   ├── index.html
│   ├── package.json
│   ├── vite.config.js
│   ├── tailwind.config.js
│   └── src/
│       ├── main.jsx                # React entry point
│       ├── App.jsx                 # Router configuration
│       ├── index.css               # Global styles
│       ├── context/
│       │   └── AuthContext.jsx     # Global auth state
│       ├── hooks/
│       │   ├── use-toast.js        # Toast notification hook
│       │   └── useWebSocket.js     # WS connection + event handlers
│       ├── components/
│       │   ├── Layout.jsx          # App shell (navbar, sidebar)
│       │   ├── ProtectedRoute.jsx  # Auth guard
│       │   ├── ShareLinkDialog.jsx # Share link creation dialog
│       │   ├── AddToCollectionDialog.jsx
│       │   ├── CookieBanner.jsx
│       │   ├── LanguageSelector.jsx
│       │   ├── RequireEmailDialog.jsx
│       │   ├── UserAvatar.jsx
│       │   └── ui/                 # shadcn/ui base components
│       ├── pages/
│       │   ├── Upload.jsx          # Upload page
│       │   ├── Gallery.jsx         # File gallery
│       │   ├── FileView.jsx        # File detail view
│       │   ├── Collections.jsx     # Collections overview
│       │   ├── CollectionView.jsx  # Individual collection
│       │   ├── ShareView.jsx       # Public share link page
│       │   ├── Settings.jsx        # User settings
│       │   ├── Login.jsx
│       │   ├── Register.jsx
│       │   ├── ForgotPassword.jsx
│       │   ├── ResetPassword.jsx
│       │   ├── Install.jsx         # Initial installation
│       │   ├── PrivacyPolicy.jsx
│       │   ├── TermsOfService.jsx
│       │   └── admin/
│       │       ├── Dashboard.jsx   # Admin home page
│       │       ├── Users.jsx       # User management
│       │       ├── Files.jsx       # File management
│       │       ├── AuditLog.jsx    # Audit log view
│       │       ├── SiteSettings.jsx# Operator settings
│       │       └── Import.jsx      # XBackBone import
│       ├── i18n/
│       │   ├── index.js            # i18next configuration
│       │   └── locales/
│       │       ├── de.json
│       │       ├── en.json
│       │       ├── es.json
│       │       ├── fr.json
│       │       ├── it.json
│       │       ├── ja.json
│       │       ├── pt.json
│       │       └── zh.json
│       └── lib/
│           └── utils.js            # Tailwind helper (cn())
│
├── scripts/
│   ├── setup-db.js
│   ├── mongo-init.js               # MongoDB initialization script
│   ├── migrate-uploads-to-user-folders.js
│   └── generate-missing-thumbnails.js
│
├── e2e/                            # Playwright tests
│   ├── admin.spec.js
│   ├── bulk-actions-fixes.spec.js
│   ├── gallery.spec.js
│   ├── sharelink.spec.js
│   ├── tags.spec.js
│   ├── upload.spec.js
│   ├── helpers.js
│   └── global-setup.js
│
├── uploads/                        # File uploads (runtime)
│   ├── .thumbnails/
│   ├── .avatars/
│   └── .chunks/                    # Temporary chunks
│
└── docs/assets/                    # Screenshots and logo

5. Configuration & Environment Variables

All variables are loaded from .env (via dotenv). The file .env.example contains the complete template.

Required Fields

VariableDescription
SESSION_SECRETSecret for session encryption — long random string, e.g. openssl rand -hex 32
MONGO_ROOT_PASSWORDMongoDB root password (required for Docker Compose only)
MONGO_APP_PASSWORDMongoDB application user password

All Environment Variables

VariableDefaultDescription
PORT3000TCP port of the HTTP server
MONGODB_URI_(constructed from Docker Compose)_Full MongoDB connection URI
MONGO_ROOT_PASSWORDMongoDB root password
MONGO_APP_USERappuserMongoDB application username
MONGO_APP_PASSWORDMongoDB application user password
MONGO_DB_NAMEsharelyMongoDB database name
SESSION_SECRETRequired — session encryption secret
BASE_URLhttp://localhost:3000Public base URL for generated share links (no trailing /)
SITE_NAMEsharelySite name in Open Graph embeds
MAX_FILE_SIZE_MB100Maximum file size for standard uploads in MB (chunked uploads up to 2 GB independently)
ALLOW_REGISTRATIONtruefalse disables public registration
SMTP_HOSTSMTP server hostname; leave empty to disable email features
SMTP_PORT587SMTP port
SMTP_SECUREfalsetrue for implicit TLS (port 465), false for STARTTLS
SMTP_USERSMTP username
SMTP_PASSSMTP password
SMTP_FROM_(SMTP_USER)_Sender address in outgoing emails
UPLOAD_DIR./uploadsAbsolute path to upload directory
NODE_ENVproduction enables secure cookies

6. Data Models

User (src/models/User.js)

{
  username:                    String (3–32, unique, alphanumeric + _-)
  password:                    String (bcrypt, 12 Rounds)
  role:                        'admin' | 'user'
  apiKey:                      String (Legacy, empty after migration)
  apiKeyHash:                  String (SHA-256, unique, sparse)
  apiKeyPrefix:                String (first 8 characters of plaintext)
  folderName:                  String (unique, sparse, max 64)
  avatarExt:                   String | null (.jpg/.png/.gif/.webp)
  embedMode:                   'embed' | 'raw'
  isActive:                    Boolean
  email:                       String (lowercase, unique, sparse)
  emailVerified:               Boolean
  emailVerificationToken:      String | null (SHA-256 hash of plaintext)
  emailVerificationExpires:    Date | null
  passwordResetToken:          String | null (SHA-256 hash)
  passwordResetExpires:        Date | null
  language:                    'en'|'de'|'fr'|'es'|'it'|'pt'|'ja'|'zh'
  predefinedTags:              [String] (max 100 tags × 50 characters)
  createdAt:                   Date
}

Important Methods:

File (src/models/File.js)

{
  shortId:      String (8 hex characters: 6 timestamp + 2 random, unique)
  deleteToken:  String (32 hex characters, unique)
  originalName: String (sanitized)
  storedName:   String (relative path: "folderName/8hex.ext")
  mimeType:     String
  size:         Number (bytes)
  uploader:     ObjectId → User
  views:        Number
  tags:         [String] (max 20 × 50 characters)
  createdAt:    Date
}

Virtuals:

Short ID Algorithm:

shortId = hex(seconds_since_2024-01-01, 6 chars) + randomBytes(2).hex()

Collection (src/models/Collection.js)

{
  shortId:     String (8 Hex, unique)
  name:        String (max 100)
  description: String (max 500)
  owner:       ObjectId → User
  files:       [ObjectId → File]
  password:    String | null (bcrypt)
  expiresAt:   Date | null
  createdAt:   Date
}
Expired collections are automatically deleted by MongoDB TTL index 7 days after expiry.
{
  token:         String (32 Hex, unique)
  file:          ObjectId → File
  createdBy:     ObjectId → User
  label:         String (max 100)
  password:      String | null (bcrypt)
  expiresAt:     Date | null
  downloadLimit: Number (-1 = unlimited)
  downloadCount: Number
  createdAt:     Date
}
Like collections: 7-day grace period after expiry via TTL index.

SiteSettings (src/models/SiteSettings.js)

Singleton document (_id: 'singleton'):

{
  operatorName:        String
  operatorAddress:     String
  operatorEmail:       String
  cloudflareAnalytics: Boolean
  fileRetentionDays:   Number (0 = disabled)
  encryptionAtRest:    Boolean
  sessionDurationDays: Number (default: 7)
  allowRegistration:   Boolean
}

AuditLog (src/models/AuditLog.js)

{
  timestamp: Date (TTL index: 90 days)
  userId:    ObjectId → User | null
  username:  String | null
  action:    String
  ip:        String | null
  meta:      Mixed (action-specific metadata)
}

7. REST API

Base URL: /api

All JSON endpoints return Content-Type: application/json. Errors: { "error": "message" }.


Authentication (/api/auth)

MethodPathAuthDescription
GET/meSessionCurrently logged-in user
POST/loginLog in (rate-limited: 10/15min)
POST/registerRegister (rate-limited, first user becomes admin)
POST/logoutLog out
GET/smtp-enabledCheck whether SMTP is configured
GET/verify-email?token=Verify email address
GET/verify-reset-token?token=Validate reset token
POST/forgot-passwordSend password reset email (rate-limited: 5/hr)
POST/reset-passwordSet new password

Login Request:

POST /api/auth/login
{ "username": "max", "password": "meinPasswort123" }

Login Response:

{
  "user": {
    "id": "...", "username": "max", "role": "user",
    "avatarUrl": null, "email": "[email protected]",
    "emailVerified": true, "language": "de"
  }
}

File Upload

MethodPathAuthDescription
POST/uploadAPI KeyShareX upload (legacy endpoint in app.js)
POST/api/uploadAPI KeyShareX/API upload (field: file)
POST/api/web-uploadSessionWeb upload (field: files[], max 500)
POST/api/chunk/initSessionInitialize chunked upload
POST/api/chunk/:uploadIdSessionUpload a single chunk (field: chunk)
POST/api/chunk/:uploadId/completeSessionAssemble chunks
DELETE/api/chunk/:uploadIdSessionCancel upload & clean up

API Upload Response:

{
  "url": "https://example.com/f/a1b2c3d4",
  "raw": "https://example.com/f/a1b2c3d4/raw",
  "delete_url": "https://example.com/api/delete/a1b2c3d4",
  "short_id": "a1b2c3d4",
  "filename": "screenshot.png",
  "size": 102400
}

Chunked Upload Flow

1. POST /api/chunk/init{ uploadId: "32hex" }

2. POST /api/chunk/:uploadId (Body: chunkIndex=N, File: chunk) → { received: N }

Parallel with 3–5 concurrent chunks

3. POST /api/chunk/:uploadId/complete{ files: [fileObject] }


File Management

MethodPathAuthDescription
GET/api/gallerySessionOwn files (admin: all), paginated (24/page), filters: q, type, tag, page
GET/api/file/:shortIdFile metadata (increments view counter)
PATCH/api/file/:shortIdSessionUpdate tags/name
DELETE/api/file/:shortIdSessionDelete file
DELETE/api/delete/:shortIdAPI KeyDelete file (API key auth)
POST/api/files/bulkSessionBulk actions: delete, tag, removeTag, addToCollection, moveToCollection
GET/api/tagsSessionAll tag suggestions for the user

Gallery Query Parameters:


User Settings

MethodPathAuthDescription
GET/api/my-keySessionShow API key prefix
POST/api/regen-keySessionRegenerate API key
GET/api/sharex-configSessionDownload ShareX .sxcu (regenerates key)
PATCH/api/user/usernameSessionChange username (password required)
PATCH/api/user/passwordSessionChange password
PATCH/api/user/emailSessionChange email (sends verification email)
PATCH/api/user/languageSessionSet UI language
PATCH/api/user/embed-modeSessionSet embed mode (embed/raw)
POST/api/user/resend-verificationSessionResend verification email
GET/api/user/exportSessionData export (GDPR Art. 20) as JSON
DELETE/api/user/accountSessionDelete account (GDPR Art. 17, password required)
GET/api/user/predefined-tagsSessionRetrieve predefined tags
PATCH/api/user/predefined-tagsSessionUpdate predefined tags
POST/api/user/avatarSessionUpload avatar (max 2 MB, JPEG/PNG/GIF/WebP)
DELETE/api/user/avatarSessionDelete avatar
GET/api/user/avatar/:userIdServe avatar

MethodPathAuthDescription
GET/api/file/:shortId/share-linksSession (Owner/Admin)All share links for a file
POST/api/file/:shortId/share-linksSession (Owner/Admin)Create share link
DELETE/api/share-links/:tokenSession (Owner/Creator/Admin)Delete share link
GET/api/share-links/:tokenShare link metadata (public)
POST/api/share-links/:token/verifyVerify share link password

Create Share Link:

POST /api/file/a1b2c3d4/share-links
{
  "label": "For colleagues",
  "password": "secret",
  "expiresAt": "2025-12-31T23:59:59Z",
  "downloadLimit": 10
}

Collections

MethodPathAuthDescription
GET/api/collectionsSessionOwn collections (admin: all)
POST/api/collectionsSessionCreate collection
GET/api/collections/:idView collection (public, password if set)
PATCH/api/collections/:idSession (Owner/Admin)Update collection
DELETE/api/collections/:idSession (Owner/Admin)Delete collection
POST/api/collections/:id/filesSession (Owner/Admin)Add file to collection
DELETE/api/collections/:id/files/:fileShortIdSession (Owner/Admin)Remove file from collection
POST/api/collections/:id/verifyVerify collection password

Admin Endpoints

MethodPathAuthDescription
GET/api/admin/statsAdminDashboard statistics
GET/api/admin/usersAdminAll users (including file counts)
POST/api/admin/usersAdminCreate user
PATCH/api/admin/users/:id/toggleAdminActivate/deactivate user
PATCH/api/admin/users/:id/roleAdminChange user role
DELETE/api/admin/users/:idAdminDelete user
POST/api/admin/users/:id/regen-keyAdminRegenerate API key
PATCH/api/admin/users/:id/passwordAdminSet password
PATCH/api/admin/users/:id/folderAdminChange folder name (moves files)
GET/api/admin/filesAdminAll files, paginated (30/page)
GET/api/admin/site-settingsAdminRead operator settings
PATCH/api/admin/site-settingsAdminUpdate operator settings
GET/api/admin/audit-logAdminPaginated audit log (50/page)
GET/api/admin/audit-log/exportAdminDownload audit log as CSV
GET/api/site-settingsPublic operator info (for privacy page)

Site Settings (Public)

GET /api/site-settings
{
  "operatorName": "Musterfirma GmbH",
  "operatorAddress": "Musterstr. 1, 12345 Musterstadt",
  "operatorEmail": "[email protected]",
  "cloudflareAnalytics": false,
  "fileRetentionDays": 365,
  "encryptionAtRest": false,
  "sessionDurationDays": 7
}

8. WebSocket API

Connection: wss://example.com/ws (logged-in users only, session cookie required)

Protocol

Client → Server (Request):

{ "id": "req-abc123", "action": "file:list", "payload": { "type": "image", "page": 1 } }

Server → Client (Response):

{ "id": "req-abc123", "data": { ... } }

Server → Client (Error):

{ "id": "req-abc123", "error": "Forbidden", "status": 403 }

Server → Client (Broadcast):

{ "event": "file:uploaded", "data": { "shortId": "a1b2c3d4", "uploaderId": "..." } }

Available Actions

ActionAuthDescription
site-settings:getPublic site settings
auth:meUserOwn user data
file:getUserFile details (increments views)
file:listUserFile list with filter/pagination
file:deleteUserDelete file
user:get-keyUserAPI key prefix
user:regen-keyUserRegenerate API key
user:change-passwordUserChange password
user:change-usernameUserChange username
user:change-emailUserChange email
user:change-languageUserSet language
user:change-embed-modeUserSet embed mode
user:resend-verificationUserResend verification email
user:exportUserData export
user:delete-accountUserDelete account
admin:statsAdminDashboard statistics
admin:settings:getAdminRead site settings
admin:settings:updateAdminUpdate site settings
admin:users:listAdminAll users
admin:users:createAdminCreate user
admin:users:toggleAdminActivate/deactivate user
admin:users:roleAdminChange user role
admin:users:deleteAdminDelete user
admin:users:regen-keyAdminRegenerate API key
admin:users:passwordAdminSet password
admin:users:folderAdminChange folder name
admin:files:listAdminAll files
admin:audit-log:listAdminPaginated audit log

Broadcast Events

EventRecipientsPayload
file:uploadedUploader{ shortId, uploaderId }
file:deletedFile owner{ shortId, uploaderId }
file:viewAll{ shortId, views }
user:createdAdmins{ id, username, role, ... }
user:deletedAdmins{ id }
user:updatedAdmins{ id, ...changed fields }
audit:logAdminsComplete AuditLog object
settings:updatedAdminsUpdated SiteSettings object
stats:invalidateAdmins{} (trigger stats refresh)

9. File Serving Routes

/f/:shortId — File Viewer

- embedMode = 'embed': OG HTML with redirect

- embedMode = 'raw' + image/video/audio: HTTP 302 → /f/:shortId/raw

/f/:shortId/raw — Direct Access

Serves the file as an HTTP response with range request support (206 Partial Content). Images/videos/audio: Content-Disposition: inline, others: attachment.

/f/:shortId/download — Force Download

Like /raw, but always Content-Disposition: attachment.

/f/:shortId/thumb — Thumbnail

Returns JPEG thumbnail (for videos and PDFs). Cache-Control: public, max-age=86400.

/f/:shortId/delete/:token — ShareX Deletion

Deletes file without session via unique delete token.

Checks password (via session flag), expiry date and download limit, then serves the file.


10. Authentication & Security

Session Authentication

API Key Authentication

Middleware Chain (src/middleware/auth.js)

requireLogin    → checks req.session.user → 401 if not logged in
requireAdmin    → like requireLogin, additionally role === 'admin' → 403
requireApiKey   → checks Authorization header or req.body.token

CSRF Protection

requireSameOrigin() in app.js compares the Origin header with the Host header for all API routes. Complements sameSite: 'strict' cookies.

Content Security Policy

default-src 'self';
script-src 'self' 'unsafe-inline' https://static.cloudflareinsights.com;
style-src 'self' 'unsafe-inline';
img-src 'self' data: blob:;
media-src 'self' blob:;
connect-src 'self' https://cloudflareinsights.com;
frame-ancestors 'self';

Security Headers

Rate Limiting

EndpointLimitWindow
Upload (/upload, /api/upload, /api/web-upload)60 requests15 minutes
Auth (/api/auth/login, /register, etc.)10 requests15 minutes
Password reset5 requests1 hour

File Blocklist

The following MIME types and extensions are rejected on upload:

Blocked MIME types: application/x-executable, application/x-sh, application/x-csh, application/x-bat

Blocked extensions: .bat, .cmd, .com, .ps1, .psm1, .psd1, .sh, .bash, .csh, .zsh, .fish, .vbs, .vbe, .jse, .scr, .pif, .application, .gadget, .hta, .php, .php3–5, .phtml, .asp, .aspx, .jsp, .jspx, .cfm

Path Traversal Protection

All file accesses via resolveUploadPath() verify that the resolved path lies within UPLOAD_DIR.


11. Upload System

Standard Upload (Multer)

Chunked Upload (>250 MB)

The frontend client automatically switches to chunked mode for large files.

Server-side directory structure:

uploads/.chunks/{uploadId}/
  meta.json          { filename, mimeType, totalSize, totalChunks, userId, createdAt }
  chunk-0
  chunk-1
  ...
  chunk-N

Chunk size: 10–20 MB (max 51 MB accepted for backward compatibility)

Parallelism: 3–5 concurrent chunk uploads

Assembly: Stream-based (no full loading into RAM)

Avatar Upload


12. Thumbnail Generation

Thumbnails are generated asynchronously after upload (.catch(() => {}) — errors are silently ignored).

Video Thumbnails (ffmpeg)

ffmpeg -y -i <file> -ss 00:00:01 -vframes 1 \
  -vf "scale=320:320:force_original_aspect_ratio=increase,crop=320:320" \
  -q:v 3 uploads/.thumbnails/<shortId>.jpg

PDF Thumbnails (Ghostscript)

gs -dNOPAUSE -dBATCH -dSAFER \
  -sDEVICE=jpeg -dFirstPage=1 -dLastPage=1 \
  -r72 -dJPEGQ=85 \
  -sOutputFile=uploads/.thumbnails/<shortId>.jpg \
  <file>

Timeout: 30 seconds per thumbnail generation

Fallback: If ffmpeg/ghostscript is not available, generation is silently skipped.

Backfill Script

npm run migrate:thumbnails
# or in container:
docker exec -it <container> npm run migrate:thumbnails

13. Email & SMTP

Configuration

SMTP is activated when SMTP_HOST is set. mailer.isConfigured() checks this value.

Email Templates

All emails are sent in the user's language (8 languages). Templates are embedded in src/utils/mailer.js.

Email types sent:

TypeTriggerToken Validity
Email verificationRegistration, email change24 hours
Password resetPOST /api/auth/forgot-password1 hour

Token Security


14. Internationalization

Library: i18next + react-i18next + i18next-browser-languagedetector

Supported Languages:

CodeLanguage
enEnglish
deDeutsch
frFrançais
esEspañol
itItaliano
ptPortuguês
ja日本語
zh中文

Language Selection:

1. Browser language detection (automatic)

2. User preference in the database (user.language)

3. Persistence via PATCH /api/user/language

Translation files: client/src/i18n/locales/{code}.json


15. Frontend (React SPA)

Router Configuration (client/src/App.jsx)

RouteComponentAuth
/Redirect → /galleryNo
/auth/loginLogin.jsxNo
/auth/registerRegister.jsxNo
/auth/forgot-passwordForgotPassword.jsxNo
/auth/reset-passwordResetPassword.jsxNo
/installInstall.jsxNo
/uploadUpload.jsxYes
/galleryGallery.jsxYes
/f/:shortIdFileView.jsxYes
/collectionsCollections.jsxYes
/c/:idCollectionView.jsxNo (public)
/s/:tokenShareView.jsxNo (public)
/settingsSettings.jsxYes
/adminDashboard.jsxAdmin
/admin/usersUsers.jsxAdmin
/admin/filesFiles.jsxAdmin
/admin/audit-logAuditLog.jsxAdmin
/admin/site-settingsSiteSettings.jsxAdmin
/admin/importImport.jsxAdmin
/privacyPrivacyPolicy.jsxNo
/termsTermsOfService.jsxNo

Auth Context (client/src/context/AuthContext.jsx)

Global state for the logged-in user. Initialized at app start via GET /api/auth/me.

WebSocket Hook (client/src/hooks/useWebSocket.js)

Manages the persistent WS connection. Provides sendMessage() and event handler registration. Reconnect logic on connection drop.

UI Components

Based on shadcn/ui (Radix UI Primitives + Tailwind CSS):


16. Admin Dashboard

The admin dashboard (/admin/*) is accessible only to users with role: 'admin'.

Dashboard (/admin)

User Management (/admin/users)

File Management (/admin/files)

Audit Log (/admin/audit-log)

Site Settings (/admin/site-settings)

XBackBone Import (/admin/import)


17. GDPR / Privacy

FeatureGDPR Article
Privacy policy (configurable)Art. 13/14 – Transparency
Terms of service page (configurable)Art. 13/14 – Transparency
Data export (JSON with URLs)Art. 20 – Data portability
Account self-deletion (files + data)Art. 17 – Right to erasure
Audit log (90-day TTL via MongoDB)Art. 5(2) – Accountability
Audit log CSV exportArt. 5(2) – Accountability
Configurable file retentionArt. 5(1)(e) – Storage limitation
API keys as SHA-256 hashArt. 32 – Security
Passwords as bcrypt (12 rounds)Art. 32 – Security
Cookie consent for Cloudflare AnalyticsArt. 13 – Transparency
Anonymization on account deletionArt. 17 – Right to erasure

GDPR Deletion Flow

On account deletion (user:delete-account / DELETE /api/user/account):

1. All user files are deleted from disk

2. Thumbnails are deleted

3. Avatar is deleted

4. Audit log entries are anonymized (username: '[deleted]', ip: null, userId: null)

5. User document is deleted

6. Session is destroyed


18. Background Jobs

Retention Cleanup (src/jobs/retentionCleanup.js)

MongoDB TTL Indexes (automatic)

CollectionTTLTrigger
AuditLog90 daystimestamp
Collection7 days after expiresAtexpiresAt
ShareLink7 days after expiresAtexpiresAt

19. Deployment

git clone https://github.com/Christianoooooo/sharely.git
cd sharely
cp .env.example .env
# Edit .env (SESSION_SECRET, MONGO passwords, BASE_URL)
docker compose up -d

Services:

Volumes:

Health checks: App checks HTTP 200 on /, MongoDB checks db.adminCommand('ping').

Nginx Reverse Proxy

server {
    listen 443 ssl;
    server_name files.example.com;

    client_max_body_size 2100M;  # At least as large as the biggest chunk + buffer

    location / {
        proxy_pass http://localhost:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        # WebSocket support
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }
}
BASE_URL in .env must match the public domain.

First Start

The first registered user automatically receives the admin role (User.countDocuments() === 0).


20. Development Environment

Prerequisites

Setup

# Backend
npm install
cp .env.example .env
# Edit .env

# Frontend
cd client
npm install

Starting

# Backend (port 3000, with nodemon)
npm run dev

# Frontend (port 5173, separate terminal)
cd client
npm run dev

The Vite dev server automatically proxies API requests to localhost:3000.

Build

npm run build   # Builds client/dist/, backend remains unchanged
npm start       # Starts the production Express server

21. Migrations & Scripts

Automatic Migrations (on every start)

These migrations run at app startup in app.js and are idempotent:

MigrationFileFunction
User folder migrationsrc/migrations/migrateUserFolders.jsMoves files from uploads/ into uploads/{folderName}/
API key hash migrationsrc/migrations/migrateApiKeyHashes.jsConverts plaintext API keys to SHA-256 hashes

Manual Scripts

# Generate thumbnails for already existing files
npm run migrate:thumbnails
# or:
node scripts/generate-missing-thumbnails.js

# Move uploads to user folders (manual)
npm run migrate:user-folders
# or:
node scripts/migrate-uploads-to-user-folders.js

Database Initialization (Docker)

scripts/mongo-init.js is executed on the first start of the MongoDB container and creates the app user with the correct permissions.


22. End-to-End Tests

Framework: Playwright (@playwright/test)

Test Files

FileTest Suite
e2e/upload.spec.jsUpload flows
e2e/gallery.spec.jsGallery and file management
e2e/admin.spec.jsAdmin dashboard
e2e/sharelink.spec.jsShare link creation and usage
e2e/tags.spec.jsTag management
e2e/bulk-actions-fixes.spec.jsBulk actions

Running Tests

# All tests
npm run test:e2e

# With UI
npm run test:e2e:ui

Playwright configuration: playwright.config.js

Global setup: e2e/global-setup.js (creates test users, admin, etc.)

Helpers: e2e/helpers.js (shared helper functions)


Appendix: Audit Log Actions

ActionTrigger
loginSuccessful login
logoutLogout
registerRegistration
uploadFile upload
delete_fileFile deleted
delete_accountAccount deleted
change_passwordPassword changed
change_usernameUsername changed
change_emailEmail changed
verify_emailEmail verified
forgot_passwordPassword reset requested
reset_passwordPassword reset
regen_api_keyAPI key regenerated
sharex_configShareX configuration downloaded
export_dataData export
admin_create_userAdmin: user created
admin_delete_userAdmin: user deleted
admin_toggle_userAdmin: user activated/deactivated
admin_change_roleAdmin: user role changed
admin_change_passwordAdmin: password set
admin_regen_keyAdmin: API key regenerated

*Documentation generated from the source code of sharely v1.0.0*