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/a-h/templ v0.3.833
|
||||||
github.com/joho/godotenv v1.5.1
|
github.com/joho/godotenv v1.5.1
|
||||||
github.com/pkg/errors v0.9.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 h1:L/KOk/0VvVTBegtE0fp2RJQiBm7/52Zxv5fqlEHiQUU=
|
||||||
github.com/a-h/templ v0.3.833/go.mod h1:cAu4AiZhtJfBjMY0HASlyzvkrtjnHWPeEsyGK2YYmfk=
|
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 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
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 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||||
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
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 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
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
|
package handlers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"database/sql"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"projectreshoot/cookies"
|
"projectreshoot/cookies"
|
||||||
|
"projectreshoot/db"
|
||||||
"projectreshoot/view/component/form"
|
"projectreshoot/view/component/form"
|
||||||
"projectreshoot/view/page"
|
"projectreshoot/view/page"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TODO: here for testing only, move to database
|
func validateLogin(conn *sql.DB, r *http.Request) (db.User, error) {
|
||||||
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) {
|
|
||||||
formUsername := r.FormValue("username")
|
formUsername := r.FormValue("username")
|
||||||
formPassword := r.FormValue("password")
|
formPassword := r.FormValue("password")
|
||||||
// TODO: search database for username
|
|
||||||
validUser := testUser()
|
user, err := db.GetUserFromUsername(conn, formUsername)
|
||||||
// TODO: check password is valid
|
if err != nil {
|
||||||
if formUsername != validUser.username || formPassword != validUser.password {
|
return db.User{}, errors.Wrap(err, "db.GetUserFromUsername")
|
||||||
return 0, errors.New("Username or password incorrect")
|
|
||||||
}
|
}
|
||||||
// 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 {
|
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(
|
return http.HandlerFunc(
|
||||||
func(w http.ResponseWriter, r *http.Request) {
|
func(w http.ResponseWriter, r *http.Request) {
|
||||||
r.ParseForm()
|
r.ParseForm()
|
||||||
userID, err := validateLogin(r)
|
user, err := validateLogin(conn, r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// TODO: add debug log
|
|
||||||
fmt.Printf("Login failed: %s\n", err)
|
|
||||||
form.LoginForm(err.Error()).Render(r.Context(), w)
|
form.LoginForm(err.Error()).Render(r.Context(), w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: login success, use the userID to set the session
|
// TODO: login success, use the userID to set the session
|
||||||
rememberMe := checkRememberMe(r)
|
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)
|
pageFrom := cookies.CheckPageFrom(w, r)
|
||||||
w.Header().Set("HX-Redirect", pageFrom)
|
w.Header().Set("HX-Redirect", pageFrom)
|
||||||
|
|||||||
14
main.go
14
main.go
@@ -4,15 +4,18 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"embed"
|
"embed"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/pkg/errors"
|
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"projectreshoot/server"
|
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"projectreshoot/db"
|
||||||
|
"projectreshoot/server"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
func run(ctx context.Context, w io.Writer) error {
|
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")
|
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{
|
httpServer := &http.Server{
|
||||||
Addr: net.JoinHostPort(config.Host, config.Port),
|
Addr: net.JoinHostPort(config.Host, config.Port),
|
||||||
Handler: srv,
|
Handler: srv,
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"database/sql"
|
||||||
"net/http"
|
"net/http"
|
||||||
"projectreshoot/handlers"
|
"projectreshoot/handlers"
|
||||||
"projectreshoot/view/page"
|
"projectreshoot/view/page"
|
||||||
@@ -9,6 +10,7 @@ import (
|
|||||||
func addRoutes(
|
func addRoutes(
|
||||||
mux *http.ServeMux,
|
mux *http.ServeMux,
|
||||||
config *Config,
|
config *Config,
|
||||||
|
conn *sql.DB,
|
||||||
) {
|
) {
|
||||||
// Static files
|
// Static files
|
||||||
mux.Handle("GET /static/", http.StripPrefix("/static/", handlers.HandleStatic()))
|
mux.Handle("GET /static/", http.StripPrefix("/static/", handlers.HandleStatic()))
|
||||||
@@ -21,5 +23,5 @@ func addRoutes(
|
|||||||
|
|
||||||
// Login page and handlers
|
// Login page and handlers
|
||||||
mux.Handle("GET /login", handlers.HandleLoginPage(config.TrustedHost))
|
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
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"database/sql"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"projectreshoot/middleware"
|
"projectreshoot/middleware"
|
||||||
|
|
||||||
"github.com/joho/godotenv"
|
"github.com/joho/godotenv"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
TrustedHost string
|
|
||||||
Host string
|
Host string
|
||||||
Port string
|
Port string
|
||||||
|
TrustedHost string
|
||||||
|
TursoURL string
|
||||||
|
TursoToken string
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetConfig() (*Config, error) {
|
func GetConfig() (*Config, error) {
|
||||||
@@ -26,6 +30,8 @@ func GetConfig() (*Config, error) {
|
|||||||
Host: os.Getenv("HOST"),
|
Host: os.Getenv("HOST"),
|
||||||
Port: os.Getenv("PORT"),
|
Port: os.Getenv("PORT"),
|
||||||
TrustedHost: os.Getenv("TRUSTED_HOST"),
|
TrustedHost: os.Getenv("TRUSTED_HOST"),
|
||||||
|
TursoURL: os.Getenv("TURSO_DATABASE_URL"),
|
||||||
|
TursoToken: os.Getenv("TURSO_AUTH_TOKEN"),
|
||||||
}
|
}
|
||||||
if config.Host == "" {
|
if config.Host == "" {
|
||||||
return nil, errors.New("Envar not set: HOST")
|
return nil, errors.New("Envar not set: HOST")
|
||||||
@@ -36,15 +42,22 @@ func GetConfig() (*Config, error) {
|
|||||||
if config.TrustedHost == "" {
|
if config.TrustedHost == "" {
|
||||||
return nil, errors.New("Envar not set: TRUSTED_HOST")
|
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
|
return config, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewServer(config *Config) http.Handler {
|
func NewServer(config *Config, conn *sql.DB) http.Handler {
|
||||||
mux := http.NewServeMux()
|
mux := http.NewServeMux()
|
||||||
addRoutes(
|
addRoutes(
|
||||||
mux,
|
mux,
|
||||||
config,
|
config,
|
||||||
|
conn,
|
||||||
)
|
)
|
||||||
var handler http.Handler = mux
|
var handler http.Handler = mux
|
||||||
handler = middleware.Logging(handler)
|
handler = middleware.Logging(handler)
|
||||||
|
|||||||
Reference in New Issue
Block a user