feat: implement core functionality for super-frpc service
This commit introduces the core functionality for the super-frpc service including: - Configuration management - User authentication and authorization - Database integration - FRPC instance management - API endpoints for user operations - Token-based authentication system - Password hashing and validation
This commit is contained in:
+246
@@ -0,0 +1,246 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
_ "modernc.org/sqlite"
|
||||
)
|
||||
|
||||
var db *sql.DB
|
||||
|
||||
type User struct {
|
||||
UserID int
|
||||
Username string
|
||||
Passwd string
|
||||
Type string
|
||||
}
|
||||
|
||||
func InitDatabase(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 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, userType string) (int, error) {
|
||||
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, userType)
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user