Files
backend/database.go
NanamiAdmin fc4ad46f6c fix(database): correct time parsing for frpc instance queries
Fix incorrect time parsing when querying frpc instances by properly handling the createdAt string field and converting it to time.Time. Also fix indentation in InitUserDatabase function.
2026-03-02 19:48:00 +08:00

351 lines
9.6 KiB
Go

package main
import (
"database/sql"
"errors"
"fmt"
"strings"
"time"
_ "modernc.org/sqlite"
)
var db *sql.DB
type User struct {
UserID int
Username string
Passwd string
Type string
}
func InitDatabase(dbPath string) error {
InitUserDatabase(dbPath)
InitFrpcDatabase(dbPath)
return nil
}
func CloseDatabase() error {
if db != nil {
return db.Close()
}
return nil
}
func isValidInput(input string) bool {
invalidChars := []string{"'", "\"", ";", "--", "/*", "*/", "xp_", "sp_", "EXEC", "EXECUTE", "DROP", "INSERT", "UPDATE", "DELETE", "SELECT"}
lowerInput := strings.ToLower(input)
for _, chars := range invalidChars {
if strings.Contains(lowerInput, chars) {
return false
}
}
return true
}
func AddUser(username, passwd string) (int, error) { // New user registration with default type "visitor"
if !isValidInput(username) || !isValidInput(passwd) {
return 0, errors.New("invalid input: contains illegal characters")
}
if !isValidPassword(passwd) {
return 0, errors.New("password does not meet complexity requirements")
}
hashedPasswd, err := hashPassword(passwd)
if err != nil {
return 0, fmt.Errorf("failed to hash password: %w", err)
}
result, err := db.Exec("INSERT INTO userLogin (username, passwd, type) VALUES (?, ?, ?)",
username, hashedPasswd, "visitor")
if err != nil {
if strings.Contains(err.Error(), "UNIQUE constraint failed") {
return 0, errors.New("username already exists")
}
return 0, fmt.Errorf("failed to insert user: %w", err)
}
lastID, err := result.LastInsertId()
if err != nil {
return 0, fmt.Errorf("failed to get last insert id: %w", err)
}
var count int
err = db.QueryRow("SELECT COUNT(*) FROM userLogin WHERE userID = ?", lastID).Scan(&count)
if err != nil {
return 0, fmt.Errorf("failed to verify user insertion: %w", err)
}
if count == 0 {
return 0, errors.New("user insertion verification failed")
}
return int(lastID), nil
}
func GetUserByUsername(username string) (*User, error) {
if !isValidInput(username) {
return nil, errors.New("invalid input: contains illegal characters")
}
var user User
err := db.QueryRow("SELECT userID, username, passwd, type FROM userLogin WHERE username = ?", username).
Scan(&user.UserID, &user.Username, &user.Passwd, &user.Type)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, errors.New("user not found")
}
return nil, fmt.Errorf("failed to query user: %w", err)
}
return &user, nil
}
func GetUserByID(userID int) (*User, error) {
var user User
err := db.QueryRow("SELECT userID, username, passwd, type FROM userLogin WHERE userID = ?", userID).
Scan(&user.UserID, &user.Username, &user.Passwd, &user.Type)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, errors.New("user not found")
}
return nil, fmt.Errorf("failed to query user: %w", err)
}
return &user, nil
}
func UpdateUserPassword(userID int, newPasswd string) error {
if !isValidInput(newPasswd) {
return errors.New("invalid input: contains illegal characters")
}
if !isValidPassword(newPasswd) {
return errors.New("password does not meet complexity requirements")
}
hashedPasswd, err := hashPassword(newPasswd)
if err != nil {
return fmt.Errorf("failed to hash password: %w", err)
}
result, err := db.Exec("UPDATE userLogin SET passwd = ? WHERE userID = ?", hashedPasswd, userID)
if err != nil {
return fmt.Errorf("failed to update password: %w", err)
}
rowsAffected, err := result.RowsAffected()
if err != nil {
return fmt.Errorf("failed to get rows affected: %w", err)
}
if rowsAffected == 0 {
return errors.New("user not found")
}
return nil
}
func UpdateUserType(userID int, newType string) error {
validTypes := map[string]bool{
"superuser": true,
"admin": true,
"visitor": true,
}
if !validTypes[newType] {
return errors.New("invalid user type")
}
result, err := db.Exec("UPDATE userLogin SET type = ? WHERE userID = ?", newType, userID)
if err != nil {
return fmt.Errorf("failed to update user type: %w", err)
}
rowsAffected, err := result.RowsAffected()
if err != nil {
return fmt.Errorf("failed to get rows affected: %w", err)
}
if rowsAffected == 0 {
return errors.New("user not found")
}
return nil
}
func DeleteUser(userID int) error {
result, err := db.Exec("DELETE FROM userLogin WHERE userID = ?", userID)
if err != nil {
return fmt.Errorf("failed to delete user: %w", err)
}
rowsAffected, err := result.RowsAffected()
if err != nil {
return fmt.Errorf("failed to get rows affected: %w", err)
}
if rowsAffected == 0 {
return errors.New("user not found")
}
return nil
}
func GetNextAvailableUserID() (int, error) {
var maxID int
err := db.QueryRow("SELECT COALESCE(MAX(userID), 0) FROM userLogin").Scan(&maxID)
if err != nil {
return 0, fmt.Errorf("failed to get max userID: %w", err)
}
return maxID + 1, nil
}
func GetAllUsers() ([]User, error) {
rows, err := db.Query("SELECT userID, username, type FROM userLogin")
if err != nil {
return nil, fmt.Errorf("failed to query users: %w", err)
}
defer rows.Close()
var users []User
for rows.Next() {
var user User
if err := rows.Scan(&user.UserID, &user.Username, &user.Type); err != nil {
return nil, fmt.Errorf("failed to scan user: %w", err)
}
users = append(users, user)
}
if err = rows.Err(); err != nil {
return nil, fmt.Errorf("rows error: %w", err)
}
return users, nil
}
func InitFrpcDatabase(dbPath string) error {
var err error
frpcDB, err = sql.Open("sqlite", dbPath)
if err != nil {
return fmt.Errorf("failed to open frpc database: %w", err)
}
if err = frpcDB.Ping(); err != nil {
return fmt.Errorf("failed to ping frpc database: %w", err)
}
createTableSQL := `
CREATE TABLE IF NOT EXISTS frpcInstances (
id INTEGER PRIMARY KEY AUTOINCREMENT,
userID INTEGER NOT NULL,
name TEXT NOT NULL,
serverAddr TEXT NOT NULL,
serverPort TEXT NOT NULL,
auth_method TEXT NOT NULL,
bootAtStart INTEGER NOT NULL DEFAULT 0,
runUser TEXT NOT NULL DEFAULT 'root',
configPath TEXT NOT NULL,
createdAt TEXT NOT NULL,
UNIQUE(userID, name)
);
`
_, err = frpcDB.Exec(createTableSQL)
if err != nil {
return fmt.Errorf("failed to create frpcInstances table: %w", err)
}
return nil
}
func InitUserDatabase(dbPath string) error {
var err error
db, err = sql.Open("sqlite", dbPath)
if err != nil {
return fmt.Errorf("failed to open database: %w", err)
}
if err = db.Ping(); err != nil {
return fmt.Errorf("failed to ping database: %w", err)
}
createTableSQL := `
CREATE TABLE IF NOT EXISTS userLogin (
userID INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT UNIQUE NOT NULL,
passwd TEXT NOT NULL,
type TEXT NOT NULL DEFAULT 'visitor'
);
`
_, err = db.Exec(createTableSQL)
if err != nil {
return fmt.Errorf("failed to create table: %w", err)
}
return nil
}
func DBAddFrpcInstance(instance FrpcInstance) error {
_, err := frpcDB.Exec("INSERT INTO frpcInstances (userID, name, serverAddr, serverPort, auth_method, bootAtStart, runUser, configPath, createdAt) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
instance.UserID, instance.Name, instance.ServerAddr, instance.ServerPort, instance.AuthMethod, instance.BootAtStart, instance.RunUser, instance.ConfigPath, time.Now().Format(time.RFC3339))
if err != nil {
return fmt.Errorf("failed to insert frpc instance: %w", err)
}
return nil
}
func DBQueryFrpcInstance(userID int, instanceName string) (FrpcInstance, error) {
var instance FrpcInstance
var createdAtStr string
err := frpcDB.QueryRow("SELECT id, userID, name, serverAddr, serverPort, auth_method, bootAtStart, runUser, configPath, createdAt FROM frpcInstances WHERE userID = ? AND name = ?", userID, instanceName).Scan(
&instance.ID, &instance.UserID, &instance.Name, &instance.ServerAddr, &instance.ServerPort, &instance.AuthMethod, &instance.BootAtStart, &instance.RunUser, &instance.ConfigPath, &createdAtStr)
if err != nil {
return instance, fmt.Errorf("failed to query frpc instance: %w", err)
}
instance.CreatedAt, _ = time.Parse(time.RFC3339, createdAtStr)
return instance, nil
}
func DBRemoveFrpcInstance(userID int, instanceName string) error {
_, err := frpcDB.Exec("DELETE FROM frpcInstances WHERE userID = ? AND name = ?", userID, instanceName)
if err != nil {
return fmt.Errorf("failed to delete frpc instance: %w", err)
}
return nil
}
func DBUpdateFrpcInstance(instance FrpcInstance) error {
_, err := frpcDB.Exec("UPDATE frpcInstances SET serverAddr = ?, serverPort = ?, auth_method = ?, bootAtStart = ?, runUser = ?, configPath = ? WHERE id = ?",
instance.ServerAddr, instance.ServerPort, instance.AuthMethod, instance.BootAtStart, instance.RunUser, instance.ConfigPath, instance.ID)
if err != nil {
return fmt.Errorf("failed to update frpc instance: %w", err)
}
return nil
}
func DBListFrpcInstances(userID int) ([]FrpcInstance, error) {
rows, err := frpcDB.Query("SELECT id, userID, name, serverAddr, serverPort, auth_method, bootAtStart, runUser, configPath, createdAt FROM frpcInstances WHERE userID = ?", userID)
if err != nil {
return nil, fmt.Errorf("failed to query frpc instances: %w", err)
}
defer rows.Close()
var instances []FrpcInstance
for rows.Next() {
var instance FrpcInstance
var createdAtStr string
if err := rows.Scan(&instance.ID, &instance.UserID, &instance.Name, &instance.ServerAddr, &instance.ServerPort, &instance.AuthMethod, &instance.BootAtStart, &instance.RunUser, &instance.ConfigPath, &createdAtStr); err != nil {
return nil, fmt.Errorf("failed to scan frpc instance: %w", err)
}
instance.CreatedAt, _ = time.Parse(time.RFC3339, createdAtStr)
instances = append(instances, instance)
}
if err = rows.Err(); err != nil {
return nil, fmt.Errorf("rows error: %w", err)
}
return instances, nil
}