Compare commits

...

22 Commits

Author SHA1 Message Date
1639dd787f Add template function experimental cmd 2024-08-14 12:32:31 -04:00
9c299bc709 Merge from main 2024-08-14 12:30:04 -04:00
56f98a9c14 Set cookie to http only 2024-08-13 11:39:19 -04:00
4cf50a7d81 Use a custom template function for csrf protection 2024-08-13 06:58:02 -04:00
8bc58eedbe Add request data to template Execute function 2024-08-12 18:55:57 -04:00
da5eeb3f0f Refactor FromFile constructor 2024-08-12 18:04:22 -04:00
de681c1ac3 Add csrf protection 2024-08-11 20:23:43 -04:00
faf9139d79 Add cookie 2024-08-08 15:44:19 -04:00
951c081680 Add sign in handler 2024-08-08 12:22:56 -04:00
c4b5dcedf9 Add user authentication function 2024-08-07 19:33:41 -04:00
2d53824194 Add Sign in page 2024-08-07 19:09:34 -04:00
7d234c5aad Connect users controller to db 2024-08-07 12:19:32 -04:00
fef066b449 Add user model 2024-08-07 00:15:41 -04:00
166049f4ff bcrypt 2024-08-06 12:35:27 -04:00
4f815151eb Sql 2024-08-05 22:42:28 -04:00
1fcbac610f Add query param autofill to form 2024-08-04 08:49:58 -04:00
1156cabe05 Add user signup controller 2024-08-03 07:34:27 -04:00
82b954af2e Add css to signup page 2024-08-01 22:40:41 -04:00
8eb491fa97 Add signup page 2024-08-01 21:31:39 -04:00
5b8e8017ca Add nav bar 2024-08-01 20:50:33 -04:00
45ff8dd334 Add tailwind v2 CSS framework 2024-08-01 19:01:51 -04:00
815c6a689d Add tailwind v2 CSS framework 2024-08-01 18:25:25 -04:00
17 changed files with 889 additions and 51 deletions

41
cmd/bcrypt/bcrypt.go Normal file
View File

@@ -0,0 +1,41 @@
package main
import (
"fmt"
"os"
"golang.org/x/crypto/bcrypt"
)
func main() {
switch os.Args[1] {
case "hash":
hash(os.Args[2])
case "compare":
compare(os.Args[2], os.Args[3])
default:
fmt.Printf("Invalid command: %v\n", os.Args[1])
}
}
func hash(password string) {
fmt.Printf("Hash the password %q\n", password)
hashedBytes, err := bcrypt.GenerateFromPassword(
[]byte(password), bcrypt.DefaultCost)
if err != nil {
fmt.Printf("error hashing: %v\n", err)
return
}
hash := string(hashedBytes)
fmt.Println(hash)
}
func compare(password, hash string) {
fmt.Printf("Compare the password %q with the hash %q\n", password, hash)
err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
if err != nil {
fmt.Printf("Password is invalid: %v\n", password)
return
}
fmt.Println("Password is correct!")
}

144
cmd/exp/exp.go Normal file
View File

@@ -0,0 +1,144 @@
package main
import (
"database/sql"
"fmt"
_ "github.com/jackc/pgx/v4/stdlib"
)
func AddUser(db *sql.DB, name string, email string) (error, int) {
row := db.QueryRow(`
INSERT INTO users(name, email)
VALUES($1, $2)
RETURNING id;
`, name, email)
var id int
err := row.Scan(&id)
return err, id
}
func QueryUser(db *sql.DB, id int) (error, string, string) {
row := db.QueryRow(`
SELECT name, email
FROM users
WHERE id=$1;`, id)
var name, email string
err := row.Scan(&name, &email)
return err, name, email
}
type Order struct {
ID int
UID int
Amount int
Desc string
}
func AddOrder(db *sql.DB, uid int, amount int, desc string) (error, int) {
row := db.QueryRow(`
insert into orders(user_id, amount, description)
values($1, $2, $3)
returning id;
`, uid, amount, desc)
var id int
err := row.Scan(&id)
return err, id
}
func QueryOrders(db *sql.DB, uid int) ([]Order, error) {
var orders []Order
rows, err := db.Query(`
select id, amount, description from orders where user_id=$1
`, uid)
if err != nil {
return orders, err
}
defer rows.Close()
for rows.Next() {
var order Order
order.UID = uid
err := rows.Scan(&order.ID, &order.Amount, &order.Desc)
if err != nil {
return orders, err
}
orders = append(orders, order)
}
err = rows.Err()
return orders, err
}
func main() {
db, err := sql.Open("pgx", "host=localhost port=5432 user=baloo dbname=lenslocked sslmode=disable")
if err != nil {
panic(err)
}
defer db.Close()
err = db.Ping()
if err != nil {
panic(err)
}
fmt.Println("Connected!")
_, err = db.Exec(`
create table if not exists users (
id serial primary key,
name text,
email text not null
);
create table if not exists orders (
id serial primary key,
user_id int not null,
amount int,
description text
);`)
if err != nil {
panic(err)
}
fmt.Println("Tables created")
var name string
var email string
// Create User
/*
name = "Jon Rob Baker"
email = "demo@user.com"
err, id := AddUser(db, name, email)
if err != nil {
panic(err)
}
fmt.Println("User ", id, " created")
*/
// Query User
err, name, email = QueryUser(db, 5)
if err == sql.ErrNoRows {
fmt.Println("Error, no rows!")
}
if err != nil {
panic(err)
}
fmt.Printf("User information: name=%s, email=%s\n", name, email)
uid := 5
// Create fake orders
/*
for i := 1; i <= 5; i++ {
amount := i * 100
desc := fmt.Sprintf("Fake order #%d", i)
err, _ := AddOrder(db, uid, amount, desc)
if err != nil {
panic(err)
}
}
fmt.Println("Created fake orders")
*/
// Query Orders
orders, err := QueryOrders(db, uid)
if err != nil {
panic(err)
}
fmt.Println("Orders: ", orders)
}

26
cmd/tplfn/tplfn.go Normal file
View File

@@ -0,0 +1,26 @@
package main
import (
"fmt"
"html/template"
"os"
)
func main() {
tplstr := "hello {{myFunc}}\n"
fmap := template.FuncMap{}
fmap["myFunc"] = func() (string, error) {
return "world", fmt.Errorf("hi")
}
tpl, err := template.New("demo").Funcs(fmap).Parse(tplstr)
if err != nil {
panic(fmt.Errorf("Error creating template: %v", err))
}
err = tpl.Execute(os.Stdout, nil)
if err != nil {
panic(err)
}
}

View File

@@ -4,22 +4,24 @@ import (
"git.kealoha.me/lks/lenslocked/templates"
"git.kealoha.me/lks/lenslocked/views"
"net/http"
"strings"
)
func StaticTemplate(templatePath string) http.HandlerFunc {
tpl := views.Must(views.FromFS(templates.FS, templatePath))
type Template interface {
Execute(w http.ResponseWriter, r *http.Request, data interface{})
}
var testWriter strings.Builder
err := tpl.ExecuteWriter(&testWriter, nil)
func StaticController(templatePath ...string) http.HandlerFunc {
tpl := views.Must(views.FromFS(templates.FS, templatePath...))
err := tpl.TestTemplate(nil)
if err != nil {
panic(err)
}
return func(w http.ResponseWriter, r *http.Request) { tpl.Execute(w, nil) }
return func(w http.ResponseWriter, r *http.Request) { tpl.Execute(w, r, nil) }
}
func FAQ(templatePath string) http.HandlerFunc {
func FAQ(templatePath ...string) http.HandlerFunc {
questions := []struct {
Question string
Answer string
@@ -34,15 +36,14 @@ func FAQ(templatePath string) http.HandlerFunc {
},
}
tpl := views.Must(views.FromFS(templates.FS, templatePath))
tpl := views.Must(views.FromFS(templates.FS, templatePath...))
var testWriter strings.Builder
err := tpl.ExecuteWriter(&testWriter, nil)
err := tpl.TestTemplate(nil)
if err != nil {
panic(err)
}
return func(w http.ResponseWriter, r *http.Request) {
tpl.Execute(w, questions)
tpl.Execute(w, r, questions)
}
}

104
controllers/users.go Normal file
View File

@@ -0,0 +1,104 @@
package controllers
import (
"fmt"
"net/http"
"git.kealoha.me/lks/lenslocked/models"
"git.kealoha.me/lks/lenslocked/templates"
"git.kealoha.me/lks/lenslocked/views"
)
type Users struct {
Templates struct {
Signup Template
Signin Template
}
UserService *models.UserService
}
func (u Users) GetSignup(w http.ResponseWriter, r *http.Request) {
var data struct {
Email string
}
data.Email = r.FormValue("email")
u.Templates.Signup.Execute(w, r, data)
}
func (u Users) PostSignup(w http.ResponseWriter, r *http.Request) {
email := r.FormValue("email")
password := r.FormValue("password")
user, err := u.UserService.Create(email, password)
if err != nil {
fmt.Println(err)
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
return
}
fmt.Fprintf(w, "User created: %+v", user)
}
func (u Users) GetSignin(w http.ResponseWriter, r *http.Request) {
var data struct {
Email string
}
data.Email = r.FormValue("email")
u.Templates.Signin.Execute(w, r, data)
}
func (u Users) PostSignin(w http.ResponseWriter, r *http.Request) {
var data struct {
Email string
Password string
}
data.Email = r.FormValue("email")
data.Password = r.FormValue("password")
user, err := u.UserService.Authenticate(data.Email, data.Password)
if err != nil {
fmt.Println(err)
http.Error(w, "Something went wrong.", http.StatusInternalServerError)
return
}
// Bad cookie
cookie := http.Cookie{
Name: "bad",
Value: user.Email,
Path: "/",
HttpOnly: true,
}
http.SetCookie(w, &cookie)
fmt.Fprintf(w, "User authenticated: %+v", user)
}
func (u Users) CurrentUser(w http.ResponseWriter, r *http.Request) {
email, err := r.Cookie("bad")
if err != nil {
fmt.Fprint(w, "The bad cookie could not be read.")
return
}
fmt.Fprintf(w, "Bad cookie: %s\n", email.Value)
}
func WithTemplates(user_service *models.UserService, signup Template, signin Template) Users {
u := Users{}
u.Templates.Signup = signup
u.Templates.Signin = signin
u.UserService = user_service
return u
}
func Default(user_service *models.UserService, templatePath ...string) Users {
signup_tpl := views.Must(views.FromFS(templates.FS, "signup.gohtml", "tailwind.gohtml"))
signin_tpl := views.Must(views.FromFS(templates.FS, "signin.gohtml", "tailwind.gohtml"))
err := signup_tpl.TestTemplate(nil)
if err != nil {
panic(err)
}
err = signin_tpl.TestTemplate(nil)
if err != nil {
panic(err)
}
return WithTemplates(user_service, signup_tpl, signin_tpl)
}

19
go.mod
View File

@@ -2,4 +2,21 @@ module git.kealoha.me/lks/lenslocked
go 1.22.5
require github.com/go-chi/chi/v5 v5.1.0 // indirect
require (
github.com/go-chi/chi/v5 v5.1.0
github.com/jackc/pgx/v4 v4.18.3
golang.org/x/crypto v0.26.0
)
require (
github.com/gorilla/csrf v1.7.2
github.com/gorilla/securecookie v1.1.2 // indirect
github.com/jackc/chunkreader/v2 v2.0.1 // indirect
github.com/jackc/pgconn v1.14.3 // indirect
github.com/jackc/pgio v1.0.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgproto3/v2 v2.3.3 // indirect
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
github.com/jackc/pgtype v1.14.0 // indirect
golang.org/x/text v0.17.0 // indirect
)

186
go.sum
View File

@@ -1,2 +1,188 @@
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc=
github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I=
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
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/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw=
github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw=
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/gorilla/csrf v1.7.2 h1:oTUjx0vyf2T+wkrx09Trsev1TE+/EbDAeHtSTbtC2eI=
github.com/gorilla/csrf v1.7.2/go.mod h1:F1Fj3KG23WYHE6gozCmBAezKookxbIvUJT+121wTuLk=
github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA=
github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo=
github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=
github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8=
github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA=
github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE=
github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s=
github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o=
github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY=
github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
github.com/jackc/pgconn v1.14.3 h1:bVoTr12EGANZz66nZPkMInAV/KHD2TxH9npjXXgiB3w=
github.com/jackc/pgconn v1.14.3/go.mod h1:RZbme4uasqzybK2RK5c65VsHxoyaml09lx3tXOcO/VM=
github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE=
github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8=
github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE=
github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c=
github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5Wi/+Zz7xoE5ALHsRQlOctkOiHc=
github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak=
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/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78=
github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA=
github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg=
github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
github.com/jackc/pgproto3/v2 v2.3.3 h1:1HLSx5H+tXR9pW3in3zaztoEwQYRC9SQaYUHjTSUOag=
github.com/jackc/pgproto3/v2 v2.3.3/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg=
github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc=
github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw=
github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM=
github.com/jackc/pgtype v1.14.0 h1:y+xUdabmyMkJLyApYuPj38mW+aAIqCe5uuBB51rH3Vw=
github.com/jackc/pgtype v1.14.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y=
github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM=
github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=
github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs=
github.com/jackc/pgx/v4 v4.18.3 h1:dE2/TrEsGX3RBprb3qryqSV9Y60iZN1C6i8IrmW9/BA=
github.com/jackc/pgx/v4 v4.18.3/go.mod h1:Ey4Oru5tH5sB6tV7hDmfWFahwF15Eb7DNXlRKx2CkVw=
github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8=
github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
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/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
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=
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=

49
main.go
View File

@@ -1,27 +1,68 @@
package main
import (
"database/sql"
"fmt"
"net/http"
"os"
ctrlrs "git.kealoha.me/lks/lenslocked/controllers"
"git.kealoha.me/lks/lenslocked/models"
"github.com/go-chi/chi/v5"
"github.com/gorilla/csrf"
"github.com/go-chi/chi/v5/middleware"
_ "github.com/jackc/pgx/v4/stdlib"
)
const DEBUG bool = true
func notFoundHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/html; charset=utf8")
w.WriteHeader(http.StatusNotFound)
fmt.Fprint(w, "404 page not found")
}
func ConnectDB() *sql.DB {
db, err := sql.Open("pgx", os.Getenv("LENSLOCKED_DB_STRING"))
if err != nil {
panic(fmt.Sprint("Error connecting to database: %w", err))
}
err = db.Ping()
if err != nil {
panic(fmt.Sprint("Error connecting to database: %w", err))
}
return db
}
func main() {
csrfKey := []byte(os.Getenv("LENSLOCKED_CSRF_KEY"))
if len(csrfKey) < 32 {
panic("Error: bad csrf protection key")
}
db := ConnectDB()
defer db.Close()
userService := models.UserService{DB: db}
var usersCtrlr ctrlrs.Users = ctrlrs.Default(&userService)
r := chi.NewRouter()
r.Use(middleware.Logger)
r.Get("/", ctrlrs.StaticTemplate("home.gohtml"))
r.Get("/contact", ctrlrs.StaticTemplate("contact.gohtml"))
r.Get("/faq", ctrlrs.FAQ("faq.gohtml"))
r.Get("/", ctrlrs.StaticController("home.gohtml", "tailwind.gohtml"))
r.Get("/contact", ctrlrs.StaticController("contact.gohtml", "tailwind.gohtml"))
r.Get("/faq", ctrlrs.FAQ("faq.gohtml", "tailwind.gohtml"))
r.Get("/signup", usersCtrlr.GetSignup)
r.Post("/signup", usersCtrlr.PostSignup)
r.Get("/signin", usersCtrlr.GetSignin)
r.Post("/signin", usersCtrlr.PostSignin)
r.Get("/user", usersCtrlr.CurrentUser)
r.NotFound(notFoundHandler)
fmt.Println("Starting the server on :3000...")
http.ListenAndServe(":3000", r)
http.ListenAndServe(":3000", csrf.Protect(csrfKey, csrf.Secure(!DEBUG))(r))
}

5
models/sql/users.sql Normal file
View File

@@ -0,0 +1,5 @@
CREATE TABLE users (
id SERIAL PRIMARY KEY,
email TEXT UNIQUE NOT NULL,
password_hash TEXT NOT NULL
);

64
models/user.go Normal file
View File

@@ -0,0 +1,64 @@
package models
import (
"database/sql"
"fmt"
"strings"
"golang.org/x/crypto/bcrypt"
)
type User struct {
ID int
Email string
PasswordHash string
}
type UserService struct {
DB *sql.DB
}
func (us *UserService) Create(email, password string) (*User, error) {
email = strings.ToLower(email)
hashedBytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil {
return nil, fmt.Errorf("create user: %w", err)
}
passwordHash := string(hashedBytes)
user := User{
Email: email,
PasswordHash: passwordHash,
}
row := us.DB.QueryRow(`
INSERT INTO users (email, password_hash)
VALUES ($1, $2) RETURNING id
`, email, passwordHash)
err = row.Scan(&user.ID)
if err != nil {
return nil, fmt.Errorf("create user: %w", err)
}
return &user, nil
}
func (us UserService) Authenticate(email, password string) (*User, error) {
user := User{
Email: strings.ToLower(email),
}
row := us.DB.QueryRow(`
SELECT id, password_hash
FROM users WHERE email=$1
`, email)
err := row.Scan(&user.ID, &user.PasswordHash)
if err != nil {
return nil, fmt.Errorf("authenticate: %w", err)
}
err = bcrypt.CompareHashAndPassword([]byte(user.PasswordHash), []byte(password))
if err != nil {
return nil, fmt.Errorf("authenticate: %w", err)
}
return &user, nil
}

View File

@@ -1,2 +1,12 @@
<h1>Contact Page</h1>
<p>To get in touch, email me at <a href="mailto:example@example.com">example@example.com</a></p>
<!doctype html>
<html>
{{template "head" .}}
<body>
{{template "header".}}
<main class="px-6">
<h1 class="py-4 text-4xl semibold tracking-tight font-sans">Contact Page</h1>
<p>To get in touch, email me at <a class="underline" href="mailto:example@example.com">example@example.com</a></p>
</main>
{{template "footer" .}}
</body>
</html>

View File

@@ -1,10 +1,27 @@
<h1>FAQ</h1>
<hr>
{{range .}}
<!doctype html>
<html>
{{template "head" .}}
<body>
{{template "header".}}
<main class="px-6">
<h1 class="py-4 text-4xl semibold tracking-tight">FAQ</h1>
<hr class="pb-4">
<div class="flex flex-col gap-8">
{{range .}}
{{template "qa" .}}
{{end}}
{{end}}
</div>
</main>
{{template "footer" .}}
</body>
</html>
{{define "qa"}}
<h3>{{.Question}}</h3>
<p>{{.Answer}}</p>
<div>
<h3 class="block text-lg semibold">{{.Question}}</h3>
<p class="block text-sm ml-2">{{.Answer}}</p>
</div>
{{end}}

View File

@@ -1,15 +1,29 @@
<h1>Welcome to my awesome site!</h1>
{{template "lorem-ipsum"}}
{{template "lorem-ipsum"}}
{{template "lorem-ipsum"}}
<!doctype html>
<html>
{{template "head" .}}
<body>
{{template "header".}}
<main class="px-6">
<h1 class="py-4 text-4xl semibold tracking-tight">
Welcome to my awesome site!
</h1>
<div class="flex flex-col gap-4">
{{template "lorem-ipsum"}}
{{template "lorem-ipsum"}}
{{template "lorem-ipsum"}}
</div>
</main>
{{template "footer" .}}
</body>
</html>
{{define "lorem-ipsum"}}
<p>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis
nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore
eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt
in culpa qui officia deserunt mollit anim id est laborum.
</p>
</p>
{{end}}

65
templates/signin.gohtml Normal file
View File

@@ -0,0 +1,65 @@
<!doctype html>
<html>
{{template "head" .}}
<body class="min-h-screen bg-gray-100">
{{template "header".}}
<main class="py-12 flex justify-center">
<div class="px-8 py-8 bg-white rounded shadow">
<h1 class="pt-4 pb-8 text-center text-3xl font-bold text-gray-900">
Welcome back!
</h1>
<form action="/signin" method="post">
{{csrfField}}
<div class="py-2">
<label for="email" class="text-sm font-semibold text-gray-800">
Email Address
</label>
<input
name="email"
id="email"
type="email"
placeholder="Email address"
required
autocomplete="email"
class="w-full px-3 py-2 border border-gray-300 placeholder-gray-500
text-gray-800 rounded"
value="{{.Email}}"
{{if not .Email}}autofocus{{end}}
/>
</div>
<div class="py-2">
<label for="password" class="text-sm font-semibold text-gray-800">
Password
</label>
<input
name="password"
id="password"
type="password"
placeholder="Password"
required
class="w-full px-3 py-2 border border-gray-300 placeholder-gray-500
text-gray-800 rounded"
{{if .Email}}autofocus{{end}}
/>
</div>
<div class="py-4">
<button class="w-full py-4 px-2 bg-indigo-600 hover:bg-indigo-700
text-white rounded font-bold text-lg">
Sign in
</button>
</div>
<div class="py-2 w-full flex justify-between">
<p class="text-xs text-gray-500">
Need an account?
<a href="/signup" class="underline">Sign up</a>
</p>
<p class="text-xs text-gray-500">
<a href="/reset-pw" class="underline">Forgot your password?</a>
</p>
</div>
</form>
</div>
</main>
{{template "footer" .}}
</body>
</html>

53
templates/signup.gohtml Normal file
View File

@@ -0,0 +1,53 @@
<!doctype html>
<html>
{{template "head" .}}
<body class="min-h-screen bg-gray-100">
{{template "header".}}
<main class="px-6">
<div class="py-12 flex justify-center">
<div class="px-8 py-8 bg-white rounded shadow">
<h1 class="pt-4 pb-8 text-center text-3xl font-bold text-gray-900">
Sign Up!
</h1>
<form action="/signup" method="post">
{{csrfField}}
<div>
<label for="signupEmail" class="text-sm font-semibold text-gray-800">Email Address</label>
<input name="email" id="signupEmail" type="email" placeholder="Email address" required autocomplete="email"
class="w-full px-3 py-2 boarder boarder-gray-300 placeholder-gray-500 text-gray-800 rounded"
value="{{.Email}}"
{{if not .Email}}autofocus{{end}}
/>
</div>
<div>
<label for="signupPassword" class="text-sm font-semibold text-gray-800">Password</label>
<input name="password" id="signupPassword" type="password" placeholder="password" required
class="w-full px-3 py-2 boarder boarder-gray-300 placeholder-gray-500 text-gray-800 rounded"
{{if .Email}}autofocus{{end}}
/>
</div>
<div>
<label for="referalCode" class="text-sm font-semibold text-gray-800">Referal Code</label>
<input name="referalCode" id="referalCode" type="text" placeholder="Referal Code" required
class="w-full px-3 py-2 boarder boarder-gray-300 placeholder-gray-500 text-gray-800 rounded"
/>
</div>
<div>
<button type="submit" class="w-full py-4 px-2 bg-indigo-600 hover:bg-indigo-700 text-white rounded font-bold text-lg">Sign up</button>
</div>
<div class="py-2 w-full flex justify-between">
<p class="text-xs text-gray-500">
Already have an account?
<a href="/signin" class="underline">Sign in</a>
</p>
<p class="text-xs text-gray-500">
<a href="/reset-pw" class="underline">Forgot your password?</a>
</p>
</div>
</form>
</div>
</div>
</main>
{{template "footer" .}}
</body>
</html>

30
templates/tailwind.gohtml Normal file
View File

@@ -0,0 +1,30 @@
{{define "head"}}
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link href="https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css" rel="stylesheet">
</head>
{{end}}
{{define "header"}}
<header class="bg-gradient-to-r from-blue-800 to-indigo-800 text-white">
<nav class="px-6 py-6 flex items-center space-x-12">
<div class="text-2xl font-serif">Lenslocked</div>
<div class="flex-grow">
<a class="text-base font-semibold hover:text-blue-100 pr-8" href="/">Home</a>
<a class="text-base font-semibold hover:text-blue-100 pr-8" href="/contact">Contact</a>
<a class="text-base font-semibold hover:text-blue-100 pr-8" href="/faq">FAQ</a>
</div>
<div class="space-x-4">
<a href="/signin">Sign in</a>
<a href="/signup" clss="px-4 py-2 bg-blue-700 hover:bg-blue-600 rounded">Sign up</a>
</div>
</nav>
</header>
{{end}}
{{define "footer"}}
<br>
<hr>
<p class="text-gray-400 font-light">Copyright your mom 2057</p>
{{end}}

View File

@@ -3,40 +3,60 @@ package views
import (
"fmt"
"html/template"
"io"
"io/fs"
"log"
"net/http"
"os"
"strings"
"github.com/gorilla/csrf"
)
type Template struct {
htmlTpl *template.Template
}
func (t Template) Execute(w http.ResponseWriter, data interface{}) {
func (t Template) Execute(w http.ResponseWriter, r *http.Request, data interface{}) {
tpl, err := t.htmlTpl.Clone()
if err != nil {
log.Printf("Template Clone Error: %v", err)
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
return
}
tpl = tpl.Funcs(template.FuncMap{
"csrfField": func() template.HTML { return csrf.TemplateField(r) },
})
w.Header().Set("Content-Type", "text/html; charset=utf8")
err := t.htmlTpl.Execute(w, data)
err = tpl.Execute(w, data)
if err != nil {
log.Printf("Error executing template: %v", err)
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
return
}
}
func (t Template) ExecuteWriter(w io.Writer, data interface{}) error {
return t.htmlTpl.Execute(w, data)
func (t Template) TestTemplate(data interface{}) error {
var testWriter strings.Builder
tpl, err := t.htmlTpl.Clone()
if err != nil {
return err
}
return tpl.Execute(&testWriter, data)
}
func FromFile(filepath string) (Template, error) {
tpl, err := template.ParseFiles(filepath)
if err != nil {
return Template{}, fmt.Errorf("Error parsing template: %v", err)
}
return Template{
htmlTpl: tpl,
}, nil
func FromFile(pattern ...string) (Template, error) {
fs := os.DirFS(".")
return FromFS(fs, pattern...)
}
func FromFS(fs fs.FS, pattern ...string) (Template, error) {
tpl, err := template.ParseFS(fs, pattern...)
tpl := template.New(pattern[0])
tpl = tpl.Funcs(template.FuncMap{
"csrfField": func() template.HTML {
return `<div class="hidden">STUB: PLACEHOLDER</div>`
},
})
tpl, err := tpl.ParseFS(fs, pattern...)
if err != nil {
return Template{}, fmt.Errorf("Error parsing template: %v", err)
}