add loger, access log and bans/whitelist
This commit is contained in:
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user