feat: auth service + security audit fixes + cleanup legacy services
Major changes:
- Add auth-svc: JWT auth, register/login/refresh, password reset
- Add auth UI: modals, pages (/login, /register, /forgot-password)
- Add usage tracking (usage_metrics table, daily limits)
- Add tiered rate limiting (free/pro/business)
- Add LLM usage limits per tier
Security fixes:
- All repos now require userID for Update/Delete operations
- JWT middleware in chat-svc, llm-svc, agent-svc, discover-svc
- ErrNotFound/ErrForbidden errors for proper access control
Cleanup:
- Remove legacy TypeScript services/ directory
- Remove computer-svc (to be reimplemented)
- Remove old deploy/docker configs
New files:
- backend/cmd/auth-svc/main.go
- backend/internal/auth/{types,repository}.go
- backend/internal/usage/{types,repository}.go
- backend/pkg/middleware/{llm_limits,ratelimit_tiered}.go
- backend/webui/src/components/auth/*
- backend/webui/src/app/(auth)/*
Made-with: Cursor
This commit is contained in:
153
backend/internal/admin/migrations.go
Normal file
153
backend/internal/admin/migrations.go
Normal file
@@ -0,0 +1,153 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
)
|
||||
|
||||
func RunAdminMigrations(ctx context.Context, db *sql.DB) error {
|
||||
migrations := []string{
|
||||
`CREATE TABLE IF NOT EXISTS users (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
email VARCHAR(255) NOT NULL UNIQUE,
|
||||
password_hash VARCHAR(255) NOT NULL,
|
||||
display_name VARCHAR(255) NOT NULL,
|
||||
avatar_url TEXT,
|
||||
role VARCHAR(50) NOT NULL DEFAULT 'user',
|
||||
tier VARCHAR(50) NOT NULL DEFAULT 'free',
|
||||
is_active BOOLEAN NOT NULL DEFAULT true,
|
||||
last_login_at TIMESTAMPTZ,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
)`,
|
||||
`CREATE INDEX IF NOT EXISTS idx_users_email ON users(email)`,
|
||||
`CREATE INDEX IF NOT EXISTS idx_users_role ON users(role)`,
|
||||
`CREATE INDEX IF NOT EXISTS idx_users_is_active ON users(is_active)`,
|
||||
|
||||
`CREATE TABLE IF NOT EXISTS posts (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
title VARCHAR(500) NOT NULL,
|
||||
slug VARCHAR(255) NOT NULL UNIQUE,
|
||||
content TEXT NOT NULL,
|
||||
excerpt TEXT,
|
||||
cover_image TEXT,
|
||||
author_id UUID REFERENCES users(id) ON DELETE SET NULL,
|
||||
category VARCHAR(100) NOT NULL DEFAULT 'general',
|
||||
tags JSONB DEFAULT '[]',
|
||||
status VARCHAR(50) NOT NULL DEFAULT 'draft',
|
||||
view_count INT NOT NULL DEFAULT 0,
|
||||
published_at TIMESTAMPTZ,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
)`,
|
||||
`CREATE INDEX IF NOT EXISTS idx_posts_slug ON posts(slug)`,
|
||||
`CREATE INDEX IF NOT EXISTS idx_posts_author ON posts(author_id)`,
|
||||
`CREATE INDEX IF NOT EXISTS idx_posts_status ON posts(status)`,
|
||||
`CREATE INDEX IF NOT EXISTS idx_posts_category ON posts(category)`,
|
||||
`CREATE INDEX IF NOT EXISTS idx_posts_published_at ON posts(published_at DESC)`,
|
||||
|
||||
`CREATE TABLE IF NOT EXISTS platform_settings (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
site_name VARCHAR(255) NOT NULL DEFAULT 'GooSeek',
|
||||
site_url VARCHAR(500) NOT NULL DEFAULT 'https://gooseek.ru',
|
||||
logo_url TEXT,
|
||||
favicon_url TEXT,
|
||||
description TEXT,
|
||||
support_email VARCHAR(255),
|
||||
features JSONB NOT NULL DEFAULT '{}',
|
||||
llm_settings JSONB NOT NULL DEFAULT '{}',
|
||||
search_settings JSONB NOT NULL DEFAULT '{}',
|
||||
metadata JSONB DEFAULT '{}',
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
)`,
|
||||
|
||||
`CREATE TABLE IF NOT EXISTS discover_categories (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
name VARCHAR(100) NOT NULL UNIQUE,
|
||||
name_ru VARCHAR(100) NOT NULL,
|
||||
icon VARCHAR(10) NOT NULL DEFAULT '📰',
|
||||
color VARCHAR(20) NOT NULL DEFAULT '#6B7280',
|
||||
keywords JSONB NOT NULL DEFAULT '[]',
|
||||
regions JSONB NOT NULL DEFAULT '["world"]',
|
||||
is_active BOOLEAN NOT NULL DEFAULT true,
|
||||
sort_order INT NOT NULL DEFAULT 0,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
)`,
|
||||
`CREATE INDEX IF NOT EXISTS idx_discover_categories_name ON discover_categories(name)`,
|
||||
`CREATE INDEX IF NOT EXISTS idx_discover_categories_sort ON discover_categories(sort_order)`,
|
||||
|
||||
`CREATE TABLE IF NOT EXISTS discover_sources (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
name VARCHAR(255) NOT NULL,
|
||||
url VARCHAR(500) NOT NULL UNIQUE,
|
||||
logo_url TEXT,
|
||||
categories JSONB NOT NULL DEFAULT '[]',
|
||||
trust_score DECIMAL(3,2) NOT NULL DEFAULT 0.5,
|
||||
is_active BOOLEAN NOT NULL DEFAULT true,
|
||||
description TEXT,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
)`,
|
||||
`CREATE INDEX IF NOT EXISTS idx_discover_sources_url ON discover_sources(url)`,
|
||||
`CREATE INDEX IF NOT EXISTS idx_discover_sources_trust ON discover_sources(trust_score DESC)`,
|
||||
|
||||
`CREATE TABLE IF NOT EXISTS audit_logs (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
user_id UUID NOT NULL,
|
||||
user_email VARCHAR(255) NOT NULL,
|
||||
action VARCHAR(100) NOT NULL,
|
||||
resource VARCHAR(100) NOT NULL,
|
||||
resource_id VARCHAR(255),
|
||||
details JSONB,
|
||||
ip_address VARCHAR(45),
|
||||
user_agent TEXT,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
)`,
|
||||
`CREATE INDEX IF NOT EXISTS idx_audit_logs_user ON audit_logs(user_id)`,
|
||||
`CREATE INDEX IF NOT EXISTS idx_audit_logs_action ON audit_logs(action)`,
|
||||
`CREATE INDEX IF NOT EXISTS idx_audit_logs_resource ON audit_logs(resource)`,
|
||||
`CREATE INDEX IF NOT EXISTS idx_audit_logs_created ON audit_logs(created_at DESC)`,
|
||||
|
||||
`CREATE TABLE IF NOT EXISTS connectors (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
type VARCHAR(50) NOT NULL,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
config JSONB NOT NULL DEFAULT '{}',
|
||||
is_active BOOLEAN NOT NULL DEFAULT true,
|
||||
last_sync_at TIMESTAMPTZ,
|
||||
status VARCHAR(50) NOT NULL DEFAULT 'pending',
|
||||
error_msg TEXT,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
)`,
|
||||
`CREATE INDEX IF NOT EXISTS idx_connectors_type ON connectors(type)`,
|
||||
`CREATE INDEX IF NOT EXISTS idx_connectors_status ON connectors(status)`,
|
||||
|
||||
`CREATE TABLE IF NOT EXISTS user_files (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
filename VARCHAR(500) NOT NULL,
|
||||
original_name VARCHAR(500) NOT NULL,
|
||||
file_type VARCHAR(100) NOT NULL,
|
||||
file_size BIGINT NOT NULL,
|
||||
bucket VARCHAR(100) NOT NULL DEFAULT 'user-files',
|
||||
storage_key TEXT NOT NULL,
|
||||
mime_type VARCHAR(100),
|
||||
metadata JSONB DEFAULT '{}',
|
||||
is_public BOOLEAN NOT NULL DEFAULT false,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
)`,
|
||||
`CREATE INDEX IF NOT EXISTS idx_user_files_user ON user_files(user_id)`,
|
||||
`CREATE INDEX IF NOT EXISTS idx_user_files_type ON user_files(file_type)`,
|
||||
`CREATE INDEX IF NOT EXISTS idx_user_files_bucket ON user_files(bucket)`,
|
||||
}
|
||||
|
||||
for _, migration := range migrations {
|
||||
if _, err := db.ExecContext(ctx, migration); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user