Added database support and update login to utilize db

This commit is contained in:
2025-02-08 19:39:44 +11:00
parent a842d09f96
commit 597fc6f072
8 changed files with 151 additions and 32 deletions

19
db/connection.go Normal file
View File

@@ -0,0 +1,19 @@
package db
import (
"database/sql"
"fmt"
"github.com/pkg/errors"
_ "github.com/tursodatabase/libsql-client-go/libsql"
)
func ConnectToDatabase(primaryUrl *string, authToken *string) (*sql.DB, error) {
url := fmt.Sprintf("libsql://%s.turso.io?authToken=%s", *primaryUrl, *authToken)
db, err := sql.Open("libsql", url)
if err != nil {
return nil, errors.Wrap(err, "sql.Open")
}
return db, nil
}

62
db/users.go Normal file
View File

@@ -0,0 +1,62 @@
package db
import (
"database/sql"
"fmt"
"github.com/pkg/errors"
"golang.org/x/crypto/bcrypt"
)
type User struct {
ID int
Username string
Password_hash string
Created_at int64
}
func (user *User) SetPassword(conn *sql.DB, password string) error {
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil {
return errors.Wrap(err, "bcrypt.GenerateFromPassword")
}
user.Password_hash = string(hashedPassword)
query := `UPDATE users SET password_hash = ? WHERE id = ?`
result, err := conn.Exec(query, user.Password_hash, user.ID)
if err != nil {
return errors.Wrap(err, "conn.Exec")
}
fmt.Println(result)
return nil
}
func (user *User) CheckPassword(password string) error {
err := bcrypt.CompareHashAndPassword([]byte(user.Password_hash), []byte(password))
if err != nil {
return errors.Wrap(err, "bcrypt.CompareHashAndPassword")
}
return nil
}
func GetUserFromUsername(conn *sql.DB, username string) (User, error) {
query := `SELECT id, username, password_hash, created_at FROM users
WHERE username = ? COLLATE NOCASE`
rows, err := conn.Query(query, username)
if err != nil {
return User{}, errors.Wrap(err, "conn.Query")
}
defer rows.Close()
var user User
for rows.Next() {
err := rows.Scan(
&user.ID,
&user.Username,
&user.Password_hash,
&user.Created_at,
)
if err != nil {
return User{}, errors.Wrap(err, "rows.Scan")
}
}
return user, nil
}

8
go.mod
View File

@@ -6,4 +6,12 @@ require (
github.com/a-h/templ v0.3.833
github.com/joho/godotenv v1.5.1
github.com/pkg/errors v0.9.1
github.com/tursodatabase/libsql-client-go v0.0.0-20240902231107-85af5b9d094d
golang.org/x/crypto v0.33.0
)
require (
github.com/antlr4-go/antlr/v4 v4.13.0 // indirect
github.com/coder/websocket v1.8.12 // indirect
golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8 // indirect
)

10
go.sum
View File

@@ -1,8 +1,18 @@
github.com/a-h/templ v0.3.833 h1:L/KOk/0VvVTBegtE0fp2RJQiBm7/52Zxv5fqlEHiQUU=
github.com/a-h/templ v0.3.833/go.mod h1:cAu4AiZhtJfBjMY0HASlyzvkrtjnHWPeEsyGK2YYmfk=
github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI=
github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g=
github.com/coder/websocket v1.8.12 h1:5bUXkEPPIbewrnkU8LTCLVaxi4N4J8ahufH2vlo4NAo=
github.com/coder/websocket v1.8.12/go.mod h1:LNVeNrXQZfe5qhS9ALED3uA+l5pPqvwXg3CKoDBB2gs=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/tursodatabase/libsql-client-go v0.0.0-20240902231107-85af5b9d094d h1:dOMI4+zEbDI37KGb0TI44GUAwxHF9cMsIoDTJ7UmgfU=
github.com/tursodatabase/libsql-client-go v0.0.0-20240902231107-85af5b9d094d/go.mod h1:l8xTsYB90uaVdMHXMCxKKLSgw5wLYBwBKKefNIUnm9s=
golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=
golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8 h1:aAcj0Da7eBAtrTp03QXWvm88pSyOt+UgdZw2BFZ+lEw=
golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8/go.mod h1:CQ1k9gNrJ50XIzaKCRR2hssIjF07kZFEiieALBM/ARQ=

View File

@@ -1,37 +1,32 @@
package handlers
import (
"errors"
"database/sql"
"fmt"
"net/http"
"projectreshoot/cookies"
"projectreshoot/db"
"projectreshoot/view/component/form"
"projectreshoot/view/page"
"github.com/pkg/errors"
)
// TODO: here for testing only, move to database
type User struct {
id int
username string
password string
}
// TODO: here for testing only, move to database
func testUser() User {
return User{id: 1, username: "Haelnorr", password: "test"}
}
func validateLogin(r *http.Request) (int, error) {
func validateLogin(conn *sql.DB, r *http.Request) (db.User, error) {
formUsername := r.FormValue("username")
formPassword := r.FormValue("password")
// TODO: search database for username
validUser := testUser()
// TODO: check password is valid
if formUsername != validUser.username || formPassword != validUser.password {
return 0, errors.New("Username or password incorrect")
user, err := db.GetUserFromUsername(conn, formUsername)
if err != nil {
return db.User{}, errors.Wrap(err, "db.GetUserFromUsername")
}
// TODO: return the users ID
return validUser.id, nil
err = user.CheckPassword(formPassword)
if err != nil {
return db.User{}, errors.New("Username or password incorrect")
}
return user, nil
}
func checkRememberMe(r *http.Request) bool {
@@ -43,21 +38,23 @@ func checkRememberMe(r *http.Request) bool {
}
}
func HandleLoginRequest() http.Handler {
func HandleLoginRequest(conn *sql.DB) http.Handler {
return http.HandlerFunc(
func(w http.ResponseWriter, r *http.Request) {
r.ParseForm()
userID, err := validateLogin(r)
user, err := validateLogin(conn, r)
if err != nil {
// TODO: add debug log
fmt.Printf("Login failed: %s\n", err)
form.LoginForm(err.Error()).Render(r.Context(), w)
return
}
// TODO: login success, use the userID to set the session
rememberMe := checkRememberMe(r)
fmt.Printf("Login success, user ID: %v - remember me?: %t\n", userID, rememberMe)
fmt.Printf(
"Login success, user: %v - remember me?: %t\n",
user.Username,
rememberMe,
)
pageFrom := cookies.CheckPageFrom(w, r)
w.Header().Set("HX-Redirect", pageFrom)

14
main.go
View File

@@ -4,15 +4,18 @@ import (
"context"
"embed"
"fmt"
"github.com/pkg/errors"
"io"
"net"
"net/http"
"os"
"os/signal"
"projectreshoot/server"
"sync"
"time"
"projectreshoot/db"
"projectreshoot/server"
"github.com/pkg/errors"
)
func run(ctx context.Context, w io.Writer) error {
@@ -24,7 +27,12 @@ func run(ctx context.Context, w io.Writer) error {
return errors.Wrap(err, "server.GetConfig")
}
srv := server.NewServer(config)
conn, err := db.ConnectToDatabase(&config.TursoURL, &config.TursoToken)
if err != nil {
return errors.Wrap(err, "db.ConnectToDatabase")
}
srv := server.NewServer(config, conn)
httpServer := &http.Server{
Addr: net.JoinHostPort(config.Host, config.Port),
Handler: srv,

View File

@@ -1,6 +1,7 @@
package server
import (
"database/sql"
"net/http"
"projectreshoot/handlers"
"projectreshoot/view/page"
@@ -9,6 +10,7 @@ import (
func addRoutes(
mux *http.ServeMux,
config *Config,
conn *sql.DB,
) {
// Static files
mux.Handle("GET /static/", http.StripPrefix("/static/", handlers.HandleStatic()))
@@ -21,5 +23,5 @@ func addRoutes(
// Login page and handlers
mux.Handle("GET /login", handlers.HandleLoginPage(config.TrustedHost))
mux.Handle("POST /login", handlers.HandleLoginRequest())
mux.Handle("POST /login", handlers.HandleLoginRequest(conn))
}

View File

@@ -1,19 +1,23 @@
package server
import (
"database/sql"
"errors"
"fmt"
"net/http"
"os"
"projectreshoot/middleware"
"github.com/joho/godotenv"
)
type Config struct {
TrustedHost string
Host string
Port string
TrustedHost string
TursoURL string
TursoToken string
}
func GetConfig() (*Config, error) {
@@ -26,6 +30,8 @@ func GetConfig() (*Config, error) {
Host: os.Getenv("HOST"),
Port: os.Getenv("PORT"),
TrustedHost: os.Getenv("TRUSTED_HOST"),
TursoURL: os.Getenv("TURSO_DATABASE_URL"),
TursoToken: os.Getenv("TURSO_AUTH_TOKEN"),
}
if config.Host == "" {
return nil, errors.New("Envar not set: HOST")
@@ -36,15 +42,22 @@ func GetConfig() (*Config, error) {
if config.TrustedHost == "" {
return nil, errors.New("Envar not set: TRUSTED_HOST")
}
if config.TursoURL == "" {
return nil, errors.New("Envar not set: TURSO_DATABASE_URL")
}
if config.TursoToken == "" {
return nil, errors.New("Envar not set: TURSO_AUTH_TOKEN")
}
return config, nil
}
func NewServer(config *Config) http.Handler {
func NewServer(config *Config, conn *sql.DB) http.Handler {
mux := http.NewServeMux()
addRoutes(
mux,
config,
conn,
)
var handler http.Handler = mux
handler = middleware.Logging(handler)