add loger, access log and bans/whitelist

This commit is contained in:
nahakubuilde
2025-08-26 07:46:01 +01:00
parent e21a0b5b10
commit 4cafd9848f
10 changed files with 1163 additions and 60 deletions

View File

@@ -142,65 +142,122 @@ func Open(cfg *config.Config) (*Service, error) {
}
func (s *Service) migrate() error {
stmts := []string{
`CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT UNIQUE NOT NULL,
email TEXT UNIQUE NOT NULL,
password_hash TEXT NOT NULL,
is_active INTEGER NOT NULL DEFAULT 1,
email_confirmed INTEGER NOT NULL DEFAULT 0,
mfa_secret TEXT NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
)` ,
`CREATE TABLE IF NOT EXISTS groups (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT UNIQUE NOT NULL
)` ,
`CREATE TABLE IF NOT EXISTS user_groups (
user_id INTEGER NOT NULL,
group_id INTEGER NOT NULL,
PRIMARY KEY(user_id, group_id),
FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY(group_id) REFERENCES groups(id) ON DELETE CASCADE
)` ,
`CREATE TABLE IF NOT EXISTS permissions (
id INTEGER PRIMARY KEY AUTOINCREMENT,
group_id INTEGER NOT NULL,
path_prefix TEXT NOT NULL,
can_read INTEGER NOT NULL DEFAULT 1,
can_write INTEGER NOT NULL DEFAULT 0,
can_delete INTEGER NOT NULL DEFAULT 0,
FOREIGN KEY(group_id) REFERENCES groups(id) ON DELETE CASCADE
)` ,
`CREATE TABLE IF NOT EXISTS email_verification_tokens (
user_id INTEGER NOT NULL,
token TEXT NOT NULL UNIQUE,
expires_at DATETIME NOT NULL,
PRIMARY KEY(user_id),
FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE
)` ,
`CREATE TABLE IF NOT EXISTS password_reset_tokens (
user_id INTEGER NOT NULL,
token TEXT NOT NULL UNIQUE,
expires_at DATETIME NOT NULL,
PRIMARY KEY(user_id),
FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE
)` ,
`CREATE TABLE IF NOT EXISTS mfa_enrollments (
user_id INTEGER PRIMARY KEY,
secret TEXT NOT NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE
)` ,
}
for _, stmt := range stmts {
if _, err := s.DB.Exec(stmt); err != nil {
return err
}
}
return nil
stmts := []string{
`CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT UNIQUE NOT NULL,
email TEXT UNIQUE NOT NULL,
password_hash TEXT NOT NULL,
is_active INTEGER NOT NULL DEFAULT 1,
email_confirmed INTEGER NOT NULL DEFAULT 0,
mfa_secret TEXT NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
)` ,
`CREATE TABLE IF NOT EXISTS groups (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT UNIQUE NOT NULL
)` ,
`CREATE TABLE IF NOT EXISTS user_groups (
user_id INTEGER NOT NULL,
group_id INTEGER NOT NULL,
PRIMARY KEY(user_id, group_id),
FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY(group_id) REFERENCES groups(id) ON DELETE CASCADE
)` ,
`CREATE TABLE IF NOT EXISTS permissions (
id INTEGER PRIMARY KEY AUTOINCREMENT,
group_id INTEGER NOT NULL,
path_prefix TEXT NOT NULL,
can_read INTEGER NOT NULL DEFAULT 1,
can_write INTEGER NOT NULL DEFAULT 0,
can_delete INTEGER NOT NULL DEFAULT 0,
FOREIGN KEY(group_id) REFERENCES groups(id) ON DELETE CASCADE
)` ,
`CREATE TABLE IF NOT EXISTS email_verification_tokens (
user_id INTEGER NOT NULL,
token TEXT NOT NULL UNIQUE,
expires_at DATETIME NOT NULL,
PRIMARY KEY(user_id),
FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE
)` ,
`CREATE TABLE IF NOT EXISTS password_reset_tokens (
user_id INTEGER NOT NULL,
token TEXT NOT NULL UNIQUE,
expires_at DATETIME NOT NULL,
PRIMARY KEY(user_id),
FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE
)` ,
`CREATE TABLE IF NOT EXISTS mfa_enrollments (
user_id INTEGER PRIMARY KEY,
secret TEXT NOT NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE
)` ,
// Access logs for all requests
`CREATE TABLE IF NOT EXISTS access_logs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NULL,
ip TEXT NOT NULL,
method TEXT NOT NULL,
path TEXT NOT NULL,
status INTEGER NOT NULL,
duration_ms INTEGER NOT NULL,
user_agent TEXT NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE SET NULL
)` ,
`CREATE INDEX IF NOT EXISTS idx_access_logs_created_at ON access_logs(created_at)` ,
`CREATE INDEX IF NOT EXISTS idx_access_logs_user_id ON access_logs(user_id)` ,
`CREATE INDEX IF NOT EXISTS idx_access_logs_ip ON access_logs(ip)` ,
// Error logs for server-side errors
`CREATE TABLE IF NOT EXISTS error_logs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NULL,
ip TEXT NULL,
path TEXT NULL,
message TEXT NOT NULL,
stack TEXT NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE SET NULL
)` ,
`CREATE INDEX IF NOT EXISTS idx_error_logs_created_at ON error_logs(created_at)` ,
// Failed login attempts (both password and MFA tracked separately)
`CREATE TABLE IF NOT EXISTS failed_logins (
id INTEGER PRIMARY KEY AUTOINCREMENT,
ip TEXT NOT NULL,
user_id INTEGER NULL,
username TEXT NULL,
type TEXT NOT NULL CHECK(type IN ('password','mfa')),
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE SET NULL
)` ,
`CREATE INDEX IF NOT EXISTS idx_failed_logins_ip_created ON failed_logins(ip, created_at)` ,
`CREATE INDEX IF NOT EXISTS idx_failed_logins_type_created ON failed_logins(type, created_at)` ,
// IP bans and whitelist
`CREATE TABLE IF NOT EXISTS ip_bans (
id INTEGER PRIMARY KEY AUTOINCREMENT,
ip TEXT NOT NULL UNIQUE,
reason TEXT NULL,
until DATETIME NULL,
permanent INTEGER NOT NULL DEFAULT 0,
whitelisted INTEGER NOT NULL DEFAULT 0,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
)` ,
`CREATE INDEX IF NOT EXISTS idx_ip_bans_ip ON ip_bans(ip)` ,
`CREATE INDEX IF NOT EXISTS idx_ip_bans_whitelist ON ip_bans(whitelisted)` ,
`CREATE INDEX IF NOT EXISTS idx_ip_bans_permanent ON ip_bans(permanent)` ,
}
for _, stmt := range stmts {
if _, err := s.DB.Exec(stmt); err != nil {
return err
}
}
return nil
}
func (s *Service) ensureDefaultAdmin() error {