Compare commits
4 Commits
40ed60bca6
...
exp
| Author | SHA1 | Date | |
|---|---|---|---|
| 1639dd787f | |||
| 9c299bc709 | |||
| 166049f4ff | |||
| 4f815151eb |
41
cmd/bcrypt/bcrypt.go
Normal file
41
cmd/bcrypt/bcrypt.go
Normal 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
144
cmd/exp/exp.go
Normal 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
26
cmd/tplfn/tplfn.go
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,7 +15,6 @@ type Users struct {
|
|||||||
Signin Template
|
Signin Template
|
||||||
}
|
}
|
||||||
UserService *models.UserService
|
UserService *models.UserService
|
||||||
SessionService *models.SessionService
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u Users) GetSignup(w http.ResponseWriter, r *http.Request) {
|
func (u Users) GetSignup(w http.ResponseWriter, r *http.Request) {
|
||||||
@@ -35,22 +34,7 @@ func (u Users) PostSignup(w http.ResponseWriter, r *http.Request) {
|
|||||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
session, err := u.SessionService.Create(user.ID)
|
fmt.Fprintf(w, "User created: %+v", user)
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
http.Redirect(w, r, "/signin", http.StatusFound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
cookie := http.Cookie{
|
|
||||||
Name: "session",
|
|
||||||
Value: session.Token,
|
|
||||||
Path: "/",
|
|
||||||
HttpOnly: true,
|
|
||||||
}
|
|
||||||
http.SetCookie(w, &cookie)
|
|
||||||
|
|
||||||
http.Redirect(w, r, "/user", http.StatusFound)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u Users) GetSignin(w http.ResponseWriter, r *http.Request) {
|
func (u Users) GetSignin(w http.ResponseWriter, r *http.Request) {
|
||||||
@@ -74,70 +58,36 @@ func (u Users) PostSignin(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
session, err := u.SessionService.Create(user.ID)
|
// Bad cookie
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
cookie := http.Cookie{
|
cookie := http.Cookie{
|
||||||
Name: "session",
|
Name: "bad",
|
||||||
Value: session.Token,
|
Value: user.Email,
|
||||||
Path: "/",
|
Path: "/",
|
||||||
HttpOnly: true,
|
HttpOnly: true,
|
||||||
}
|
}
|
||||||
http.SetCookie(w, &cookie)
|
http.SetCookie(w, &cookie)
|
||||||
|
|
||||||
http.Redirect(w, r, "/user", http.StatusFound)
|
fmt.Fprintf(w, "User authenticated: %+v", user)
|
||||||
}
|
|
||||||
|
|
||||||
func (u Users) GetSignout(w http.ResponseWriter, r *http.Request) {
|
|
||||||
sessionCookie, err := r.Cookie("session")
|
|
||||||
if err != nil {
|
|
||||||
http.Redirect(w, r, "/signin", http.StatusFound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = u.SessionService.Delete(sessionCookie.Value)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c := http.Cookie{
|
|
||||||
Name: "session",
|
|
||||||
MaxAge: -1,
|
|
||||||
}
|
|
||||||
http.SetCookie(w, &c)
|
|
||||||
http.Redirect(w, r, "/signin", http.StatusFound)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u Users) CurrentUser(w http.ResponseWriter, r *http.Request) {
|
func (u Users) CurrentUser(w http.ResponseWriter, r *http.Request) {
|
||||||
seshCookie, err := r.Cookie("session")
|
email, err := r.Cookie("bad")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Fprint(w, "The bad cookie could not be read.")
|
||||||
http.Redirect(w, r, "/signin", http.StatusFound)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
user, err := u.SessionService.User(seshCookie.Value)
|
fmt.Fprintf(w, "Bad cookie: %s\n", email.Value)
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
http.Redirect(w, r, "/signin", http.StatusFound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
fmt.Fprintf(w, "Current user: %s\n", user.Email)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func WithTemplates(user_service *models.UserService, session_service *models.SessionService, signup Template, signin Template) Users {
|
func WithTemplates(user_service *models.UserService, signup Template, signin Template) Users {
|
||||||
u := Users{}
|
u := Users{}
|
||||||
u.Templates.Signup = signup
|
u.Templates.Signup = signup
|
||||||
u.Templates.Signin = signin
|
u.Templates.Signin = signin
|
||||||
u.UserService = user_service
|
u.UserService = user_service
|
||||||
u.SessionService = session_service
|
|
||||||
return u
|
return u
|
||||||
}
|
}
|
||||||
|
|
||||||
func Default(user_service *models.UserService, session_service *models.SessionService) Users {
|
func Default(user_service *models.UserService, templatePath ...string) Users {
|
||||||
signup_tpl := views.Must(views.FromFS(templates.FS, "signup.gohtml", "tailwind.gohtml"))
|
signup_tpl := views.Must(views.FromFS(templates.FS, "signup.gohtml", "tailwind.gohtml"))
|
||||||
signin_tpl := views.Must(views.FromFS(templates.FS, "signin.gohtml", "tailwind.gohtml"))
|
signin_tpl := views.Must(views.FromFS(templates.FS, "signin.gohtml", "tailwind.gohtml"))
|
||||||
|
|
||||||
@@ -150,5 +100,5 @@ func Default(user_service *models.UserService, session_service *models.SessionSe
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return WithTemplates(user_service, session_service, signup_tpl, signin_tpl)
|
return WithTemplates(user_service, signup_tpl, signin_tpl)
|
||||||
}
|
}
|
||||||
|
|||||||
5
go.mod
5
go.mod
@@ -6,11 +6,10 @@ require (
|
|||||||
github.com/go-chi/chi/v5 v5.1.0
|
github.com/go-chi/chi/v5 v5.1.0
|
||||||
github.com/jackc/pgx/v4 v4.18.3
|
github.com/jackc/pgx/v4 v4.18.3
|
||||||
golang.org/x/crypto v0.26.0
|
golang.org/x/crypto v0.26.0
|
||||||
google.golang.org/grpc v1.65.0
|
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/gorilla/csrf v1.7.2 // indirect
|
github.com/gorilla/csrf v1.7.2
|
||||||
github.com/gorilla/securecookie v1.1.2 // indirect
|
github.com/gorilla/securecookie v1.1.2 // indirect
|
||||||
github.com/jackc/chunkreader/v2 v2.0.1 // indirect
|
github.com/jackc/chunkreader/v2 v2.0.1 // indirect
|
||||||
github.com/jackc/pgconn v1.14.3 // indirect
|
github.com/jackc/pgconn v1.14.3 // indirect
|
||||||
@@ -19,7 +18,5 @@ require (
|
|||||||
github.com/jackc/pgproto3/v2 v2.3.3 // indirect
|
github.com/jackc/pgproto3/v2 v2.3.3 // indirect
|
||||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
|
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
|
||||||
github.com/jackc/pgtype v1.14.0 // indirect
|
github.com/jackc/pgtype v1.14.0 // indirect
|
||||||
golang.org/x/sys v0.23.0 // indirect
|
|
||||||
golang.org/x/text v0.17.0 // indirect
|
golang.org/x/text v0.17.0 // indirect
|
||||||
google.golang.org/protobuf v1.34.1 // indirect
|
|
||||||
)
|
)
|
||||||
|
|||||||
14
go.sum
14
go.sum
@@ -16,8 +16,8 @@ github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG
|
|||||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
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 h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw=
|
||||||
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
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/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 h1:oTUjx0vyf2T+wkrx09Trsev1TE+/EbDAeHtSTbtC2eI=
|
||||||
github.com/gorilla/csrf v1.7.2/go.mod h1:F1Fj3KG23WYHE6gozCmBAezKookxbIvUJT+121wTuLk=
|
github.com/gorilla/csrf v1.7.2/go.mod h1:F1Fj3KG23WYHE6gozCmBAezKookxbIvUJT+121wTuLk=
|
||||||
@@ -142,8 +142,6 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
|
|||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
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-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/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
|
|
||||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
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-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-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
@@ -157,8 +155,6 @@ golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/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-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM=
|
|
||||||
golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
|
||||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
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/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.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
@@ -181,12 +177,6 @@ golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8T
|
|||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/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-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157 h1:Zy9XzmMEflZ/MAaA7vNcoebnRAld7FsPW1EeBB7V0m8=
|
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0=
|
|
||||||
google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc=
|
|
||||||
google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ=
|
|
||||||
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
|
|
||||||
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
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/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/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||||
|
|||||||
6
main.go
6
main.go
@@ -38,15 +38,14 @@ func ConnectDB() *sql.DB {
|
|||||||
func main() {
|
func main() {
|
||||||
csrfKey := []byte(os.Getenv("LENSLOCKED_CSRF_KEY"))
|
csrfKey := []byte(os.Getenv("LENSLOCKED_CSRF_KEY"))
|
||||||
if len(csrfKey) < 32 {
|
if len(csrfKey) < 32 {
|
||||||
panic("Error: bad csrf protection key\nPlease set a key with the LENSLOCKED_CSRF_KEY env var.")
|
panic("Error: bad csrf protection key")
|
||||||
}
|
}
|
||||||
|
|
||||||
db := ConnectDB()
|
db := ConnectDB()
|
||||||
defer db.Close()
|
defer db.Close()
|
||||||
|
|
||||||
userService := models.UserService{DB: db}
|
userService := models.UserService{DB: db}
|
||||||
sessionService := models.SessionService{DB: db}
|
var usersCtrlr ctrlrs.Users = ctrlrs.Default(&userService)
|
||||||
var usersCtrlr ctrlrs.Users = ctrlrs.Default(&userService, &sessionService)
|
|
||||||
|
|
||||||
r := chi.NewRouter()
|
r := chi.NewRouter()
|
||||||
|
|
||||||
@@ -60,7 +59,6 @@ func main() {
|
|||||||
r.Post("/signup", usersCtrlr.PostSignup)
|
r.Post("/signup", usersCtrlr.PostSignup)
|
||||||
r.Get("/signin", usersCtrlr.GetSignin)
|
r.Get("/signin", usersCtrlr.GetSignin)
|
||||||
r.Post("/signin", usersCtrlr.PostSignin)
|
r.Post("/signin", usersCtrlr.PostSignin)
|
||||||
r.Post("/signout", usersCtrlr.GetSignout)
|
|
||||||
|
|
||||||
r.Get("/user", usersCtrlr.CurrentUser)
|
r.Get("/user", usersCtrlr.CurrentUser)
|
||||||
|
|
||||||
|
|||||||
@@ -1,105 +0,0 @@
|
|||||||
package models
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/rand"
|
|
||||||
"crypto/sha256"
|
|
||||||
"database/sql"
|
|
||||||
"encoding/base64"
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
func RandBytes(n int) ([]byte, error) {
|
|
||||||
b := make([]byte, n)
|
|
||||||
nRead, err := rand.Read(b)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("bytes: %w", err)
|
|
||||||
}
|
|
||||||
if nRead < n {
|
|
||||||
return nil, fmt.Errorf("bytes: didn't read enough random bytes")
|
|
||||||
}
|
|
||||||
return b, nil
|
|
||||||
}
|
|
||||||
func RandString(n int) (string, error) {
|
|
||||||
b, err := RandBytes(n)
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("string: %w", err)
|
|
||||||
}
|
|
||||||
return base64.URLEncoding.EncodeToString(b), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
const SessionTokenBytes = 32
|
|
||||||
|
|
||||||
func SessionToken() (string, error) {
|
|
||||||
return RandString(SessionTokenBytes)
|
|
||||||
}
|
|
||||||
func hash(token string) string {
|
|
||||||
tokenHash := sha256.Sum256([]byte(token))
|
|
||||||
return base64.StdEncoding.EncodeToString(tokenHash[:])
|
|
||||||
}
|
|
||||||
|
|
||||||
type Session struct {
|
|
||||||
ID int
|
|
||||||
UserID int
|
|
||||||
TokenHash string
|
|
||||||
// Token is only set when creating a new session. When looking up a session
|
|
||||||
// this will be left empty, as we only store the hash of a session token
|
|
||||||
// in our database and we cannot reverse it into a raw token.
|
|
||||||
Token string
|
|
||||||
}
|
|
||||||
|
|
||||||
type SessionService struct {
|
|
||||||
DB *sql.DB
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ss *SessionService) Create(userID int) (*Session, error) {
|
|
||||||
token, err := SessionToken()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("create: %w", err)
|
|
||||||
}
|
|
||||||
session := Session{
|
|
||||||
UserID: userID,
|
|
||||||
Token: token,
|
|
||||||
TokenHash: hash(token),
|
|
||||||
}
|
|
||||||
row := ss.DB.QueryRow(`
|
|
||||||
INSERT INTO sessions (user_id, token_hash)
|
|
||||||
VALUES ($1, $2) ON CONFLICT (user_id) DO
|
|
||||||
UPDATE
|
|
||||||
SET token_hash = $2
|
|
||||||
RETURNING id;
|
|
||||||
`, session.UserID, session.TokenHash)
|
|
||||||
err = row.Scan(&session.ID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("create: %w", err)
|
|
||||||
}
|
|
||||||
return &session, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ss *SessionService) Delete(token string) error {
|
|
||||||
tokenHash := hash(token)
|
|
||||||
_, err := ss.DB.Exec(`DELETE FROM sessions WHERE token_hash = $1;`, tokenHash)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("delete: %w", err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ss *SessionService) User(token string) (*User, error) {
|
|
||||||
token_hash := hash(token)
|
|
||||||
var user User
|
|
||||||
|
|
||||||
row := ss.DB.QueryRow(`
|
|
||||||
SELECT users.id,
|
|
||||||
users.email,
|
|
||||||
users.password_hash
|
|
||||||
FROM sessions
|
|
||||||
JOIN users ON users.id = sessions.user_id
|
|
||||||
WHERE sessions.token_hash = $1;
|
|
||||||
`, token_hash)
|
|
||||||
err := row.Scan(&user.ID, &user.Email, &user.PasswordHash)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("user: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &user, err
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
CREATE TABLE sessions (
|
|
||||||
id SERIAL PRIMARY KEY,
|
|
||||||
user_id INT UNIQUE REFERENCES users (id) ON DELETE CASCADE,
|
|
||||||
token_hash TEXT UNIQUE NOT NULL
|
|
||||||
);
|
|
||||||
@@ -16,10 +16,6 @@
|
|||||||
<a class="text-base font-semibold hover:text-blue-100 pr-8" href="/faq">FAQ</a>
|
<a class="text-base font-semibold hover:text-blue-100 pr-8" href="/faq">FAQ</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="space-x-4">
|
<div class="space-x-4">
|
||||||
<form action="/signout" method="post" class="inline pr-4">
|
|
||||||
{{csrfField}}
|
|
||||||
<button type="submit">Sign out</button>
|
|
||||||
</form>
|
|
||||||
<a href="/signin">Sign in</a>
|
<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>
|
<a href="/signup" clss="px-4 py-2 bg-blue-700 hover:bg-blue-600 rounded">Sign up</a>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
package views
|
package views
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
"io"
|
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
@@ -31,14 +29,12 @@ func (t Template) Execute(w http.ResponseWriter, r *http.Request, data interface
|
|||||||
})
|
})
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "text/html; charset=utf8")
|
w.Header().Set("Content-Type", "text/html; charset=utf8")
|
||||||
var buf bytes.Buffer
|
err = tpl.Execute(w, data)
|
||||||
err = tpl.Execute(&buf, data)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error executing template: %v", err)
|
log.Printf("Error executing template: %v", err)
|
||||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
io.Copy(w, &buf)
|
|
||||||
}
|
}
|
||||||
func (t Template) TestTemplate(data interface{}) error {
|
func (t Template) TestTemplate(data interface{}) error {
|
||||||
var testWriter strings.Builder
|
var testWriter strings.Builder
|
||||||
@@ -46,11 +42,6 @@ func (t Template) TestTemplate(data interface{}) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
tpl = tpl.Funcs(template.FuncMap{
|
|
||||||
"csrfField": func() template.HTML {
|
|
||||||
return `<div class="hidden">STUB: PLACEHOLDER</div>`
|
|
||||||
},
|
|
||||||
})
|
|
||||||
return tpl.Execute(&testWriter, data)
|
return tpl.Execute(&testWriter, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,8 +52,8 @@ func FromFile(pattern ...string) (Template, error) {
|
|||||||
func FromFS(fs fs.FS, pattern ...string) (Template, error) {
|
func FromFS(fs fs.FS, pattern ...string) (Template, error) {
|
||||||
tpl := template.New(pattern[0])
|
tpl := template.New(pattern[0])
|
||||||
tpl = tpl.Funcs(template.FuncMap{
|
tpl = tpl.Funcs(template.FuncMap{
|
||||||
"csrfField": func() (template.HTML, error) {
|
"csrfField": func() template.HTML {
|
||||||
return `<div class="hidden">STUB: PLACEHOLDER</div>`, fmt.Errorf("csrfField Not Implimented")
|
return `<div class="hidden">STUB: PLACEHOLDER</div>`
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
tpl, err := tpl.ParseFS(fs, pattern...)
|
tpl, err := tpl.ParseFS(fs, pattern...)
|
||||||
|
|||||||
Reference in New Issue
Block a user