the login code lives in a library now
This commit is contained in:
parent
d728004cad
commit
723efee364
4 changed files with 42 additions and 374 deletions
3
go.mod
3
go.mod
|
|
@ -3,7 +3,8 @@ module humungus.tedunangst.com/r/honk
|
||||||
require (
|
require (
|
||||||
github.com/gorilla/mux v1.7.1
|
github.com/gorilla/mux v1.7.1
|
||||||
github.com/mattn/go-runewidth v0.0.4
|
github.com/mattn/go-runewidth v0.0.4
|
||||||
golang.org/x/crypto v0.0.0-20190404164418-38d8ce5564a5
|
golang.org/x/crypto v0.0.0-20190424203555-c05e17bb3b2d
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3
|
||||||
humungus.tedunangst.com/r/go-sqlite3 v1.1.2
|
humungus.tedunangst.com/r/go-sqlite3 v1.1.2
|
||||||
|
humungus.tedunangst.com/r/webs v0.1.0
|
||||||
)
|
)
|
||||||
|
|
|
||||||
9
go.sum
9
go.sum
|
|
@ -3,13 +3,14 @@ github.com/gorilla/mux v1.7.1/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2z
|
||||||
github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y=
|
github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y=
|
||||||
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20190404164418-38d8ce5564a5 h1:bselrhR0Or1vomJZC8ZIjWtbDmn9OYFLX5Ik9alpJpE=
|
golang.org/x/crypto v0.0.0-20190424203555-c05e17bb3b2d h1:adrbvkTDn9rGnXg2IJDKozEpXXLZN89pdIA+Syt4/u0=
|
||||||
golang.org/x/crypto v0.0.0-20190404164418-38d8ce5564a5/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
|
golang.org/x/crypto v0.0.0-20190424203555-c05e17bb3b2d/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ=
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
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=
|
||||||
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e h1:nFYrTHrdrAOpShe27kaFHjsqYSEQ0KWqdWLu3xuZJts=
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
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=
|
||||||
humungus.tedunangst.com/r/go-sqlite3 v1.1.2 h1:bRAXNRZ4VNFRFhhG4tdudK4Lv4ktHQAHEppKlDANUFg=
|
humungus.tedunangst.com/r/go-sqlite3 v1.1.2 h1:bRAXNRZ4VNFRFhhG4tdudK4Lv4ktHQAHEppKlDANUFg=
|
||||||
humungus.tedunangst.com/r/go-sqlite3 v1.1.2/go.mod h1:FtEEmQM7U2Ey1TuEEOyY1BmphTZnmiEjPsNLEAkpf/M=
|
humungus.tedunangst.com/r/go-sqlite3 v1.1.2/go.mod h1:FtEEmQM7U2Ey1TuEEOyY1BmphTZnmiEjPsNLEAkpf/M=
|
||||||
|
humungus.tedunangst.com/r/webs v0.1.0 h1:TaJBDhgWWL66oK+6aldgn5BdSwTD+9epqhWHoKFc0iI=
|
||||||
|
humungus.tedunangst.com/r/webs v0.1.0/go.mod h1:6yLLDXBaE4pKURa/3/bxoQPod37uAqc/Kq8J0IopWW0=
|
||||||
|
|
|
||||||
73
honk.go
73
honk.go
|
|
@ -39,13 +39,9 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
|
"humungus.tedunangst.com/r/webs/login"
|
||||||
)
|
)
|
||||||
|
|
||||||
type UserInfo struct {
|
|
||||||
UserID int64
|
|
||||||
Username string
|
|
||||||
}
|
|
||||||
|
|
||||||
type WhatAbout struct {
|
type WhatAbout struct {
|
||||||
ID int64
|
ID int64
|
||||||
Name string
|
Name string
|
||||||
|
|
@ -101,14 +97,14 @@ func getInfo(r *http.Request) map[string]interface{} {
|
||||||
templinfo["LocalStyleParam"] = getstyleparam("views/local.css")
|
templinfo["LocalStyleParam"] = getstyleparam("views/local.css")
|
||||||
templinfo["ServerName"] = serverName
|
templinfo["ServerName"] = serverName
|
||||||
templinfo["IconName"] = iconName
|
templinfo["IconName"] = iconName
|
||||||
templinfo["UserInfo"] = GetUserInfo(r)
|
templinfo["UserInfo"] = login.GetUserInfo(r)
|
||||||
templinfo["LogoutCSRF"] = GetCSRF("logout", r)
|
templinfo["LogoutCSRF"] = login.GetCSRF("logout", r)
|
||||||
return templinfo
|
return templinfo
|
||||||
}
|
}
|
||||||
|
|
||||||
func homepage(w http.ResponseWriter, r *http.Request) {
|
func homepage(w http.ResponseWriter, r *http.Request) {
|
||||||
templinfo := getInfo(r)
|
templinfo := getInfo(r)
|
||||||
u := GetUserInfo(r)
|
u := login.GetUserInfo(r)
|
||||||
var honks []*Honk
|
var honks []*Honk
|
||||||
if u != nil {
|
if u != nil {
|
||||||
if r.URL.Path == "/atme" {
|
if r.URL.Path == "/atme" {
|
||||||
|
|
@ -116,7 +112,7 @@ func homepage(w http.ResponseWriter, r *http.Request) {
|
||||||
} else {
|
} else {
|
||||||
honks = gethonksforuser(u.UserID)
|
honks = gethonksforuser(u.UserID)
|
||||||
}
|
}
|
||||||
templinfo["HonkCSRF"] = GetCSRF("honkhonk", r)
|
templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
|
||||||
} else {
|
} else {
|
||||||
honks = getpublichonks()
|
honks = getpublichonks()
|
||||||
}
|
}
|
||||||
|
|
@ -414,27 +410,27 @@ func viewuser(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
honks := gethonksbyuser(name)
|
honks := gethonksbyuser(name)
|
||||||
u := GetUserInfo(r)
|
u := login.GetUserInfo(r)
|
||||||
honkpage(w, r, u, user, honks, "")
|
honkpage(w, r, u, user, honks, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
func viewhonker(w http.ResponseWriter, r *http.Request) {
|
func viewhonker(w http.ResponseWriter, r *http.Request) {
|
||||||
name := mux.Vars(r)["name"]
|
name := mux.Vars(r)["name"]
|
||||||
u := GetUserInfo(r)
|
u := login.GetUserInfo(r)
|
||||||
honks := gethonksbyhonker(u.UserID, name)
|
honks := gethonksbyhonker(u.UserID, name)
|
||||||
honkpage(w, r, u, nil, honks, "honks by honker: " + name)
|
honkpage(w, r, u, nil, honks, "honks by honker: " + name)
|
||||||
}
|
}
|
||||||
|
|
||||||
func viewcombo(w http.ResponseWriter, r *http.Request) {
|
func viewcombo(w http.ResponseWriter, r *http.Request) {
|
||||||
name := mux.Vars(r)["name"]
|
name := mux.Vars(r)["name"]
|
||||||
u := GetUserInfo(r)
|
u := login.GetUserInfo(r)
|
||||||
honks := gethonksbycombo(u.UserID, name)
|
honks := gethonksbycombo(u.UserID, name)
|
||||||
honkpage(w, r, u, nil, honks, "honks by combo: " + name)
|
honkpage(w, r, u, nil, honks, "honks by combo: " + name)
|
||||||
}
|
}
|
||||||
func viewconvoy(w http.ResponseWriter, r *http.Request) {
|
func viewconvoy(w http.ResponseWriter, r *http.Request) {
|
||||||
c := r.FormValue("c")
|
c := r.FormValue("c")
|
||||||
var userid int64 = -1
|
var userid int64 = -1
|
||||||
u := GetUserInfo(r)
|
u := login.GetUserInfo(r)
|
||||||
if u != nil {
|
if u != nil {
|
||||||
userid = u.UserID
|
userid = u.UserID
|
||||||
}
|
}
|
||||||
|
|
@ -512,18 +508,19 @@ func viewhonk(w http.ResponseWriter, r *http.Request) {
|
||||||
WriteJunk(w, j)
|
WriteJunk(w, j)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
u := GetUserInfo(r)
|
u := login.GetUserInfo(r)
|
||||||
honkpage(w, r, u, nil, []*Honk{h}, "one honk")
|
honkpage(w, r, u, nil, []*Honk{h}, "one honk")
|
||||||
}
|
}
|
||||||
|
|
||||||
func honkpage(w http.ResponseWriter, r *http.Request, u *UserInfo, user *WhatAbout, honks []*Honk, infomsg string) {
|
func honkpage(w http.ResponseWriter, r *http.Request, u *login.UserInfo, user *WhatAbout,
|
||||||
|
honks []*Honk, infomsg string) {
|
||||||
reverbolate(honks)
|
reverbolate(honks)
|
||||||
templinfo := getInfo(r)
|
templinfo := getInfo(r)
|
||||||
if u != nil {
|
if u != nil {
|
||||||
if user != nil && u.Username == user.Name {
|
if user != nil && u.Username == user.Name {
|
||||||
templinfo["UserCSRF"] = GetCSRF("saveuser", r)
|
templinfo["UserCSRF"] = login.GetCSRF("saveuser", r)
|
||||||
}
|
}
|
||||||
templinfo["HonkCSRF"] = GetCSRF("honkhonk", r)
|
templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
|
||||||
}
|
}
|
||||||
if u == nil {
|
if u == nil {
|
||||||
w.Header().Set("Cache-Control", "max-age=60")
|
w.Header().Set("Cache-Control", "max-age=60")
|
||||||
|
|
@ -545,7 +542,7 @@ func honkpage(w http.ResponseWriter, r *http.Request, u *UserInfo, user *WhatAbo
|
||||||
|
|
||||||
func saveuser(w http.ResponseWriter, r *http.Request) {
|
func saveuser(w http.ResponseWriter, r *http.Request) {
|
||||||
whatabout := r.FormValue("whatabout")
|
whatabout := r.FormValue("whatabout")
|
||||||
u := GetUserInfo(r)
|
u := login.GetUserInfo(r)
|
||||||
db := opendatabase()
|
db := opendatabase()
|
||||||
_, err := db.Exec("update users set about = ? where username = ?", whatabout, u.Username)
|
_, err := db.Exec("update users set about = ? where username = ?", whatabout, u.Username)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -721,7 +718,7 @@ func savebonk(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
convoy := xonk.Convoy
|
convoy := xonk.Convoy
|
||||||
|
|
||||||
userinfo := GetUserInfo(r)
|
userinfo := login.GetUserInfo(r)
|
||||||
|
|
||||||
dt := time.Now().UTC()
|
dt := time.Now().UTC()
|
||||||
bonk := Honk{
|
bonk := Honk{
|
||||||
|
|
@ -767,7 +764,7 @@ func zonkit(w http.ResponseWriter, r *http.Request) {
|
||||||
xid := r.FormValue("xid")
|
xid := r.FormValue("xid")
|
||||||
|
|
||||||
log.Printf("zonking %s", xid)
|
log.Printf("zonking %s", xid)
|
||||||
userinfo := GetUserInfo(r)
|
userinfo := login.GetUserInfo(r)
|
||||||
stmtZonkIt.Exec(userinfo.UserID, xid)
|
stmtZonkIt.Exec(userinfo.UserID, xid)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -775,7 +772,7 @@ func savehonk(w http.ResponseWriter, r *http.Request) {
|
||||||
rid := r.FormValue("rid")
|
rid := r.FormValue("rid")
|
||||||
noise := r.FormValue("noise")
|
noise := r.FormValue("noise")
|
||||||
|
|
||||||
userinfo := GetUserInfo(r)
|
userinfo := login.GetUserInfo(r)
|
||||||
|
|
||||||
dt := time.Now().UTC()
|
dt := time.Now().UTC()
|
||||||
xid := xfiltrate()
|
xid := xfiltrate()
|
||||||
|
|
@ -909,10 +906,10 @@ func savehonk(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func viewhonkers(w http.ResponseWriter, r *http.Request) {
|
func viewhonkers(w http.ResponseWriter, r *http.Request) {
|
||||||
userinfo := GetUserInfo(r)
|
userinfo := login.GetUserInfo(r)
|
||||||
templinfo := getInfo(r)
|
templinfo := getInfo(r)
|
||||||
templinfo["Honkers"] = gethonkers(userinfo.UserID)
|
templinfo["Honkers"] = gethonkers(userinfo.UserID)
|
||||||
templinfo["HonkerCSRF"] = GetCSRF("savehonker", r)
|
templinfo["HonkerCSRF"] = login.GetCSRF("savehonker", r)
|
||||||
err := readviews.ExecuteTemplate(w, "honkers.html", templinfo)
|
err := readviews.ExecuteTemplate(w, "honkers.html", templinfo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Print(err)
|
log.Print(err)
|
||||||
|
|
@ -960,7 +957,7 @@ func gofish(name string) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func savehonker(w http.ResponseWriter, r *http.Request) {
|
func savehonker(w http.ResponseWriter, r *http.Request) {
|
||||||
u := GetUserInfo(r)
|
u := login.GetUserInfo(r)
|
||||||
name := r.FormValue("name")
|
name := r.FormValue("name")
|
||||||
url := r.FormValue("url")
|
url := r.FormValue("url")
|
||||||
peep := r.FormValue("peep")
|
peep := r.FormValue("peep")
|
||||||
|
|
@ -1009,7 +1006,7 @@ type Zonker struct {
|
||||||
|
|
||||||
func killzone(w http.ResponseWriter, r *http.Request) {
|
func killzone(w http.ResponseWriter, r *http.Request) {
|
||||||
db := opendatabase()
|
db := opendatabase()
|
||||||
userinfo := GetUserInfo(r)
|
userinfo := login.GetUserInfo(r)
|
||||||
rows, err := db.Query("select name, wherefore from zonkers where userid = ?", userinfo.UserID)
|
rows, err := db.Query("select name, wherefore from zonkers where userid = ?", userinfo.UserID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("err: %s", err)
|
log.Printf("err: %s", err)
|
||||||
|
|
@ -1023,7 +1020,7 @@ func killzone(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
templinfo := getInfo(r)
|
templinfo := getInfo(r)
|
||||||
templinfo["Zonkers"] = zonkers
|
templinfo["Zonkers"] = zonkers
|
||||||
templinfo["KillCSRF"] = GetCSRF("killitwithfire", r)
|
templinfo["KillCSRF"] = login.GetCSRF("killitwithfire", r)
|
||||||
err = readviews.ExecuteTemplate(w, "zonkers.html", templinfo)
|
err = readviews.ExecuteTemplate(w, "zonkers.html", templinfo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Print(err)
|
log.Print(err)
|
||||||
|
|
@ -1031,7 +1028,7 @@ func killzone(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func killitwithfire(w http.ResponseWriter, r *http.Request) {
|
func killitwithfire(w http.ResponseWriter, r *http.Request) {
|
||||||
userinfo := GetUserInfo(r)
|
userinfo := login.GetUserInfo(r)
|
||||||
wherefore := r.FormValue("wherefore")
|
wherefore := r.FormValue("wherefore")
|
||||||
name := r.FormValue("name")
|
name := r.FormValue("name")
|
||||||
if name == "" {
|
if name == "" {
|
||||||
|
|
@ -1099,7 +1096,7 @@ func servefile(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
func serve() {
|
func serve() {
|
||||||
db := opendatabase()
|
db := opendatabase()
|
||||||
LoginInit(db)
|
login.Init(db)
|
||||||
|
|
||||||
listener, err := openListener()
|
listener, err := openListener()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -1126,7 +1123,7 @@ func serve() {
|
||||||
}
|
}
|
||||||
|
|
||||||
mux := mux.NewRouter()
|
mux := mux.NewRouter()
|
||||||
mux.Use(LoginChecker)
|
mux.Use(login.Checker)
|
||||||
|
|
||||||
posters := mux.Methods("POST").Subrouter()
|
posters := mux.Methods("POST").Subrouter()
|
||||||
getters := mux.Methods("GET").Subrouter()
|
getters := mux.Methods("GET").Subrouter()
|
||||||
|
|
@ -1147,22 +1144,22 @@ func serve() {
|
||||||
getters.HandleFunc("/style.css", servecss)
|
getters.HandleFunc("/style.css", servecss)
|
||||||
getters.HandleFunc("/local.css", servecss)
|
getters.HandleFunc("/local.css", servecss)
|
||||||
getters.HandleFunc("/login", servehtml)
|
getters.HandleFunc("/login", servehtml)
|
||||||
posters.HandleFunc("/dologin", dologin)
|
posters.HandleFunc("/dologin", login.LoginFunc)
|
||||||
getters.HandleFunc("/logout", dologout)
|
getters.HandleFunc("/logout", login.LogoutFunc)
|
||||||
|
|
||||||
loggedin := mux.NewRoute().Subrouter()
|
loggedin := mux.NewRoute().Subrouter()
|
||||||
loggedin.Use(LoginRequired)
|
loggedin.Use(login.Required)
|
||||||
loggedin.HandleFunc("/atme", homepage)
|
loggedin.HandleFunc("/atme", homepage)
|
||||||
loggedin.HandleFunc("/killzone", killzone)
|
loggedin.HandleFunc("/killzone", killzone)
|
||||||
loggedin.Handle("/honk", CSRFWrap("honkhonk", http.HandlerFunc(savehonk)))
|
loggedin.Handle("/honk", login.CSRFWrap("honkhonk", http.HandlerFunc(savehonk)))
|
||||||
loggedin.Handle("/bonk", CSRFWrap("honkhonk", http.HandlerFunc(savebonk)))
|
loggedin.Handle("/bonk", login.CSRFWrap("honkhonk", http.HandlerFunc(savebonk)))
|
||||||
loggedin.Handle("/zonkit", CSRFWrap("honkhonk", http.HandlerFunc(zonkit)))
|
loggedin.Handle("/zonkit", login.CSRFWrap("honkhonk", http.HandlerFunc(zonkit)))
|
||||||
loggedin.Handle("/killitwithfire", CSRFWrap("killitwithfire", http.HandlerFunc(killitwithfire)))
|
loggedin.Handle("/killitwithfire", login.CSRFWrap("killitwithfire", http.HandlerFunc(killitwithfire)))
|
||||||
loggedin.Handle("/saveuser", CSRFWrap("saveuser", http.HandlerFunc(saveuser)))
|
loggedin.Handle("/saveuser", login.CSRFWrap("saveuser", http.HandlerFunc(saveuser)))
|
||||||
loggedin.HandleFunc("/honkers", viewhonkers)
|
loggedin.HandleFunc("/honkers", viewhonkers)
|
||||||
loggedin.HandleFunc("/h/{name:[[:alnum:]]+}", viewhonker)
|
loggedin.HandleFunc("/h/{name:[[:alnum:]]+}", viewhonker)
|
||||||
loggedin.HandleFunc("/c/{name:[[:alnum:]]+}", viewcombo)
|
loggedin.HandleFunc("/c/{name:[[:alnum:]]+}", viewcombo)
|
||||||
loggedin.Handle("/savehonker", CSRFWrap("savehonker", http.HandlerFunc(savehonker)))
|
loggedin.Handle("/savehonker", login.CSRFWrap("savehonker", http.HandlerFunc(savehonker)))
|
||||||
|
|
||||||
err = http.Serve(listener, mux)
|
err = http.Serve(listener, mux)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
331
login.go
331
login.go
|
|
@ -1,331 +0,0 @@
|
||||||
//
|
|
||||||
// Copyright (c) 2019 Ted Unangst <tedu@tedunangst.com>
|
|
||||||
//
|
|
||||||
// Permission to use, copy, modify, and distribute this software for any
|
|
||||||
// purpose with or without fee is hereby granted, provided that the above
|
|
||||||
// copyright notice and this permission notice appear in all copies.
|
|
||||||
//
|
|
||||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
||||||
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
||||||
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
||||||
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
||||||
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
||||||
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
||||||
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"crypto/rand"
|
|
||||||
"crypto/sha512"
|
|
||||||
"crypto/subtle"
|
|
||||||
"database/sql"
|
|
||||||
"fmt"
|
|
||||||
"hash"
|
|
||||||
"io"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"reflect"
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"golang.org/x/crypto/bcrypt"
|
|
||||||
)
|
|
||||||
|
|
||||||
type keytype struct{}
|
|
||||||
|
|
||||||
var thekey keytype
|
|
||||||
|
|
||||||
func LoginChecker(handler http.Handler) http.Handler {
|
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
userinfo, ok := checkauthcookie(r)
|
|
||||||
if ok {
|
|
||||||
ctx := context.WithValue(r.Context(), thekey, userinfo)
|
|
||||||
r = r.WithContext(ctx)
|
|
||||||
}
|
|
||||||
handler.ServeHTTP(w, r)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func LoginRequired(handler http.Handler) http.Handler {
|
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
ok := GetUserInfo(r) != nil
|
|
||||||
if !ok {
|
|
||||||
loginredirect(w, r)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
handler.ServeHTTP(w, r)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetUserInfo(r *http.Request) *UserInfo {
|
|
||||||
userinfo, ok := r.Context().Value(thekey).(*UserInfo)
|
|
||||||
if !ok {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return userinfo
|
|
||||||
}
|
|
||||||
|
|
||||||
func calculateCSRF(salt, action, auth string) string {
|
|
||||||
hasher := sha512.New512_256()
|
|
||||||
zero := []byte{0}
|
|
||||||
hasher.Write(zero)
|
|
||||||
hasher.Write([]byte(auth))
|
|
||||||
hasher.Write(zero)
|
|
||||||
hasher.Write([]byte(csrfkey))
|
|
||||||
hasher.Write(zero)
|
|
||||||
hasher.Write([]byte(salt))
|
|
||||||
hasher.Write(zero)
|
|
||||||
hasher.Write([]byte(action))
|
|
||||||
hasher.Write(zero)
|
|
||||||
hash := hexsum(hasher)
|
|
||||||
|
|
||||||
return salt + hash
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetCSRF(action string, r *http.Request) string {
|
|
||||||
auth := getauthcookie(r)
|
|
||||||
if auth == "" {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
hasher := sha512.New512_256()
|
|
||||||
io.CopyN(hasher, rand.Reader, 32)
|
|
||||||
salt := hexsum(hasher)
|
|
||||||
|
|
||||||
return calculateCSRF(salt, action, auth)
|
|
||||||
}
|
|
||||||
|
|
||||||
func CheckCSRF(action string, r *http.Request) bool {
|
|
||||||
auth := getauthcookie(r)
|
|
||||||
if auth == "" {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
csrf := r.FormValue("CSRF")
|
|
||||||
if len(csrf) != authlen*2 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
salt := csrf[0:authlen]
|
|
||||||
rv := calculateCSRF(salt, action, auth)
|
|
||||||
ok := subtle.ConstantTimeCompare([]byte(rv), []byte(csrf)) == 1
|
|
||||||
return ok
|
|
||||||
}
|
|
||||||
|
|
||||||
func CSRFWrap(action string, handler http.Handler) http.Handler {
|
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
ok := CheckCSRF(action, r)
|
|
||||||
if !ok {
|
|
||||||
http.Error(w, "invalid csrf", 403)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
handler.ServeHTTP(w, r)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func loginredirect(w http.ResponseWriter, r *http.Request) {
|
|
||||||
http.SetCookie(w, &http.Cookie{
|
|
||||||
Name: "auth",
|
|
||||||
Value: "",
|
|
||||||
MaxAge: -1,
|
|
||||||
Secure: securecookies,
|
|
||||||
HttpOnly: true,
|
|
||||||
})
|
|
||||||
http.Redirect(w, r, "/login", http.StatusSeeOther)
|
|
||||||
}
|
|
||||||
|
|
||||||
var authregex = regexp.MustCompile("^[[:alnum:]]+$")
|
|
||||||
var authlen = 32
|
|
||||||
|
|
||||||
var stmtUserName, stmtUserAuth, stmtSaveAuth, stmtDeleteAuth *sql.Stmt
|
|
||||||
var csrfkey string
|
|
||||||
var securecookies bool
|
|
||||||
|
|
||||||
func LoginInit(db *sql.DB) {
|
|
||||||
var err error
|
|
||||||
stmtUserName, err = db.Prepare("select userid, hash from users where username = ?")
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
var userinfo UserInfo
|
|
||||||
t := reflect.TypeOf(userinfo)
|
|
||||||
var fields []string
|
|
||||||
for i := 0; i < t.NumField(); i++ {
|
|
||||||
f := t.Field(i)
|
|
||||||
fields = append(fields, strings.ToLower(f.Name))
|
|
||||||
}
|
|
||||||
stmtUserAuth, err = db.Prepare(fmt.Sprintf("select %s from users where userid = (select userid from auth where hash = ?)", strings.Join(fields, ", ")))
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
stmtSaveAuth, err = db.Prepare("insert into auth (userid, hash) values (?, ?)")
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
stmtDeleteAuth, err = db.Prepare("delete from auth where userid = ?")
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
debug := false
|
|
||||||
getconfig("debug", &debug)
|
|
||||||
securecookies = !debug
|
|
||||||
getconfig("csrfkey", &csrfkey)
|
|
||||||
}
|
|
||||||
|
|
||||||
var authinprogress = make(map[string]bool)
|
|
||||||
var authprogressmtx sync.Mutex
|
|
||||||
|
|
||||||
func rateandwait(username string) bool {
|
|
||||||
authprogressmtx.Lock()
|
|
||||||
defer authprogressmtx.Unlock()
|
|
||||||
if authinprogress[username] {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
authinprogress[username] = true
|
|
||||||
go func(name string) {
|
|
||||||
time.Sleep(1 * time.Second / 2)
|
|
||||||
authprogressmtx.Lock()
|
|
||||||
authinprogress[name] = false
|
|
||||||
authprogressmtx.Unlock()
|
|
||||||
}(username)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func getauthcookie(r *http.Request) string {
|
|
||||||
cookie, err := r.Cookie("auth")
|
|
||||||
if err != nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
auth := cookie.Value
|
|
||||||
if !(len(auth) == authlen && authregex.MatchString(auth)) {
|
|
||||||
log.Printf("login: bad auth: %s", auth)
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return auth
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkauthcookie(r *http.Request) (*UserInfo, bool) {
|
|
||||||
auth := getauthcookie(r)
|
|
||||||
if auth == "" {
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
hasher := sha512.New512_256()
|
|
||||||
hasher.Write([]byte(auth))
|
|
||||||
authhash := hexsum(hasher)
|
|
||||||
row := stmtUserAuth.QueryRow(authhash)
|
|
||||||
var userinfo UserInfo
|
|
||||||
v := reflect.ValueOf(&userinfo).Elem()
|
|
||||||
var ptrs []interface{}
|
|
||||||
for i := 0; i < v.NumField(); i++ {
|
|
||||||
f := v.Field(i)
|
|
||||||
ptrs = append(ptrs, f.Addr().Interface())
|
|
||||||
}
|
|
||||||
err := row.Scan(ptrs...)
|
|
||||||
if err != nil {
|
|
||||||
if err == sql.ErrNoRows {
|
|
||||||
log.Printf("login: no auth found")
|
|
||||||
} else {
|
|
||||||
log.Printf("login: error scanning auth row: %s", err)
|
|
||||||
}
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
return &userinfo, true
|
|
||||||
}
|
|
||||||
|
|
||||||
func loaduser(username string) (int64, string, bool) {
|
|
||||||
row := stmtUserName.QueryRow(username)
|
|
||||||
var userid int64
|
|
||||||
var hash string
|
|
||||||
err := row.Scan(&userid, &hash)
|
|
||||||
if err != nil {
|
|
||||||
if err == sql.ErrNoRows {
|
|
||||||
log.Printf("login: no username found")
|
|
||||||
} else {
|
|
||||||
log.Printf("login: error loading username: %s", err)
|
|
||||||
}
|
|
||||||
return -1, "", false
|
|
||||||
}
|
|
||||||
return userid, hash, true
|
|
||||||
}
|
|
||||||
|
|
||||||
var userregex = regexp.MustCompile("^[[:alnum:]]+$")
|
|
||||||
var userlen = 32
|
|
||||||
var passlen = 128
|
|
||||||
|
|
||||||
func hexsum(h hash.Hash) string {
|
|
||||||
return fmt.Sprintf("%x", h.Sum(nil))[0:authlen]
|
|
||||||
}
|
|
||||||
|
|
||||||
func dologin(w http.ResponseWriter, r *http.Request) {
|
|
||||||
username := r.FormValue("username")
|
|
||||||
password := r.FormValue("password")
|
|
||||||
|
|
||||||
if len(username) == 0 || len(username) > userlen ||
|
|
||||||
!userregex.MatchString(username) || len(password) == 0 ||
|
|
||||||
len(password) > passlen {
|
|
||||||
log.Printf("login: invalid password attempt")
|
|
||||||
loginredirect(w, r)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
userid, hash, ok := loaduser(username)
|
|
||||||
if !ok {
|
|
||||||
loginredirect(w, r)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if !rateandwait(username) {
|
|
||||||
loginredirect(w, r)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("login: incorrect password")
|
|
||||||
loginredirect(w, r)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
hasher := sha512.New512_256()
|
|
||||||
io.CopyN(hasher, rand.Reader, 32)
|
|
||||||
hash = hexsum(hasher)
|
|
||||||
|
|
||||||
http.SetCookie(w, &http.Cookie{
|
|
||||||
Name: "auth",
|
|
||||||
Value: hash,
|
|
||||||
MaxAge: 3600 * 24 * 30,
|
|
||||||
Secure: securecookies,
|
|
||||||
HttpOnly: true,
|
|
||||||
})
|
|
||||||
|
|
||||||
hasher.Reset()
|
|
||||||
hasher.Write([]byte(hash))
|
|
||||||
authhash := hexsum(hasher)
|
|
||||||
|
|
||||||
_, err = stmtSaveAuth.Exec(userid, authhash)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("error saving auth: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Printf("login: successful login")
|
|
||||||
http.Redirect(w, r, "/", http.StatusSeeOther)
|
|
||||||
}
|
|
||||||
|
|
||||||
func dologout(w http.ResponseWriter, r *http.Request) {
|
|
||||||
userinfo, ok := checkauthcookie(r)
|
|
||||||
if ok && CheckCSRF("logout", r) {
|
|
||||||
_, err := stmtDeleteAuth.Exec(userinfo.UserID)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("login: error deleting old auth: %s", err)
|
|
||||||
http.Redirect(w, r, "/", http.StatusSeeOther)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
http.SetCookie(w, &http.Cookie{
|
|
||||||
Name: "auth",
|
|
||||||
Value: "",
|
|
||||||
MaxAge: -1,
|
|
||||||
Secure: securecookies,
|
|
||||||
HttpOnly: true,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
http.Redirect(w, r, "/", http.StatusSeeOther)
|
|
||||||
}
|
|
||||||
Loading…
Reference in a new issue