mirror of
https://github.com/J3vb/OwnCord.git
synced 2026-09-03 03:50:00 +03:00
The client held the Klipy key in VITE_KLIPY_API_KEY, which Vite inlines into
the shipped bundle by design — a build variable can never hold a secret. Move
the integration behind the server:
- New authenticated GET /api/v1/gif/search and /api/v1/gif/trending. The key
comes from the new `gif.api_key` config section (koanf,
OWNCORD_GIF_API_KEY) and never leaves the server.
- Default-off: with no key, both endpoints return 503 GIF_DISABLED so clients
can hide the picker instead of showing a broken one. Auth is checked first,
so anonymous callers cannot probe whether a key is configured.
- Outbound call reuses the existing SSRF-guarded dialer (exported as
plugin.GuardedDialContext) rather than a bare http.Get: resolve once,
reject private/loopback/link-local/CGN, dial only vetted IPs. Redirects are
not followed and the response body is size-capped.
- Only id/title/media_formats.{tinygif,gif}.url are forwarded — decoding into
the narrow struct is the allowlist, so an upstream that echoed the key
could not leak it. Upstream errors become a generic 502 and the key is
redacted from anything that reaches the logs.
- Dedicated `gif:` rate-limit bucket (30/min per IP) so debounced search
traffic cannot exhaust the shared bucket used by password/TOTP endpoints.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
151 lines
5.8 KiB
Go
151 lines
5.8 KiB
Go
package api
|
|
|
|
import (
|
|
"time"
|
|
|
|
"github.com/owncord/server/config"
|
|
)
|
|
|
|
// ─── Rate limits ────────────────────────────────────────────────────────────
|
|
//
|
|
// Each constant defines either a request cap or a sliding-window duration used
|
|
// by the per-endpoint rate limiters.
|
|
|
|
const (
|
|
// registerRateLimitPerMinute is the maximum registration attempts per IP per minute.
|
|
registerRateLimitPerMinute = 3
|
|
|
|
// loginRateLimitPerMinute is the maximum login attempts per IP per minute.
|
|
loginRateLimitPerMinute = 5
|
|
|
|
// verifyTOTPRateLimitPerMinute is the maximum TOTP verification attempts per IP per minute.
|
|
verifyTOTPRateLimitPerMinute = 10
|
|
|
|
// sensitiveEndpointRateLimitPerMinute is the rate limit applied to destructive
|
|
// or sensitive endpoints (account deletion, TOTP enable/confirm/disable).
|
|
sensitiveEndpointRateLimitPerMinute = 5
|
|
|
|
// searchRateLimitPerMinute is the maximum full-text search requests per IP per minute.
|
|
searchRateLimitPerMinute = 30
|
|
|
|
// livekitProxyRateLimitPerMinute is the maximum LiveKit proxy requests per IP per minute.
|
|
livekitProxyRateLimitPerMinute = 30
|
|
|
|
// clientUpdateRateLimitPerMinute is the maximum client-update checks per IP per minute.
|
|
clientUpdateRateLimitPerMinute = 30
|
|
|
|
// gifRateLimitPerMinute is the maximum GIF proxy requests per IP per minute.
|
|
// The picker debounces at 300ms, so a user typing continuously for a minute
|
|
// stays under this; it exists to bound abuse of the operator's Klipy quota.
|
|
gifRateLimitPerMinute = 30
|
|
|
|
// loginFailureThreshold is the number of failed login attempts (within
|
|
// loginFailureWindow) before the IP is locked out.
|
|
loginFailureThreshold = 9
|
|
|
|
// loginFailureWindow is the sliding window for counting login failures.
|
|
loginFailureWindow = 15 * time.Minute
|
|
|
|
// loginLockoutDuration is how long an IP is locked out after exceeding
|
|
// loginFailureThreshold.
|
|
loginLockoutDuration = 15 * time.Minute
|
|
|
|
// deleteAccountFailureThreshold is the number of wrong-password attempts
|
|
// before the per-user lockout kicks in.
|
|
deleteAccountFailureThreshold = 3
|
|
|
|
// deleteAccountFailureWindow is the sliding window for counting
|
|
// delete-account password failures.
|
|
deleteAccountFailureWindow = 15 * time.Minute
|
|
|
|
// deleteAccountLockoutDuration is how long the account-deletion endpoint
|
|
// is locked after exceeding deleteAccountFailureThreshold.
|
|
deleteAccountLockoutDuration = 15 * time.Minute
|
|
|
|
// totpFailureRateLimit is the maximum TOTP verification failures per user
|
|
// within totpFailureWindow before the user is rate-limited.
|
|
totpFailureRateLimit = 10
|
|
|
|
// totpFailureWindow is the sliding window for counting per-user TOTP failures.
|
|
totpFailureWindow = 15 * time.Minute
|
|
|
|
// partialAuthMaxFailures is the number of failed TOTP attempts on a single
|
|
// partial-auth challenge before it is revoked.
|
|
partialAuthMaxFailures = 5
|
|
|
|
// profilePasswordRateLimitPerMinute is the maximum password change attempts
|
|
// per IP per minute.
|
|
profilePasswordRateLimitPerMinute = 5
|
|
|
|
// profileUpdateRateLimitPerMinute is the maximum profile update attempts
|
|
// per user per minute.
|
|
profileUpdateRateLimitPerMinute = 10
|
|
|
|
// loginUserFailureThreshold is the number of failed login attempts for a
|
|
// specific username (regardless of source IP) before the account is locked.
|
|
loginUserFailureThreshold = 9
|
|
|
|
// loginUserFailureWindow is the sliding window for per-username login failures.
|
|
loginUserFailureWindow = 15 * time.Minute
|
|
|
|
// loginUserLockoutDuration is how long a username is locked after exceeding
|
|
// loginUserFailureThreshold.
|
|
loginUserLockoutDuration = 15 * time.Minute
|
|
|
|
// pwConfirmFailureThreshold is the number of wrong-password attempts on
|
|
// password-confirmation endpoints before per-user lockout kicks in.
|
|
pwConfirmFailureThreshold = 3
|
|
|
|
// pwConfirmFailureWindow is the sliding window for per-user password
|
|
// confirmation failures.
|
|
pwConfirmFailureWindow = 15 * time.Minute
|
|
|
|
// pwConfirmLockoutDuration is how long password-confirmation endpoints are
|
|
// locked after exceeding pwConfirmFailureThreshold.
|
|
pwConfirmLockoutDuration = 15 * time.Minute
|
|
|
|
// uploadRateLimitPerMinute is the maximum file uploads per user per minute.
|
|
uploadRateLimitPerMinute = 10
|
|
)
|
|
|
|
// ─── Timeouts & TTLs ────────────────────────────────────────────────────────
|
|
|
|
const (
|
|
// partialAuthStoreTTL is the lifetime of a partial-auth (2FA) challenge token.
|
|
partialAuthStoreTTL = 10 * time.Minute
|
|
|
|
// pendingTOTPStoreTTL is the lifetime of a pending TOTP enrollment secret.
|
|
pendingTOTPStoreTTL = 10 * time.Minute
|
|
|
|
// rateLimiterCleanupInterval is how often stale rate-limiter entries are reaped.
|
|
rateLimiterCleanupInterval = 5 * time.Minute
|
|
|
|
// rateLimiterCleanupMaxWindow is the maximum window considered when pruning
|
|
// stale rate-limiter entries.
|
|
rateLimiterCleanupMaxWindow = 15 * time.Minute
|
|
|
|
// hstsMaxAgeSeconds is the max-age value for the Strict-Transport-Security header.
|
|
hstsMaxAgeSeconds = 31536000
|
|
)
|
|
|
|
// ─── Size limits ────────────────────────────────────────────────────────────
|
|
|
|
const (
|
|
// defaultMaxBodySize is the default request body size limit (1 MiB).
|
|
defaultMaxBodySize = config.MaxMessageBytes
|
|
|
|
// uploadMaxBodySize is the request body size limit for file uploads (100 MiB).
|
|
uploadMaxBodySize = 100 << 20
|
|
|
|
// multipartMemoryLimit is the in-memory limit for multipart form parsing;
|
|
// data beyond this is spilled to disk.
|
|
multipartMemoryLimit = 10 << 20
|
|
|
|
// maxUploadFilenameLength is the maximum length of an upload filename
|
|
// (filesystem-safe limit).
|
|
maxUploadFilenameLength = 255
|
|
|
|
// maxAvatarURLLen is the maximum length of a user avatar URL.
|
|
maxAvatarURLLen = 512
|
|
)
|