Added database support and update login to utilize db
This commit is contained in:
19
db/connection.go
Normal file
19
db/connection.go
Normal 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
62
db/users.go
Normal 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
8
go.mod
@@ -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
10
go.sum
@@ -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=
|
||||
|
||||
@@ -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
14
main.go
@@ -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,
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user