15 Commits

17 changed files with 126 additions and 138 deletions

View File

@ -18,7 +18,7 @@ fine with getting your hands dirty, but I plan on having it ready to go for more
- Minimal user login/registration + sessions - Minimal user login/registration + sessions
- Config file handling - Config file handling
- Scheduled tasks - Scheduled tasks
- Entire website compiles into a single binary (~10mb) (excluding env.json) - Entire website compiles into a single binary (~10mb) (excluding env.toml)
- Minimal dependencies (just standard library, postgres driver, and experimental package for bcrypt) - Minimal dependencies (just standard library, postgres driver, and experimental package for bcrypt)
<hr> <hr>
@ -39,10 +39,13 @@ fine with getting your hands dirty, but I plan on having it ready to go for more
## How to use 🤔 ## How to use 🤔
1. Clone 1. Clone
2. Run `go get` to install dependencies 2. Delete the git folder, so you can start tracking in your own repo
3. Copy env_example.json to env.json and fill in the values 3. Run `go get` to install dependencies
4. Run `go run main.go` to start the server 4. Copy env_example.toml to env.toml and fill in the values
5. Start building your app! 5. Run `go run main.go` to start the server
6. Rename the occurences of "GoWeb" to your app name
7. Start building your app!
8. When you see useful changes to GoWeb you'd like in your project copy them over
## How to contribute 👨‍💻 ## How to contribute 👨‍💻
@ -59,4 +62,4 @@ fine with getting your hands dirty, but I plan on having it ready to go for more
- You and you alone are responsible for the security and everything else regarding your application. - You and you alone are responsible for the security and everything else regarding your application.
- It is not required, but I ask that when you use this project you give me credit by linking to this repository. - It is not required, but I ask that when you use this project you give me credit by linking to this repository.
- I also ask that when releasing self-hosted or other end-user applications that you release it under - I also ask that when releasing self-hosted or other end-user applications that you release it under
the [GPLv3](https://www.gnu.org/licenses/gpl-3.0.html) license. This too is not required, but I would appreciate it. the [GPLv3](https://www.gnu.org/licenses/gpl-3.0.html) license. This too is not required, but I would appreciate it.

View File

@ -1,53 +1,44 @@
package config package config
import ( import (
"encoding/json"
"flag" "flag"
"log" "github.com/BurntSushi/toml"
"os" "os"
) )
type Configuration struct { type Configuration struct {
Db struct { Db struct {
Ip string `json:"DbIp"` Ip string `toml:"DbIp"`
Port string `json:"DbPort"` Port string `toml:"DbPort"`
Name string `json:"DbName"` Name string `toml:"DbName"`
User string `json:"DbUser"` User string `toml:"DbUser"`
Password string `json:"DbPassword"` Password string `toml:"DbPassword"`
AutoMigrate bool `json:"DbAutoMigrate"` AutoMigrate bool `toml:"DbAutoMigrate"`
} }
Listen struct { Listen struct {
Ip string `json:"HttpIp"` Ip string `toml:"HttpIp"`
Port string `json:"HttpPort"` Port string `toml:"HttpPort"`
} }
Template struct { Template struct {
BaseName string `json:"BaseTemplateName"` BaseName string `toml:"BaseTemplateName"`
} }
} }
// LoadConfig loads and returns a configuration struct // LoadConfig loads and returns a configuration struct
func LoadConfig() Configuration { func LoadConfig() Configuration {
c := flag.String("c", "env.json", "Path to the json configuration file") c := flag.String("c", "env.toml", "Path to the toml configuration file")
flag.Parse() flag.Parse()
file, err := os.Open(*c) file, err := os.ReadFile(*c)
if err != nil { if err != nil {
log.Fatal("Unable to open JSON config file: ", err) panic("Unable to read TOML config file: " + err.Error())
} }
defer func(file *os.File) { var Config Configuration
err := file.Close() _, err = toml.Decode(string(file), &Config)
if err != nil {
log.Fatal("Unable to close JSON config file: ", err)
}
}(file)
decoder := json.NewDecoder(file)
Config := Configuration{}
err = decoder.Decode(&Config)
if err != nil { if err != nil {
log.Fatal("Unable to decode JSON config file: ", err) panic("Unable to decode TOML config file: " + err.Error())
} }
return Config return Config

View File

@ -3,7 +3,7 @@ package controllers
import ( import (
"GoWeb/app" "GoWeb/app"
"GoWeb/models" "GoWeb/models"
"log" "log/slog"
"net/http" "net/http"
"time" "time"
) )
@ -19,15 +19,12 @@ func (p *Post) Login(w http.ResponseWriter, r *http.Request) {
remember := r.FormValue("remember") == "on" remember := r.FormValue("remember") == "on"
if username == "" || password == "" { if username == "" || password == "" {
log.Println("Tried to login user with empty username or password") http.Redirect(w, r, "/login", http.StatusUnauthorized)
http.Redirect(w, r, "/login", http.StatusFound)
} }
_, err := models.AuthenticateUser(p.App, w, username, password, remember) _, err := models.AuthenticateUser(p.App, w, username, password, remember)
if err != nil { if err != nil {
log.Println("Error authenticating user") http.Redirect(w, r, "/login", http.StatusUnauthorized)
log.Println(err)
http.Redirect(w, r, "/login", http.StatusFound)
return return
} }
@ -41,15 +38,14 @@ func (p *Post) Register(w http.ResponseWriter, r *http.Request) {
updatedAt := time.Now() updatedAt := time.Now()
if username == "" || password == "" { if username == "" || password == "" {
log.Println("Tried to create user with empty username or password") http.Redirect(w, r, "/register", http.StatusUnauthorized)
http.Redirect(w, r, "/register", http.StatusFound)
} }
_, err := models.CreateUser(p.App, username, password, createdAt, updatedAt) _, err := models.CreateUser(p.App, username, password, createdAt, updatedAt)
if err != nil { if err != nil {
log.Println("Error creating user") // TODO: if err == bcrypt.ErrPasswordTooLong display error to user, this will require a flash message system with cookies
log.Println(err) slog.Error("error creating user: " + err.Error())
return http.Redirect(w, r, "/register", http.StatusInternalServerError)
} }
http.Redirect(w, r, "/login", http.StatusFound) http.Redirect(w, r, "/login", http.StatusFound)

View File

@ -5,29 +5,26 @@ import (
"database/sql" "database/sql"
"fmt" "fmt"
_ "github.com/lib/pq" _ "github.com/lib/pq"
"log" "log/slog"
) )
// Connect returns a new database connection // Connect returns a new database connection
func Connect(app *app.App) *sql.DB { func Connect(app *app.App) *sql.DB {
// Set connection parameters from config
postgresConfig := fmt.Sprintf("host=%s port=%s user=%s "+ postgresConfig := fmt.Sprintf("host=%s port=%s user=%s "+
"password=%s dbname=%s sslmode=disable", "password=%s dbname=%s sslmode=disable",
app.Config.Db.Ip, app.Config.Db.Port, app.Config.Db.User, app.Config.Db.Password, app.Config.Db.Name) app.Config.Db.Ip, app.Config.Db.Port, app.Config.Db.User, app.Config.Db.Password, app.Config.Db.Name)
// Create connection
db, err := sql.Open("postgres", postgresConfig) db, err := sql.Open("postgres", postgresConfig)
if err != nil { if err != nil {
panic(err) panic(err)
} }
// Test connection
err = db.Ping() err = db.Ping()
if err != nil { if err != nil {
panic(err) panic(err)
} }
log.Println("Connected to database successfully on " + app.Config.Db.Ip + ":" + app.Config.Db.Port + " using database " + app.Config.Db.Name) slog.Info("connected to database successfully on " + app.Config.Db.Ip + ":" + app.Config.Db.Port + " using database " + app.Config.Db.Name)
return db return db
} }

View File

@ -5,7 +5,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"github.com/lib/pq" "github.com/lib/pq"
"log" "log/slog"
"reflect" "reflect"
) )
@ -39,23 +39,23 @@ func createTable(app *app.App, tableName string) error {
var tableExists bool var tableExists bool
err := app.Db.QueryRow("SELECT EXISTS (SELECT 1 FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE c.relname ~ $1 AND pg_catalog.pg_table_is_visible(c.oid))", "^"+tableName+"$").Scan(&tableExists) err := app.Db.QueryRow("SELECT EXISTS (SELECT 1 FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE c.relname ~ $1 AND pg_catalog.pg_table_is_visible(c.oid))", "^"+tableName+"$").Scan(&tableExists)
if err != nil { if err != nil {
log.Println("Error checking if table exists: " + tableName) slog.Error("error checking if table exists: " + tableName)
return err return err
} }
if tableExists { if tableExists {
log.Println("Table already exists: " + tableName) slog.Info("table already exists: " + tableName)
return nil return nil
} else { } else {
sanitizedTableQuery := fmt.Sprintf("CREATE TABLE IF NOT EXISTS \"%s\" (\"Id\" serial primary key)", tableName) sanitizedTableQuery := fmt.Sprintf("CREATE TABLE IF NOT EXISTS \"%s\" (\"Id\" serial primary key)", tableName)
_, err := app.Db.Query(sanitizedTableQuery) _, err := app.Db.Query(sanitizedTableQuery)
if err != nil { if err != nil {
log.Println("Error creating table: " + tableName) slog.Error("error creating table: " + tableName)
return err return err
} }
log.Println("Table created successfully: " + tableName) slog.Info("table created successfully: " + tableName)
return nil return nil
} }
} }
@ -65,17 +65,17 @@ func createColumn(app *app.App, tableName, columnName, columnType string) error
var columnExists bool var columnExists bool
err := app.Db.QueryRow("SELECT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = $1 AND column_name = $2)", tableName, columnName).Scan(&columnExists) err := app.Db.QueryRow("SELECT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = $1 AND column_name = $2)", tableName, columnName).Scan(&columnExists)
if err != nil { if err != nil {
log.Println("Error checking if column exists: " + columnName + " in table: " + tableName) slog.Error("error checking if column exists: " + columnName + " in table: " + tableName)
return err return err
} }
if columnExists { if columnExists {
log.Println("Column already exists: " + columnName + " in table: " + tableName) slog.Info("column already exists: " + columnName + " in table: " + tableName)
return nil return nil
} else { } else {
postgresType, err := getPostgresType(columnType) postgresType, err := getPostgresType(columnType)
if err != nil { if err != nil {
log.Println("Error creating column: " + columnName + " in table: " + tableName + " with type: " + postgresType) slog.Error("error creating column: " + columnName + " in table: " + tableName + " with type: " + postgresType)
return err return err
} }
@ -84,11 +84,11 @@ func createColumn(app *app.App, tableName, columnName, columnType string) error
_, err = app.Db.Query(query) _, err = app.Db.Query(query)
if err != nil { if err != nil {
log.Println("Error creating column: " + columnName + " in table: " + tableName + " with type: " + postgresType) slog.Error("error creating column: " + columnName + " in table: " + tableName + " with type: " + postgresType)
return err return err
} }
log.Println("Column created successfully:", columnName) slog.Info("column created successfully:", columnName)
return nil return nil
} }

View File

@ -1,17 +0,0 @@
{
"Db": {
"DbIp": "127.0.0.1",
"DbPort": "5432",
"DbName": "database",
"DbUser": "user",
"DbPassword": "password",
"DbAutoMigrate": true
},
"Listen": {
"HttpIp": "127.0.0.1",
"HttpPort": "8090"
},
"Template": {
"BaseTemplateName": "templates/base.html"
}
}

14
env_example.toml Normal file
View File

@ -0,0 +1,14 @@
[Db]
DbIp = "127.0.0.1"
DbPort = "5432"
DbName = "test"
DbUser = "postgres"
DbPassword = "postgres"
DbAutoMigrate = true
[Listen]
HttpIp = "127.0.0.1"
HttpPort = "8090"
[Template]
BaseTemplateName = "templates/base.html"

6
go.mod
View File

@ -1,8 +1,10 @@
module GoWeb module GoWeb
go 1.20 go 1.21
require ( require (
github.com/lib/pq v1.10.9 github.com/lib/pq v1.10.9
golang.org/x/crypto v0.11.0 golang.org/x/crypto v0.13.0
) )
require github.com/BurntSushi/toml v1.3.2

6
go.sum
View File

@ -1,4 +1,6 @@
github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA= golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck=
golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=

32
main.go
View File

@ -8,7 +8,8 @@ import (
"GoWeb/routes" "GoWeb/routes"
"context" "context"
"embed" "embed"
"log" "errors"
"log/slog"
"net/http" "net/http"
"os" "os"
"os/signal" "os/signal"
@ -33,23 +34,26 @@ func main() {
if _, err := os.Stat("logs"); os.IsNotExist(err) { if _, err := os.Stat("logs"); os.IsNotExist(err) {
err := os.Mkdir("logs", 0755) err := os.Mkdir("logs", 0755)
if err != nil { if err != nil {
log.Println("Failed to create log directory") panic("failed to create log directory: " + err.Error())
log.Println(err)
return
} }
} }
// Create log file and set output // Create log file and set output
file, err := os.OpenFile("logs/"+time.Now().Format("2006-01-02")+".log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) file, err := os.OpenFile("logs/"+time.Now().Format("2006-01-02")+".log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
log.SetOutput(file) if err != nil {
panic("error creating log file: " + err.Error())
}
logger := slog.New(slog.NewTextHandler(file, nil))
slog.SetDefault(logger) // Set structured logger globally
// Connect to database and run migrations // Connect to database and run migrations
appLoaded.Db = database.Connect(&appLoaded) appLoaded.Db = database.Connect(&appLoaded)
if appLoaded.Config.Db.AutoMigrate { if appLoaded.Config.Db.AutoMigrate {
err = models.RunAllMigrations(&appLoaded) err = models.RunAllMigrations(&appLoaded)
if err != nil { if err != nil {
log.Println(err) slog.Error("error running migrations: " + err.Error())
return os.Exit(1)
} }
} }
@ -66,10 +70,11 @@ func main() {
// Start server // Start server
server := &http.Server{Addr: appLoaded.Config.Listen.Ip + ":" + appLoaded.Config.Listen.Port} server := &http.Server{Addr: appLoaded.Config.Listen.Ip + ":" + appLoaded.Config.Listen.Port}
go func() { go func() {
log.Println("Starting server and listening on " + appLoaded.Config.Listen.Ip + ":" + appLoaded.Config.Listen.Port) slog.Info("starting server and listening on " + appLoaded.Config.Listen.Ip + ":" + appLoaded.Config.Listen.Port)
err := server.ListenAndServe() err := server.ListenAndServe()
if err != nil && err != http.ErrServerClosed { if err != nil && !errors.Is(err, http.ErrServerClosed) {
log.Fatalf("Could not listen on %s: %v\n", appLoaded.Config.Listen.Ip+":"+appLoaded.Config.Listen.Port, err) slog.Error("could not listen on %s: %v\n", appLoaded.Config.Listen.Ip+":"+appLoaded.Config.Listen.Port, err)
os.Exit(1)
} }
}() }()
@ -80,10 +85,11 @@ func main() {
go app.RunScheduledTasks(&appLoaded, 100, stop) go app.RunScheduledTasks(&appLoaded, 100, stop)
<-interrupt <-interrupt
log.Println("Interrupt signal received. Shutting down server...") slog.Info("interrupt signal received. Shutting down server...")
err = server.Shutdown(context.Background()) err = server.Shutdown(context.Background())
if err != nil { if err != nil {
log.Fatalf("Could not gracefully shutdown the server: %v\n", err) slog.Error("could not gracefully shutdown the server: %v\n", err)
os.Exit(1)
} }
} }

View File

@ -2,16 +2,16 @@ package middleware
import ( import (
"GoWeb/security" "GoWeb/security"
"log" "log/slog"
"net/http" "net/http"
) )
// Csrf validates the CSRF token and returns the handler function if it succeded // Csrf validates the CSRF token and returns the handler function if it succeeded
func Csrf(f func(w http.ResponseWriter, r *http.Request)) func(w http.ResponseWriter, r *http.Request) { func Csrf(f func(w http.ResponseWriter, r *http.Request)) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
_, err := security.VerifyCsrfToken(r) _, err := security.VerifyCsrfToken(r)
if err != nil { if err != nil {
log.Println("Error verifying csrf token") slog.Info("error verifying csrf token")
http.Error(w, "Forbidden", http.StatusForbidden) http.Error(w, "Forbidden", http.StatusForbidden)
return return
} }

View File

@ -4,7 +4,7 @@ import (
"GoWeb/app" "GoWeb/app"
"crypto/rand" "crypto/rand"
"encoding/hex" "encoding/hex"
"log" "log/slog"
"net/http" "net/http"
"time" "time"
) )
@ -42,21 +42,19 @@ func CreateSession(app *app.App, w http.ResponseWriter, userId int64, remember b
var existingAuthToken bool var existingAuthToken bool
err := app.Db.QueryRow(selectAuthTokenIfExists, session.AuthToken).Scan(&existingAuthToken) err := app.Db.QueryRow(selectAuthTokenIfExists, session.AuthToken).Scan(&existingAuthToken)
if err != nil { if err != nil {
log.Println("Error checking for existing auth token") slog.Error("error checking for existing auth token" + err.Error())
log.Println(err)
return Session{}, err return Session{}, err
} }
// If duplicate token found, recursively call function until unique token is generated // If duplicate token found, recursively call function until unique token is generated
if existingAuthToken == true { if existingAuthToken {
log.Println("Duplicate token found in sessions table, generating new token...") slog.Warn("duplicate token found in sessions table, generating new token...")
return CreateSession(app, w, userId, remember) return CreateSession(app, w, userId, remember)
} }
// Insert session into database
err = app.Db.QueryRow(insertSession, session.UserId, session.AuthToken, session.RememberMe, session.CreatedAt).Scan(&session.Id) err = app.Db.QueryRow(insertSession, session.UserId, session.AuthToken, session.RememberMe, session.CreatedAt).Scan(&session.Id)
if err != nil { if err != nil {
log.Println("Error inserting session into database") slog.Error("error inserting session into database")
return Session{}, err return Session{}, err
} }
@ -69,19 +67,18 @@ func GetSessionByAuthToken(app *app.App, authToken string) (Session, error) {
err := app.Db.QueryRow(selectSessionByAuthToken, authToken).Scan(&session.Id, &session.UserId, &session.AuthToken, &session.RememberMe, &session.CreatedAt) err := app.Db.QueryRow(selectSessionByAuthToken, authToken).Scan(&session.Id, &session.UserId, &session.AuthToken, &session.RememberMe, &session.CreatedAt)
if err != nil { if err != nil {
log.Println("Error getting session by auth token")
return Session{}, err return Session{}, err
} }
return session, nil return session, nil
} }
// Generates a random 64-byte string // generateAuthToken generates a random 64-byte string
func generateAuthToken(app *app.App) string { func generateAuthToken(app *app.App) string {
b := make([]byte, 64) b := make([]byte, 64)
_, err := rand.Read(b) _, err := rand.Read(b)
if err != nil { if err != nil {
log.Println("Error generating random bytes") slog.Error("error generating random bytes for auth token")
} }
return hex.EncodeToString(b) return hex.EncodeToString(b)
@ -129,7 +126,7 @@ func deleteSessionCookie(app *app.App, w http.ResponseWriter) {
func DeleteSessionByAuthToken(app *app.App, w http.ResponseWriter, authToken string) error { func DeleteSessionByAuthToken(app *app.App, w http.ResponseWriter, authToken string) error {
_, err := app.Db.Exec(deleteSessionByAuthToken, authToken) _, err := app.Db.Exec(deleteSessionByAuthToken, authToken)
if err != nil { if err != nil {
log.Println("Error deleting session from database") slog.Error("error deleting session from database")
return err return err
} }
@ -143,16 +140,14 @@ func ScheduledSessionCleanup(app *app.App) {
// Delete sessions older than 30 days (remember me sessions) // Delete sessions older than 30 days (remember me sessions)
_, err := app.Db.Exec(deleteSessionsOlderThan30Days) _, err := app.Db.Exec(deleteSessionsOlderThan30Days)
if err != nil { if err != nil {
log.Println("Error deleting 30 day expired sessions from database") slog.Error("error deleting 30 day expired sessions from database" + err.Error())
log.Println(err)
} }
// Delete sessions older than 6 hours // Delete sessions older than 6 hours
_, err = app.Db.Exec(deleteSessionsOlderThan6Hours) _, err = app.Db.Exec(deleteSessionsOlderThan6Hours)
if err != nil { if err != nil {
log.Println("Error deleting 6 hour expired sessions from database") slog.Error("error deleting 6 hour expired sessions from database" + err.Error())
log.Println(err)
} }
log.Println("Deleted expired sessions from database") slog.Info("deleted expired sessions from database")
} }

View File

@ -2,9 +2,8 @@ package models
import ( import (
"GoWeb/app" "GoWeb/app"
"log" "log/slog"
"net/http" "net/http"
"strconv"
"time" "time"
"golang.org/x/crypto/bcrypt" "golang.org/x/crypto/bcrypt"
@ -32,13 +31,11 @@ const (
func GetCurrentUser(app *app.App, r *http.Request) (User, error) { func GetCurrentUser(app *app.App, r *http.Request) (User, error) {
cookie, err := r.Cookie("session") cookie, err := r.Cookie("session")
if err != nil { if err != nil {
log.Println("Error getting session cookie")
return User{}, err return User{}, err
} }
session, err := GetSessionByAuthToken(app, cookie.Value) session, err := GetSessionByAuthToken(app, cookie.Value)
if err != nil { if err != nil {
log.Println("Error getting session by auth token")
return User{}, err return User{}, err
} }
@ -51,7 +48,6 @@ func GetUserById(app *app.App, id int64) (User, error) {
err := app.Db.QueryRow(selectUserById, id).Scan(&user.Id, &user.Username, &user.Password, &user.CreatedAt, &user.UpdatedAt) err := app.Db.QueryRow(selectUserById, id).Scan(&user.Id, &user.Username, &user.Password, &user.CreatedAt, &user.UpdatedAt)
if err != nil { if err != nil {
log.Println("Get user error (user not found) for user id:" + strconv.FormatInt(id, 10))
return User{}, err return User{}, err
} }
@ -64,7 +60,6 @@ func GetUserByUsername(app *app.App, username string) (User, error) {
err := app.Db.QueryRow(selectUserByUsername, username).Scan(&user.Id, &user.Username, &user.Password, &user.CreatedAt, &user.UpdatedAt) err := app.Db.QueryRow(selectUserByUsername, username).Scan(&user.Id, &user.Username, &user.Password, &user.CreatedAt, &user.UpdatedAt)
if err != nil { if err != nil {
log.Println("Get user error (user not found) for user:" + username)
return User{}, err return User{}, err
} }
@ -75,7 +70,7 @@ func GetUserByUsername(app *app.App, username string) (User, error) {
func CreateUser(app *app.App, username string, password string, createdAt time.Time, updatedAt time.Time) (User, error) { func CreateUser(app *app.App, username string, password string, createdAt time.Time, updatedAt time.Time) (User, error) {
hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil { if err != nil {
log.Println("Error hashing password when creating user") slog.Error("error hashing password: " + err.Error())
return User{}, err return User{}, err
} }
@ -83,7 +78,7 @@ func CreateUser(app *app.App, username string, password string, createdAt time.T
err = app.Db.QueryRow(insertUser, username, string(hash), createdAt, updatedAt).Scan(&lastInsertId) err = app.Db.QueryRow(insertUser, username, string(hash), createdAt, updatedAt).Scan(&lastInsertId)
if err != nil { if err != nil {
log.Println("Error creating user row") slog.Error("error creating user row: " + err.Error())
return User{}, err return User{}, err
} }
@ -96,13 +91,13 @@ func AuthenticateUser(app *app.App, w http.ResponseWriter, username string, pass
err := app.Db.QueryRow(selectUserByUsername, username).Scan(&user.Id, &user.Username, &user.Password, &user.CreatedAt, &user.UpdatedAt) err := app.Db.QueryRow(selectUserByUsername, username).Scan(&user.Id, &user.Username, &user.Password, &user.CreatedAt, &user.UpdatedAt)
if err != nil { if err != nil {
log.Println("Authentication error (user not found) for user:" + username) slog.Info("user not found: " + username)
return Session{}, err return Session{}, err
} }
err = bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password)) err = bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password))
if err != nil { // Failed to validate password, doesn't match if err != nil { // Failed to validate password, doesn't match
log.Println("Authentication error (incorrect password) for user:" + username) slog.Info("incorrect password:" + username)
return Session{}, err return Session{}, err
} else { } else {
return CreateSession(app, w, user.Id, remember) return CreateSession(app, w, user.Id, remember)
@ -113,13 +108,11 @@ func AuthenticateUser(app *app.App, w http.ResponseWriter, username string, pass
func LogoutUser(app *app.App, w http.ResponseWriter, r *http.Request) { func LogoutUser(app *app.App, w http.ResponseWriter, r *http.Request) {
cookie, err := r.Cookie("session") cookie, err := r.Cookie("session")
if err != nil { if err != nil {
log.Println("Error getting cookie from request")
return return
} }
err = DeleteSessionByAuthToken(app, w, cookie.Value) err = DeleteSessionByAuthToken(app, w, cookie.Value)
if err != nil { if err != nil {
log.Println("Error deleting session by AuthToken")
return return
} }
} }

View File

@ -21,9 +21,17 @@ func SendRequest(url string, method string, headers map[string]string, body inte
reqBody = &bytes.Buffer{} reqBody = &bytes.Buffer{}
writer := multipart.NewWriter(reqBody) writer := multipart.NewWriter(reqBody)
for key, value := range v { for key, value := range v {
writer.WriteField(key, value) err := writer.WriteField(key, value)
if err != nil {
return http.Response{}, err
}
} }
writer.Close()
err := writer.Close()
if err != nil {
return http.Response{}, err
}
contentType = writer.FormDataContentType() contentType = writer.FormDataContentType()
default: default:
jsonBody, err := json.Marshal(body) jsonBody, err := json.Marshal(body)

View File

@ -4,7 +4,7 @@ import (
"GoWeb/app" "GoWeb/app"
"GoWeb/controllers" "GoWeb/controllers"
"io/fs" "io/fs"
"log" "log/slog"
"net/http" "net/http"
) )
@ -18,12 +18,12 @@ func Get(app *app.App) {
// Serve static files // Serve static files
staticFS, err := fs.Sub(app.Res, "static") staticFS, err := fs.Sub(app.Res, "static")
if err != nil { if err != nil {
log.Println(err) slog.Error(err.Error())
return return
} }
staticHandler := http.FileServer(http.FS(staticFS)) staticHandler := http.FileServer(http.FS(staticFS))
http.Handle("/static/", http.StripPrefix("/static/", staticHandler)) http.Handle("/static/", http.StripPrefix("/static/", staticHandler))
log.Println("Serving static files from embedded file system /static") slog.Info("serving static files from embedded file system /static")
// Pages // Pages
http.HandleFunc("/", getController.ShowHome) http.HandleFunc("/", getController.ShowHome)

View File

@ -3,7 +3,7 @@ package security
import ( import (
"crypto/rand" "crypto/rand"
"encoding/hex" "encoding/hex"
"log" "log/slog"
"math" "math"
"net/http" "net/http"
) )
@ -13,8 +13,7 @@ func GenerateCsrfToken(w http.ResponseWriter, _ *http.Request) (string, error) {
buff := make([]byte, int(math.Ceil(float64(64)/2))) buff := make([]byte, int(math.Ceil(float64(64)/2)))
_, err := rand.Read(buff) _, err := rand.Read(buff)
if err != nil { if err != nil {
log.Println("Error creating random buffer for csrf token value") slog.Error("error creating random buffer for csrf token value" + err.Error())
log.Println(err)
return "", err return "", err
} }
str := hex.EncodeToString(buff) str := hex.EncodeToString(buff)
@ -38,8 +37,7 @@ func GenerateCsrfToken(w http.ResponseWriter, _ *http.Request) (string, error) {
func VerifyCsrfToken(r *http.Request) (bool, error) { func VerifyCsrfToken(r *http.Request) (bool, error) {
cookie, err := r.Cookie("csrf_token") cookie, err := r.Cookie("csrf_token")
if err != nil { if err != nil {
log.Println("Error getting csrf_token cookie") slog.Info("unable to get csrf_token cookie" + err.Error())
log.Println(err)
return false, err return false, err
} }

View File

@ -3,7 +3,7 @@ package templating
import ( import (
"GoWeb/app" "GoWeb/app"
"html/template" "html/template"
"log" "log/slog"
"net/http" "net/http"
) )
@ -13,35 +13,35 @@ func RenderTemplate(app *app.App, w http.ResponseWriter, contentPath string, dat
templateContent, err := app.Res.ReadFile(templatePath) templateContent, err := app.Res.ReadFile(templatePath)
if err != nil { if err != nil {
log.Println(err) slog.Error(err.Error())
http.Error(w, err.Error(), 500) http.Error(w, err.Error(), 500)
return return
} }
t, err := template.New(templatePath).Parse(string(templateContent)) t, err := template.New(templatePath).Parse(string(templateContent))
if err != nil { if err != nil {
log.Println(err) slog.Error(err.Error())
http.Error(w, err.Error(), 500) http.Error(w, err.Error(), 500)
return return
} }
content, err := app.Res.ReadFile(contentPath) content, err := app.Res.ReadFile(contentPath)
if err != nil { if err != nil {
log.Println(err) slog.Error(err.Error())
http.Error(w, err.Error(), 500) http.Error(w, err.Error(), 500)
return return
} }
t, err = t.Parse(string(content)) t, err = t.Parse(string(content))
if err != nil { if err != nil {
log.Println(err) slog.Error(err.Error())
http.Error(w, err.Error(), 500) http.Error(w, err.Error(), 500)
return return
} }
err = t.Execute(w, data) err = t.Execute(w, data)
if err != nil { if err != nil {
log.Println(err) slog.Error(err.Error())
http.Error(w, err.Error(), 500) http.Error(w, err.Error(), 500)
return return
} }