mirror of
https://github.com/J3vb/OwnCord.git
synced 2026-09-03 03:50:00 +03:00
Merge pull request #1198 from J3vb/feat/gif-server-proxy
feat(gif): move Klipy integration behind a server proxy so no API key ships in the client bundle
This commit is contained in:
+70
-1
@@ -48,7 +48,8 @@ All error responses use this JSON envelope:
|
||||
| `CONFLICT` | 409 | Duplicate username on register, or server already up-to-date on update |
|
||||
| `TOO_LARGE` | 413 | File exceeds upload size limit |
|
||||
| `SERVER_ERROR` / `INTERNAL` | 500 | Internal server error |
|
||||
| `BAD_GATEWAY` | 502 | Upstream failure (GitHub API, LiveKit, asset download) |
|
||||
| `BAD_GATEWAY` | 502 | Upstream failure (GitHub API, LiveKit, GIF provider, asset download) |
|
||||
| `GIF_DISABLED` | 503 | GIF proxy is not configured on this server (no `gif.api_key`) |
|
||||
|
||||
---
|
||||
|
||||
@@ -621,6 +622,74 @@ Full-text search across messages in channels the user can read. Uses SQLite FTS5
|
||||
|
||||
---
|
||||
|
||||
## GIFs
|
||||
|
||||
The server proxies the Klipy GIF API so the provider API key stays server-side.
|
||||
Clients never contact `api.klipy.com` — a key shipped in the desktop bundle
|
||||
would be public by construction. The key is configured as `gif.api_key`
|
||||
(see [Server Configuration](server-configuration.md#gif-picker-gif)).
|
||||
|
||||
**Default-off contract:** with no key configured, both endpoints return
|
||||
`503` with error code `GIF_DISABLED`. Clients must treat that as "this server
|
||||
does not have GIFs" and hide/disable the GIF affordance — not retry.
|
||||
|
||||
The media URLs in the response point at Klipy's CDN; the client still validates
|
||||
them against its `klipy.com` CDN allowlist before rendering.
|
||||
|
||||
### GET /api/v1/gif/search
|
||||
|
||||
**Auth:** Required
|
||||
**Rate limit:** 30 requests/minute (dedicated per-IP bucket)
|
||||
|
||||
#### Query Parameters
|
||||
|
||||
| Param | Type | Default | Range | Description |
|
||||
| ----- | ---- | ------- | ----- | ----------- |
|
||||
| `q` | string | (required) | 1-100 chars | Search term |
|
||||
| `limit` | int | 20 | 1-50 | Maximum results to return |
|
||||
|
||||
#### Response 200 OK
|
||||
|
||||
```json
|
||||
{
|
||||
"results": [
|
||||
{
|
||||
"id": "abc123",
|
||||
"title": "happy cat",
|
||||
"media_formats": {
|
||||
"tinygif": { "url": "https://media.klipy.com/abc123_tiny.gif" },
|
||||
"gif": { "url": "https://media.klipy.com/abc123.gif" }
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Only `id`, `title`, and the two `media_formats` URLs are forwarded. Every other
|
||||
field the upstream returns is dropped, so an upstream that echoed the API key
|
||||
could not leak it to clients. Results missing either format are omitted.
|
||||
|
||||
#### Errors
|
||||
|
||||
| Status | Code | When |
|
||||
| ------ | ---- | ---- |
|
||||
| 400 | `INVALID_INPUT` | Missing/blank `q`, `q` over 100 chars, or `limit` outside 1-50 |
|
||||
| 401 | `UNAUTHORIZED` | No valid session (checked before the disabled check) |
|
||||
| 429 | `RATE_LIMITED` | Over 30 requests/minute |
|
||||
| 502 | `BAD_GATEWAY` | Upstream error, timeout, or unparseable response |
|
||||
| 503 | `GIF_DISABLED` | `gif.api_key` is not configured |
|
||||
|
||||
### GET /api/v1/gif/trending
|
||||
|
||||
Same auth, rate limit, response shape, and error codes as
|
||||
`/api/v1/gif/search`, minus the `q` parameter.
|
||||
|
||||
| Param | Type | Default | Range | Description |
|
||||
| ----- | ---- | ------- | ----- | ----------- |
|
||||
| `limit` | int | 20 | 1-50 | Maximum results to return |
|
||||
|
||||
---
|
||||
|
||||
## Direct Messages
|
||||
|
||||
DM channels use participant-based authorization rather than role-based permissions.
|
||||
|
||||
@@ -99,6 +99,7 @@ other (auth→voice→members), and the Solid beachhead is dead weight.
|
||||
| Updates | `src/lib/updater.ts` + `update_commands.rs` | Endpoint derived from the connected server URL, https-only, TLS pinned to TOFU fingerprint, minisign-verified |
|
||||
| Settings | `commands.rs` + `src/lib/preferences.ts` | Split persistence: Rust store (`settings.json`, key-allowlisted) *and* raw `localStorage` for UI prefs/themes |
|
||||
| Theming | `src/lib/themes.ts` + `styles/tokens.css` | CSS custom properties; 4 built-in themes + custom overrides |
|
||||
| GIF picker | `src/lib/gifProvider.ts` + `components/GifPicker.ts` | Calls the user's own server (`/api/v1/gif/*`) through `api.ts` — no provider API key in the bundle. Server answers `503 GIF_DISABLED` when unconfigured: the picker shows "GIFs are not enabled on this server" and `onUnavailable` disables the composer's GIF button (with a `title`/`aria-label` reason) instead of failing silently. Returned media URLs are still pinned to the `klipy.com` CDN. |
|
||||
|
||||
### Quality tooling
|
||||
|
||||
|
||||
+1
-1
@@ -75,7 +75,7 @@ The Tauri desktop client implements the following security measures:
|
||||
- URLs are validated via `isSafeUrl` (rejects `javascript:`, `data:`, `vbscript:`)
|
||||
- YouTube embeds use `sandbox` attribute on iframes
|
||||
- `image/svg+xml` is excluded from safe MIME types for data URIs
|
||||
- Tenor GIF URLs are validated against trusted CDN origins
|
||||
- GIF media URLs are validated against the trusted Klipy CDN origins
|
||||
- Linkified URLs strip trailing punctuation to prevent misleading destinations
|
||||
|
||||
### Search and Rate Limiting
|
||||
|
||||
@@ -114,6 +114,23 @@ Controls the Wazero WASM plugin runtime. Requires building with `-tags wazero`.
|
||||
| `plugins.cpu_budget_ms` | int | `100` | Maximum CPU time per plugin invocation (milliseconds) |
|
||||
| `plugins.http_allowlist` | string[] | `[]` | Host suffixes plugins may reach via the `host_http` capability (e.g. `["api.steampowered.com"]`). Empty = no outbound HTTP. |
|
||||
|
||||
### GIF Picker (`gif`)
|
||||
|
||||
Powers the client's GIF picker. The server proxies the Klipy API so the key
|
||||
never ships in the desktop bundle — the client only ever calls
|
||||
`/api/v1/gif/*` on its own server.
|
||||
|
||||
**Disabled by default.** With no `gif.api_key` set, `/api/v1/gif/*` returns
|
||||
`503 GIF_DISABLED` and clients hide their GIF button. Nothing else changes.
|
||||
|
||||
| Key | Type | Default | Description |
|
||||
|-----|------|---------|-------------|
|
||||
| `gif.api_key` | string | `""` | Klipy API key. Get one at [partner.klipy.com](https://partner.klipy.com). Empty = feature off. |
|
||||
|
||||
> **Treat this as a credential.** Prefer `OWNCORD_GIF_API_KEY` (or a secrets
|
||||
> manager) over writing it into `config.yaml`, and rotate it if it has ever
|
||||
> been exposed to a client build.
|
||||
|
||||
## Environment Variable Overrides
|
||||
|
||||
Every config key can be overridden via environment variables using the prefix `OWNCORD_`.
|
||||
@@ -146,6 +163,7 @@ Every config key can be overridden via environment variables using the prefix `O
|
||||
| `OWNCORD_TELEMETRY_SERVICE_NAME` | `telemetry.service_name` |
|
||||
| `OWNCORD_PLUGINS_ENABLED` | `plugins.enabled` |
|
||||
| `OWNCORD_PLUGINS_DIRECTORY` | `plugins.directory` |
|
||||
| `OWNCORD_GIF_API_KEY` | `gif.api_key` |
|
||||
|
||||
## Example config.yaml
|
||||
|
||||
@@ -214,6 +232,11 @@ plugins:
|
||||
max_memory_mb: 64
|
||||
cpu_budget_ms: 100
|
||||
http_allowlist: [] # host suffixes plugins may reach, e.g. ["api.steampowered.com"]
|
||||
|
||||
# GIF picker (server-side Klipy proxy). Empty key = feature off.
|
||||
# Prefer OWNCORD_GIF_API_KEY over storing the key in this file.
|
||||
gif:
|
||||
api_key: ""
|
||||
```
|
||||
|
||||
## See Also
|
||||
|
||||
Reference in New Issue
Block a user