aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlexander Kavon <hawk@alexkavon.com>2023-11-29 02:05:38 -0500
committerAlexander Kavon <hawk@alexkavon.com>2023-11-29 02:05:38 -0500
commit1c32e1497525bc6f609898f0f79bd6cc3e6d92fb (patch)
tree99abc1c2690b151bfb3d45e9f76a8770b3597e7a
parent5e26ebc96bd41f9d232573d873cb6cb55f0050c4 (diff)
user create/insert into users table, pgxpool defer moved to top of app, me.tmpl.html, secret hashing with argon2id, updated migrations
-rw-r--r--go.mod10
-rw-r--r--go.sum30
-rw-r--r--migrations/002_create_users_table.sql5
-rw-r--r--src/db/db.go30
-rw-r--r--src/main.go4
-rw-r--r--src/server/server.go31
-rw-r--r--src/server/session.go9
-rw-r--r--src/user/routes.go56
-rw-r--r--src/user/user.go63
-rw-r--r--ui/pages/user/create.tmpl.html8
-rw-r--r--ui/pages/user/me.tmpl.html5
11 files changed, 218 insertions, 33 deletions
diff --git a/go.mod b/go.mod
index 7ed3daf..b1d3196 100644
--- a/go.mod
+++ b/go.mod
@@ -5,14 +5,22 @@ go 1.21.3
require (
github.com/BurntSushi/toml v1.3.2
github.com/go-chi/chi/v5 v5.0.10
+ github.com/go-playground/validator/v10 v10.16.0
+ github.com/google/uuid v1.4.0
github.com/jackc/pgx/v5 v5.5.0
+ golang.org/x/crypto v0.16.0
)
require (
+ github.com/gabriel-vasile/mimetype v1.4.3 // indirect
+ github.com/go-playground/locales v0.14.1 // indirect
+ github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
github.com/jackc/puddle/v2 v2.2.1 // indirect
- golang.org/x/crypto v0.14.0 // indirect
+ github.com/leodido/go-urn v1.2.4 // indirect
+ golang.org/x/net v0.19.0 // indirect
golang.org/x/sync v0.5.0 // indirect
+ golang.org/x/sys v0.15.0 // indirect
golang.org/x/text v0.14.0 // indirect
)
diff --git a/go.sum b/go.sum
index edfed08..0854549 100644
--- a/go.sum
+++ b/go.sum
@@ -3,8 +3,20 @@ github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbi
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
+github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
github.com/go-chi/chi/v5 v5.0.10 h1:rLz5avzKpjqxrYwXNfmjkrYYXOyLJd37pz53UFHC6vk=
github.com/go-chi/chi/v5 v5.0.10/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
+github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
+github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
+github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
+github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
+github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
+github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
+github.com/go-playground/validator/v10 v10.16.0 h1:x+plE831WK4vaKHO/jpgUGsvLKIqRRkz6M78GuJAfGE=
+github.com/go-playground/validator/v10 v10.16.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
+github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=
+github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
@@ -13,17 +25,27 @@ github.com/jackc/pgx/v5 v5.5.0 h1:NxstgwndsTRy7eq9/kqYc/BZh5w2hHJV86wjvO+1xPw=
github.com/jackc/pgx/v5 v5.5.0/go.mod h1:Ig06C2Vu0t5qXC60W8sqIthScaEnFvojjj9dSljmHRA=
github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk=
github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
+github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
+github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
+github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
-github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
-golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
-golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
+github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
+github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
+github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
+golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY=
+golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
+golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c=
+golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE=
golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
+golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
+golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
diff --git a/migrations/002_create_users_table.sql b/migrations/002_create_users_table.sql
index a561a05..d2322f9 100644
--- a/migrations/002_create_users_table.sql
+++ b/migrations/002_create_users_table.sql
@@ -1,7 +1,8 @@
CREATE TABLE users(
id SERIAL NOT NULL PRIMARY KEY,
- username VARCHAR(50) NOT NULL,
- secret VARCHAR(255) NOT NULL,
+ username VARCHAR(50) UNIQUE NOT NULL,
+ secret VARCHAR(256) NOT NULL,
+ email VARCHAR(256) UNIQUE NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
diff --git a/src/db/db.go b/src/db/db.go
index 2b72382..6f617c2 100644
--- a/src/db/db.go
+++ b/src/db/db.go
@@ -2,8 +2,11 @@ package db
import (
"context"
+ "fmt"
"log"
+ "strings"
+ "github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgxpool"
"gitlab.com/alexkavon/newsstand/src/conf"
@@ -18,7 +21,6 @@ func NewDb(config *conf.Conf) *Database {
if err != nil {
log.Fatal(err)
}
- defer pool.Close()
var testquery string
err = pool.QueryRow(context.Background(), "select 'Hello, PostgreSQL!'").Scan(&testquery)
@@ -30,3 +32,29 @@ func NewDb(config *conf.Conf) *Database {
p: pool,
}
}
+
+func (d *Database) InsertTable(table string, columns []string, values pgx.NamedArgs) error {
+ columnstr := stringifyColumns(columns, "")
+ valuesstr := stringifyColumns(columns, "@")
+ query := fmt.Sprintf("INSERT INTO %s (%s) VALUES (%s) RETURNING *", table, columnstr, valuesstr)
+ log.Println(query, values)
+ ctx := context.Background()
+ row, err := d.p.Exec(ctx, query, values)
+ if err != nil {
+ return err
+ }
+ log.Println("Row", row)
+ return nil
+}
+
+func (d *Database) Close() {
+ d.p.Close()
+}
+
+func stringifyColumns(columns []string, prefix string) string {
+ ncolumns := []string{}
+ for _, v := range columns {
+ ncolumns = append(ncolumns, fmt.Sprintf("%s%s", prefix, v))
+ }
+ return strings.Join(ncolumns, ", ")
+}
diff --git a/src/main.go b/src/main.go
index 0687aae..4dc2caf 100644
--- a/src/main.go
+++ b/src/main.go
@@ -12,7 +12,9 @@ func main() {
config := conf.NewConf()
// connect database
// start server
- s := server.NewServer(config, db.NewDb(config))
+ database := db.NewDb(config)
+ defer database.Close()
+ s := server.NewServer(config, database)
s.BuildUi()
routers := []server.Routes{
user.Routes,
diff --git a/src/server/server.go b/src/server/server.go
index 9cb2a3f..c483069 100644
--- a/src/server/server.go
+++ b/src/server/server.go
@@ -4,23 +4,26 @@ import (
"net/http"
"github.com/go-chi/chi/v5"
+ "github.com/google/uuid"
"gitlab.com/alexkavon/newsstand/src/conf"
"gitlab.com/alexkavon/newsstand/src/db"
)
type Server struct {
- Router *chi.Mux
- Db *db.Database
- Config *conf.Conf
- Ui Ui
+ Router *chi.Mux
+ Db *db.Database
+ Config *conf.Conf
+ Ui Ui
+ Sessions map[string]session
}
func NewServer(config *conf.Conf, database *db.Database) *Server {
return &Server{
- Router: NewRouter(config),
- Db: database,
- Config: config,
- Ui: NewUi(config),
+ Router: NewRouter(config),
+ Db: database,
+ Config: config,
+ Ui: NewUi(config),
+ Sessions: map[string]session{},
}
}
@@ -37,3 +40,15 @@ func (s *Server) RegisterRoutes(routes Routes) {
func (s *Server) Serve() {
http.ListenAndServe(":"+s.Config.Server.Port, s.Router)
}
+
+func (s *Server) NewSession(w http.ResponseWriter, username string) {
+ token := uuid.NewString()
+ s.Sessions[token] = session{
+ username: username,
+ }
+
+ http.SetCookie(w, &http.Cookie{
+ Name: "session_token",
+ Value: token,
+ })
+}
diff --git a/src/server/session.go b/src/server/session.go
new file mode 100644
index 0000000..3a12215
--- /dev/null
+++ b/src/server/session.go
@@ -0,0 +1,9 @@
+package server
+
+type session struct {
+ username string
+}
+
+func (s *session) Username() string {
+ return s.username
+}
diff --git a/src/user/routes.go b/src/user/routes.go
index f826822..64018d0 100644
--- a/src/user/routes.go
+++ b/src/user/routes.go
@@ -1,9 +1,10 @@
package user
import (
- "fmt"
+ "log"
"net/http"
+ "github.com/go-playground/validator/v10"
"gitlab.com/alexkavon/newsstand/src/server"
)
@@ -11,7 +12,7 @@ var Routes = server.Routes{
server.Route{
Name: "Create",
Method: "GET",
- Path: "/user/create",
+ Path: "/u/create",
HandlerFunc: Create,
},
server.Route{
@@ -23,19 +24,19 @@ var Routes = server.Routes{
server.Route{
Name: "LoginForm",
Method: "GET",
- Path: "/user/auth",
+ Path: "/u/auth",
HandlerFunc: LoginForm,
},
server.Route{
Name: "Authenticate",
Method: "POST",
- Path: "/user/auth",
+ Path: "/u/auth",
HandlerFunc: Authenticate,
},
server.Route{
Name: "Me",
Method: "GET",
- Path: "/user/me",
+ Path: "/u/me",
AuthRequired: true,
HandlerFunc: Show,
},
@@ -50,8 +51,36 @@ func Create(s *server.Server) http.HandlerFunc {
func Store(s *server.Server) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
r.ParseForm()
- fmt.Println(r.PostForm)
- s.Ui.RenderTemplate(w, "core", "messages", &struct{ Message string }{Message: "Congrats"})
+
+ user := &User{
+ Db: s.Db,
+ Username: r.PostFormValue("username"),
+ Secret: r.PostFormValue("secret"),
+ Email: r.PostFormValue("email"),
+ }
+ // Validate User Input
+ v := validator.New()
+ err := v.Struct(user)
+ if err != nil {
+ log.Println("Validator failed", err.(validator.ValidationErrors))
+ }
+
+ // Hash secret
+ err = user.HashSecret()
+ if err != nil {
+ log.Println("Hash failure", err)
+ }
+
+ // Store user
+ err = user.Insert()
+ if err != nil {
+ log.Println("Insert Error", err)
+ }
+ // Send email validation
+ // Create cookie session
+ s.NewSession(w, user.Username)
+ // Redirect to user profile
+ http.Redirect(w, r, "/u/me", http.StatusSeeOther)
}
}
@@ -66,5 +95,16 @@ func Authenticate(s *server.Server) http.HandlerFunc {
}
func Show(s *server.Server) http.HandlerFunc {
- return func(w http.ResponseWriter, r *http.Request) {}
+ return func(w http.ResponseWriter, r *http.Request) {
+ token, err := r.Cookie("session_token")
+ if err != nil {
+ s.Ui.Render(w, "user/login", &struct{ Message string }{"You are not logged in! Missing Cookie"})
+ }
+ session, ok := s.Sessions[token.Value]
+ if !ok {
+ s.Ui.Render(w, "user/login", &struct{ Message string }{"You are not logged in! With Session."})
+ }
+
+ s.Ui.Render(w, "user/me", &struct{ Message, Username string }{"Congrats on getting this far!", session.Username()})
+ }
}
diff --git a/src/user/user.go b/src/user/user.go
index 83e39e3..e35c7c5 100644
--- a/src/user/user.go
+++ b/src/user/user.go
@@ -1,18 +1,73 @@
package user
import (
- "errors"
+ "crypto/rand"
+ "encoding/base64"
+ "fmt"
"time"
+
+ "github.com/jackc/pgx/v5"
+ "gitlab.com/alexkavon/newsstand/src/db"
+ "golang.org/x/crypto/argon2"
)
type User struct {
Id int64
- Username string
+ Username string `validate:"required,max=50"`
+ Secret string `validate:"required,min=8,max=128"`
+ Email string `validate:"required,email"`
Karma uint64
UpdatedAt time.Time
CreatedAt time.Time
+ hash string
+ Db *db.Database
+}
+
+func (u *User) Insert() error {
+ err := u.Db.InsertTable(
+ "users",
+ []string{"username", "secret", "email"},
+ pgx.NamedArgs{"username": u.Username, "secret": string(u.hash), "email": u.Email},
+ )
+ if err != nil {
+ return err
+ }
+ return nil
}
-func NewUser(username, secret string) error {
- return errors.New("Not Implemented")
+func (u *User) HashSecret() error {
+ hashconf := &struct {
+ memory uint32
+ iterations uint32
+ parallelism uint8
+ keyLength uint32
+ saltLength uint32
+ }{64 * 1024, 3, 2, 12, 16}
+ salt := make([]byte, hashconf.saltLength)
+ _, err := rand.Read(salt)
+ if err != nil {
+ return err
+ }
+
+ hash := argon2.IDKey(
+ []byte(u.Secret),
+ salt,
+ hashconf.iterations,
+ hashconf.memory,
+ hashconf.parallelism,
+ hashconf.keyLength,
+ )
+ b64Salt := base64.RawStdEncoding.EncodeToString(salt)
+ b64Hash := base64.RawStdEncoding.EncodeToString(hash)
+ encodedHash := fmt.Sprintf(
+ "$argon2id$v=%d$m=%d,t=%d,p=%d$%s$%s",
+ argon2.Version,
+ hashconf.memory,
+ hashconf.iterations,
+ hashconf.parallelism,
+ b64Salt,
+ b64Hash,
+ )
+ u.hash = encodedHash
+ return nil
}
diff --git a/ui/pages/user/create.tmpl.html b/ui/pages/user/create.tmpl.html
index 5e1087a..47143c6 100644
--- a/ui/pages/user/create.tmpl.html
+++ b/ui/pages/user/create.tmpl.html
@@ -2,7 +2,7 @@
{{define "main"}}
<h1>Create User</h1>
- <form hx-post="/user" action="/user" method="POST" hx-target="#messages" hx-swap="outerHTML">
+ <form action="/user" method="POST">
<label>
Email
<input type="email" placeholder="email" name="email" />
@@ -12,9 +12,9 @@
<input type="text" placeholder="username" name="username" />
</label>
<label>
- Password
- <input type="password" placeholder="password" name="password" />
+ Secret
+ <input type="password" placeholder="secret" name="secret" />
</label>
- <button type="submit" hx-disabled-elt="this">Create</button>
+ <button type="submit">Create</button>
</form>
{{end}}
diff --git a/ui/pages/user/me.tmpl.html b/ui/pages/user/me.tmpl.html
new file mode 100644
index 0000000..3f3950c
--- /dev/null
+++ b/ui/pages/user/me.tmpl.html
@@ -0,0 +1,5 @@
+{{ define "title"}}Profile{{end}}
+
+{{ define "main"}}
+<h1>Welcome, {{.Username}}</h1>
+{{end}}