feat: security hardening, DB indexes, token revocation, and input validation
- SSRF protection: DNS resolution + private IP blocking on metadata fetcher - Upload security: CSP/X-Frame-Options headers, SVG forced download, nosniff - Auth hardening: JWT secret min length, password min 8 chars, token revocation via password_changed_at - Attachment ownership verification before linking to messages - Message length limit (4000 chars) enforced on client and server - Asset URL validation on avatar/banner updates - Federation instance validation (domain regex, origin scheme, length limits) - DB indexes on all FK columns for query performance - Migrations: nullable moderator columns, dm_messages reply_to FK constraint - File cleanup on avatar/banner replacement and space deletion - Fastify trustProxy, AbortController on fetches, typing map size cap
This commit is contained in:
@@ -5,7 +5,7 @@ import { wsSend } from '../../hooks/useWebSocket';
|
||||
import { MentionPopover } from './MentionPopover';
|
||||
import { TypingIndicator } from './TypingIndicator';
|
||||
import { hasPermissionBit, PermissionBits } from '../../utils/permissions';
|
||||
import type { MemberWithUser } from '@backspace/shared';
|
||||
import { MAX_MESSAGE_LENGTH, type MemberWithUser } from '@backspace/shared';
|
||||
|
||||
interface MessageInputProps {
|
||||
channelId: string;
|
||||
@@ -76,9 +76,13 @@ export function MessageInput({ channelId, channelName }: MessageInputProps) {
|
||||
}, 3000);
|
||||
}, [channelId]);
|
||||
|
||||
const remaining = MAX_MESSAGE_LENGTH - content.length;
|
||||
const isOverLimit = remaining < 0;
|
||||
|
||||
const handleSubmit = async () => {
|
||||
const trimmed = content.trim();
|
||||
if (!trimmed && files.length === 0) return;
|
||||
if (isOverLimit) return;
|
||||
|
||||
setIsUploading(true);
|
||||
setMentionState(null);
|
||||
@@ -364,6 +368,13 @@ export function MessageInput({ channelId, channelName }: MessageInputProps) {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Character counter (shows when near or over limit) */}
|
||||
{content.length > MAX_MESSAGE_LENGTH - 200 && (
|
||||
<span className={`text-[12px] font-medium tabular-nums flex-shrink-0 px-1 ${isOverLimit ? 'text-accent-rose' : 'text-txt-tertiary'}`}>
|
||||
{remaining}
|
||||
</span>
|
||||
)}
|
||||
|
||||
{/* GIF button */}
|
||||
<button className="w-[34px] h-[34px] flex items-center justify-center rounded-[6px] text-txt-tertiary hover:text-txt-secondary transition-colors flex-shrink-0" title="GIF">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor">
|
||||
|
||||
Reference in New Issue
Block a user