- Go 54.1%
- JavaScript 29.9%
- HTML 15.2%
- Dockerfile 0.8%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
|
|
||
| .forgejo/workflows | ||
| cmd/libreshare | ||
| .dockerignore | ||
| .env.example | ||
| .gitignore | ||
| Dockerfile | ||
| go.mod | ||
| go.sum | ||
| LICENSE | ||
| README.md | ||
LibreShare
LibreShare is a small, self-hosted temporary file-sharing service. A Go server handles Authelia/OIDC login, quotas, metadata, expiry, and byte streaming. The browser handles optional password encryption so the server never receives the password, encryption key, or plaintext of a protected file.
The frontend is kept in normal, independently editable files under cmd/libreshare/web: HTML templates in templates/, and CSS/JavaScript in static/. Go's embed package includes them in the compiled binary, so production still only needs the one executable.
Run it
Requirements: Go 1.26+ and an OIDC client in Authelia.
cp .env.example .env
# edit .env, then:
go run ./cmd/libreshare
In Authelia, register a confidential OIDC client whose redirect URI exactly matches OIDC_REDIRECT_URL (for local development, http://localhost:8080/callback). Use authorization_code, scopes openid profile email, and put the same client ID and secret in .env.
Development login
For local testing without Authelia, set:
LIBRESHARE_MODE=dev
OIDC settings may be empty in this mode. The homepage asks for a username and creates an ordinary signed LibreShare session for dev:<lowercase username>, so the upload, ownership, quota, link, and download paths behave normally. Identity is deliberately honour-based: anyone can enter anyone else's username and see that identity's files. Never expose dev mode to an untrusted network or use it in production. production is the default mode and requires the OIDC settings.
Production must use HTTPS. Put LibreShare behind an ingress/reverse proxy, set BASE_URL and OIDC_REDIRECT_URL to their public HTTPS values, and set COOKIE_SECURE=true. Persist DATA_DIR; losing metadata.json loses ownership/link information, while losing blobs/ loses the files.
How it works
Login and sessions
GET /login generates a random OIDC state value, stores an HMAC-signed copy in an HttpOnly cookie, and redirects to Authelia. Authelia sends the browser to /callback; the server checks the state, exchanges the authorization code, verifies the ID token signature/issuer/audience using provider discovery, and stores the useful claims in another HMAC-signed HttpOnly cookie. The OIDC client secret is only used by Go.
Unauthenticated visitors cannot list or upload files. A random download URL is a bearer credential: anyone holding it can download that one file until it expires. Only a SHA-256 hash of that token is retained in metadata, so leaking metadata.json does not reveal working links.
Uploads, expiry, and credits
The browser sends the original byte size and requested duration. The backend independently checks:
file credits = original bytes / 1 GiB × requested hours
active usage = sum(original GiB × hours remaining) for the user's live files
The new file is accepted only if active usage plus its credits is at most MAX_CREDITS. Usage naturally falls with time. With 1,680 credits, 10 GiB for 168 hours costs 1,680. MAX_FILE_GIB and MAX_RETENTION_HOURS are separate hard ceilings. The original size, rather than ciphertext overhead, is charged.
The server streams request bodies to DATA_DIR/blobs through a bounded reader and rejects incorrect lengths. An hourly cleanup removes expired metadata and blobs. Download checks expiry on every request, so a file becomes inaccessible on time even before physical cleanup runs.
This initial store is deliberately simple: one JSON metadata file plus local blobs. Run exactly one application replica. For multiple replicas or stronger durability, move metadata to PostgreSQL and blobs to S3-compatible object storage; the current local-file operations are not a distributed storage layer.
Password protection / E2EE
Protected uploads are split into 4 MiB chunks in the browser. A key is derived from the password with PBKDF2-SHA-256 (310,000 iterations and a random 128-bit salt), and each chunk is encrypted with AES-256-GCM and a unique counter nonce. Only ciphertext, salt, chunk size, and original size reach Go. On download, JavaScript fetches the ciphertext and decrypts locally. A wrong password fails GCM authentication.
Important consequences:
- Password protection can only be chosen before upload. The server cannot convert an existing plaintext upload without seeing plaintext.
- A lost password cannot be reset by the hoster.
- The person downloading must trust the JavaScript served at that moment. A malicious/compromised host could alter future JavaScript to steal passwords. Stronger protection requires a separately installed/auditable client, not a web page served by the host.
- Current browsers with Origin Private File System support stage encrypted uploads and decrypted downloads in browser-managed temporary disk storage. Cryptographic work is chunked, so RAM use stays bounded instead of scaling with file size. The device needs roughly the file size in free temporary storage, in addition to space for the final downloaded file. Temporary files are removed after transfer.
- Browsers without disk-backed temporary storage fall back to memory only for files up to 256 MiB and clearly refuse larger E2EE transfers. Very large transfers remain subject to browser storage quotas; a native CLI would provide more predictable handling at hundreds-of-GiB scale.
Unprotected files are plaintext by design and readable by the host.
Configuration
All settings are documented in .env.example. .env is loaded on startup but real environment variables win, which works naturally with Kubernetes Secrets and ConfigMaps. Limits are global per authenticated OIDC subject.
Bandwidth limiting
Use both layers, for different jobs:
- Put the primary per-connection/per-IP download rate and request/concurrency limits at the ingress or reverse proxy. It is more efficient than waking the Go application for abusive traffic and applies uniformly. NGINX Ingress can use its rate/connection annotations; other controllers have equivalents.
- Use Kubernetes resource requests/limits for CPU and memory, but do not treat them as a precise network-bandwidth quota. Kubernetes itself has no universally reliable core
bandwidth limitcontrol; behavior depends on CNI/traffic shaping. - Add application-level accounting only if you need user-aware policy such as “this OIDC user may transfer 50 GiB/day.” The ingress cannot reliably understand LibreShare ownership and credits.
For this app, start with ingress per-IP connection/rate limits plus MAX_UPLOAD_CONCURRENCY, monitor real traffic, then add Go-level user transfer quotas only if needed. Avoid an extremely low response timeout at the ingress: legitimate large downloads must remain open.
API map
GET /,/login,/callback,POST /logout: UI and authenticationGET/POST /api/files: list and upload (authenticated)DELETE /api/files/{id}: delete owned filePOST /api/files/{id}/links: mint a new bearer linkGET /d/{token}: public download pageGET /d/{token}/metaand/content: public metadata and bytes
Verify
go test ./...
go vet ./...
The most useful next production upgrades are PostgreSQL/object storage, explicit CSRF tokens, upload resumability, link revocation, and a native E2EE client for exceptionally large transfers.