File Service
AuthProxy provides file upload and download transport for Chat attachments. AuthProxy stores bytes and enforces transport-level limits; Chat owns file metadata, access checks, and attachment lifecycle.
There are two upload flows:
auth/v1/file/*— buffered chunk upload used by the current Chat PWA.auth/v2/file/*— draft + streaming upload, plus code-based downloads for anonymous or public-link scenarios.
Both flows support two storage backends:
- local disk via
LocalFileStoragePath - external HTTP file servers via
FileServers
v1 Chunked Upload
The v1 flow buffers chunks in AuthProxy memory, validates CRC32 per chunk, validates the full file CRC32 after all chunks arrive, creates a pending Chat file message, writes bytes to storage, and then completes the pending Chat message. There is no separate complete_upload endpoint.
Initialize Upload
Start a new upload session. The returned result is the numeric upload ID. In the current implementation it is the declared full-file CRC32 value and is passed back as uploadId for chunk calls.
POST /auth/v1/file/init_upload
Content-Type: application/json
Cookie: sid=...
{
"FileName": "document.pdf",
"FileSize": 1048576,
"CRC32": 305419896,
"ChatId": "638412345678901234",
"Message": "optional message text",
"LinkedMessageId": "0"
}
Response
{
"result": 305419896
}
ChatId and Personal Storage fallback
Every uploaded file becomes a Chat message (chat_mess), so each upload must reference a
real chat where the caller is a member.
ChatId > 0— a normal conversation. The caller must be a member; Chat enforces this.ChatId <= 0(or omitted) — a "no-conversation" file such as an OAuth app logo, a user avatar, etc. AuthProxy lazily resolves a per-user Personal Storage chat (get_or_create_personal_storage_chat) and substitutes its timetick before sending the message. This applies to bothinit_upload(v1) andcreate_draft(v2). On a resolve failure the upload returns a gateway/file error instead of an opaqueChatNotFound.
This removes the need for clients to invent a fake chatId for sessionless attachments.
Upload Part
Upload one binary chunk. Parameters are query-string values; the request body is raw application/octet-stream, not multipart form data.
POST /auth/v1/file/upload_part?uploadId=305419896&chunkNumber=0&chunkCrc32=2598427311
Content-Type: application/octet-stream
Cookie: sid=...
[binary chunk bytes]
Chunks are zero-based. The fixed chunk size is 256 KB except for the final chunk.
For non-final chunks AuthProxy returns the accepted chunk number:
{
"result": {
"chunkNumber": 0,
"fileId": null,
"fileCode": null
}
}
When the last missing chunk is accepted, AuthProxy finalizes the upload and returns the canonical file ID. For Chat attachments, fileId is the Chat message timetick.
{
"result": {
"chunkNumber": 3,
"fileId": "638412345678901234",
"fileCode": "encoded-file-code"
}
}
Cancel Upload
Cancel an active buffered upload session and release the rented memory buffer.
POST /auth/v1/file/cancel_upload?uploadId=305419896
Cookie: sid=...
v2 Streaming Upload
The v2 flow creates a pending Chat file message first, then streams the request body directly to local disk or the configured HTTP file server. It is intended for clients that do not want AuthProxy to buffer the whole file in memory.
The actual request body is capped by the dynamic FileMaxSizeBytes setting. AuthProxy rejects oversized streaming uploads with HTTP 413 Payload Too Large.
Create Draft
POST /auth/v2/file/create_draft?fileName=document.pdf&fileSize=1048576&chatId=638412345678901234&message=optional%20text&linkedMessageId=0&crc32=305419896
Cookie: sid=...
Response
{
"result": "638412345678901234:encoded-file-code"
}
The value before : is fileId; the value after : is the code that can be used with /auth/v2/file/{id_or_code}.
Stream Upload
POST /auth/v2/file/stream_upload/638412345678901234
Content-Type: application/octet-stream
Range: bytes=0-
Cookie: sid=...
[binary file bytes]
On success the endpoint completes the pending Chat message and returns HTTP 200 with an empty body. If the upload fails or the client disconnects, AuthProxy attempts to delete the stored bytes and the pending Chat message.
Download API
v1 Session Download
GET /auth/v1/file/{fileId}
Cookie: sid=...
The v1 download endpoint always requires a valid session and validates access through Chat file metadata before serving bytes.
Optional chunk download:
GET /auth/v1/file/{fileId}?part_number=0
Cookie: sid=...
part_number uses 256 KB chunks and returns these headers:
X-Chunk-CRC32— full-file CRC32 from Chat metadataX-File-Part—whole-fileorpart-N/totalCache-Control— public immutable-style browser cache hint
HEAD /auth/v1/file/{fileId} is also supported.
v2 Streaming Download
GET /auth/v2/file/{id_or_code}
id_or_code can be:
- an obfuscated file code, always accepted by the route
- a raw numeric file ID, always accepted for authenticated users and accepted anonymously only when
FileDownloadByIdEnabledis true
By default code-based v2 downloads do not require a session. Set FileDownloadAuthRequired to require authentication for every v2 download. Authenticated raw-ID downloads always validate access through Chat metadata with the current user's CRM ID.
The v2 download endpoint supports:
HEADRange/206 Partial ContentAccept-Ranges: bytesETagandIf-None-MatchIf-Match- bandwidth limiting through AuthProxy runtime limits
Storage Backend
Recommended for Single-Proxy Deployments
For a mono-proxy or other single-instance deployment, use local disk storage:
{
"LocalFileStoragePath": "/var/lib/authproxy/files"
}
AuthProxy stores file bytes under:
{LocalFileStoragePath}/{owner}/{fileId}
Notes:
- The directory must be writable by the AuthProxy process.
- This is deploy-time configuration in
appsettings.jsonor environment variables. - This is not managed through ProxyAdmin.
External HTTP Storage Backend
If you need storage outside the AuthProxy process, configure HTTP file servers:
{
"FileServers": {
"0": {
"Host": "http://files:80"
}
}
}
FileServers uses numeric thresholds as keys. AuthProxy selects the first configured backend whose threshold is lower than the generated fileId.
Use this mode when you intentionally want file bytes outside the local proxy instance. For single-proxy deployments, LocalFileStoragePath is usually simpler and is the preferred setup.
Dynamic Settings
These values are runtime settings in ProxyAdmin → Settings, not static appsettings.json fields.
| Setting type | Default | Effect |
|---|---|---|
FileMaxSizeBytes | 20971520 | Project-level upload size cap. Values are clamped by the AuthProxy hard cap of 100 MiB. |
FileBlockedExtensions | empty | Semicolon-separated upload extension denylist in setting_str, for example exe;bat;cmd;ps1. |
FileDownloadBytesPerSecond | 5242880 | Shared v2 download quota in bytes per second (setting_value). Set it to 0 to disable byte throttling. |
FileUploadBytesPerSecond | 2097152 | Shared v2 upload quota in bytes per second (setting_value). Set it to 0 to disable byte throttling. |
FileDownloadByIdEnabled | 0 | Allows anonymous raw numeric IDs on /auth/v2/file/{id}. Authenticated raw-ID downloads and code-based v2 downloads are still available when this is false. |
FileDownloadAuthRequired | 0 | Requires a browser session for every v2 download, including code-based links and anonymous raw-ID links. |
Static storage settings remain in deploy-time configuration:
| Setting | Default | Effect |
|---|---|---|
LocalFileStoragePath | not set | If set, AuthProxy stores bytes under {LocalFileStoragePath}/{owner}/{fileId} instead of using HTTP file servers. |
FileServers | not set | HTTP storage backend map. Keys are numeric thresholds; AuthProxy selects the first configured backend whose threshold is lower than fileId. |
Integrity Verification
| Level | Check | When |
|---|---|---|
| v1 per-chunk | CRC32 | Each upload_part call |
| v1 full file | CRC32 | After the last missing chunk is accepted |
| v2 metadata | Optional CRC32 string | Stored on the pending Chat message when supplied to create_draft |
For v1, CRC32 checksums are computed client-side and verified server-side. Any mismatch rejects the call.
Memory Management
The v1 buffered flow uses ArrayPool<byte> for upload buffers:
- Buffers are rented from the pool during upload
- Returned to the pool after the file is written to the selected storage backend
- Maximum 60 concurrent upload sessions prevent memory exhaustion
- Each user can have only one active v1 upload session
- Inactive upload sessions expire quickly and are cleaned up before new uploads are accepted
The v2 streaming upload flow does not buffer the whole file in AuthProxy memory.
Download Limits
Keep v1 chunked behavior in place. The v1 download path is useful for compatibility and small files, but it is guarded mostly by request/session limits and does not share a bandwidth quota between clients. Turning v1 into a large unthrottled single-response path can let one large file transfer consume proxy memory/IO.
Prefer v2 for new preview and download flows. The v2 stream path uses the AuthProxy bandwidth limiter with dynamic FileDownloadBytesPerSecond and FileUploadBytesPerSecond settings, so configured capacity is shared across clients and a single transfer cannot monopolize the service.
Integration with Chat Module
File uploads are integrated with the Chat module for message attachments:
- AuthProxy creates a pending Chat message with file metadata.
- AuthProxy writes bytes to local disk or the configured file server.
- AuthProxy marks the Chat file message as complete.
- Clients download the attachment through AuthProxy using the Chat message timetick as
fileId, or a v2 file code where applicable.
File metadata (name, size, hash) is stored in the Chat module's message records.
Even when LocalFileStoragePath is enabled, AuthProxy still uses the Chat module for file metadata and attachment lifecycle operations.
Error Codes
| Code | Constant | Description |
|---|---|---|
| 1101 | SessionNotFound | No valid session was found where the endpoint requires one. |
| 1301 | MaxFileSize | File size is zero or exceeds the 20 MB limit. |
| 1302 | AlreadyDownload | The current user already has an active v1 upload session. |
| 1303 | TooManyDownloads | The server already has 60 active v1 upload sessions. |
| 1304 | CRC32AlreadyUsed | The declared CRC32 is already used by an active v1 upload session. |
| 1305 | InvalidID | Upload ID is invalid or no longer active. |
| 1306 | InvalidPart | Chunk number is outside the expected range. |
| 1307 | WrongPart | Uploaded chunk body is larger than the expected chunk size. |
| 1308 | ChecksumError | Chunk CRC32 does not match. |
| 1309 | InvalidFileName | File name failed validation. |
| 1310 | ChartApiFileError | Chat service failed to create or complete the file message. |