the login code lives in a library now
This commit is contained in:
parent
d728004cad
commit
723efee364
3
go.mod
3
go.mod
|
@ -3,7 +3,8 @@ module humungus.tedunangst.com/r/honk
|
|||
require (
|
||||
github.com/gorilla/mux v1.7.1
|
||||
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
|
||||
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/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-20190404164418-38d8ce5564a5 h1:bselrhR0Or1vomJZC8ZIjWtbDmn9OYFLX5Ik9alpJpE=
|
||||
golang.org/x/crypto v0.0.0-20190404164418-38d8ce5564a5/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
|
||||
golang.org/x/crypto v0.0.0-20190424203555-c05e17bb3b2d h1:adrbvkTDn9rGnXg2IJDKozEpXXLZN89pdIA+Syt4/u0=
|
||||
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/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-20190403152447-81d4e9dc473e h1:nFYrTHrdrAOpShe27kaFHjsqYSEQ0KWqdWLu3xuZJts=
|
||||
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/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/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"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"humungus.tedunangst.com/r/webs/login"
|
||||
)
|
||||
|
||||
type UserInfo struct {
|
||||
UserID int64
|
||||
Username string
|
||||
}
|
||||
|
||||
type WhatAbout struct {
|
||||
ID int64
|
||||
Name string
|
||||
|
@ -101,14 +97,14 @@ func getInfo(r *http.Request) map[string]interface{} {
|
|||
templinfo["LocalStyleParam"] = getstyleparam("views/local.css")
|
||||
templinfo["ServerName"] = serverName
|
||||
templinfo["IconName"] = iconName
|
||||
templinfo["UserInfo"] = GetUserInfo(r)
|
||||
templinfo["LogoutCSRF"] = GetCSRF("logout", r)
|
||||
templinfo["UserInfo"] = login.GetUserInfo(r)
|
||||
templinfo["LogoutCSRF"] = login.GetCSRF("logout", r)
|
||||
return templinfo
|
||||
}
|
||||
|
||||
func homepage(w http.ResponseWriter, r *http.Request) {
|
||||
templinfo := getInfo(r)
|
||||
u := GetUserInfo(r)
|
||||
u := login.GetUserInfo(r)
|
||||
var honks []*Honk
|
||||
if u != nil {
|
||||
if r.URL.Path == "/atme" {
|
||||
|
@ -116,7 +112,7 @@ func homepage(w http.ResponseWriter, r *http.Request) {
|
|||
} else {
|
||||
honks = gethonksforuser(u.UserID)
|
||||
}
|
||||
templinfo["HonkCSRF"] = GetCSRF("honkhonk", r)
|
||||
templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
|
||||
} else {
|
||||
honks = getpublichonks()
|
||||
}
|
||||
|
@ -414,27 +410,27 @@ func viewuser(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
honks := gethonksbyuser(name)
|
||||
u := GetUserInfo(r)
|
||||
u := login.GetUserInfo(r)
|
||||
honkpage(w, r, u, user, honks, "")
|
||||
}
|
||||
|
||||
func viewhonker(w http.ResponseWriter, r *http.Request) {
|
||||
name := mux.Vars(r)["name"]
|
||||
u := GetUserInfo(r)
|
||||
u := login.GetUserInfo(r)
|
||||
honks := gethonksbyhonker(u.UserID, name)
|
||||
honkpage(w, r, u, nil, honks, "honks by honker: " + name)
|
||||
}
|
||||
|
||||
func viewcombo(w http.ResponseWriter, r *http.Request) {
|
||||
name := mux.Vars(r)["name"]
|
||||
u := GetUserInfo(r)
|
||||
u := login.GetUserInfo(r)
|
||||
honks := gethonksbycombo(u.UserID, name)
|
||||
honkpage(w, r, u, nil, honks, "honks by combo: " + name)
|
||||
}
|
||||
func viewconvoy(w http.ResponseWriter, r *http.Request) {
|
||||
c := r.FormValue("c")
|
||||
var userid int64 = -1
|
||||
u := GetUserInfo(r)
|
||||
u := login.GetUserInfo(r)
|
||||
if u != nil {
|
||||
userid = u.UserID
|
||||
}
|
||||
|
@ -512,18 +508,19 @@ func viewhonk(w http.ResponseWriter, r *http.Request) {
|
|||
WriteJunk(w, j)
|
||||
return
|
||||
}
|
||||
u := GetUserInfo(r)
|
||||
u := login.GetUserInfo(r)
|
||||
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)
|
||||
templinfo := getInfo(r)
|
||||
if u != nil {
|
||||
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 {
|
||||
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) {
|
||||
whatabout := r.FormValue("whatabout")
|
||||
u := GetUserInfo(r)
|
||||
u := login.GetUserInfo(r)
|
||||
db := opendatabase()
|
||||
_, err := db.Exec("update users set about = ? where username = ?", whatabout, u.Username)
|
||||
if err != nil {
|
||||
|
@ -721,7 +718,7 @@ func savebonk(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
convoy := xonk.Convoy
|
||||
|
||||
userinfo := GetUserInfo(r)
|
||||
userinfo := login.GetUserInfo(r)
|
||||
|
||||
dt := time.Now().UTC()
|
||||
bonk := Honk{
|
||||
|
@ -767,7 +764,7 @@ func zonkit(w http.ResponseWriter, r *http.Request) {
|
|||
xid := r.FormValue("xid")
|
||||
|
||||
log.Printf("zonking %s", xid)
|
||||
userinfo := GetUserInfo(r)
|
||||
userinfo := login.GetUserInfo(r)
|
||||
stmtZonkIt.Exec(userinfo.UserID, xid)
|
||||
}
|
||||
|
||||
|
@ -775,7 +772,7 @@ func savehonk(w http.ResponseWriter, r *http.Request) {
|
|||
rid := r.FormValue("rid")
|
||||
noise := r.FormValue("noise")
|
||||
|
||||
userinfo := GetUserInfo(r)
|
||||
userinfo := login.GetUserInfo(r)
|
||||
|
||||
dt := time.Now().UTC()
|
||||
xid := xfiltrate()
|
||||
|
@ -909,10 +906,10 @@ func savehonk(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["Honkers"] = gethonkers(userinfo.UserID)
|
||||
templinfo["HonkerCSRF"] = GetCSRF("savehonker", r)
|
||||
templinfo["HonkerCSRF"] = login.GetCSRF("savehonker", r)
|
||||
err := readviews.ExecuteTemplate(w, "honkers.html", templinfo)
|
||||
if err != nil {
|
||||
log.Print(err)
|
||||
|
@ -960,7 +957,7 @@ func gofish(name string) string {
|
|||
}
|
||||
|
||||
func savehonker(w http.ResponseWriter, r *http.Request) {
|
||||
u := GetUserInfo(r)
|
||||
u := login.GetUserInfo(r)
|
||||
name := r.FormValue("name")
|
||||
url := r.FormValue("url")
|
||||
peep := r.FormValue("peep")
|
||||
|
@ -1009,7 +1006,7 @@ type Zonker struct {
|
|||
|
||||
func killzone(w http.ResponseWriter, r *http.Request) {
|
||||
db := opendatabase()
|
||||
userinfo := GetUserInfo(r)
|
||||
userinfo := login.GetUserInfo(r)
|
||||
rows, err := db.Query("select name, wherefore from zonkers where userid = ?", userinfo.UserID)
|
||||
if err != nil {
|
||||
log.Printf("err: %s", err)
|
||||
|
@ -1023,7 +1020,7 @@ func killzone(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
templinfo := getInfo(r)
|
||||
templinfo["Zonkers"] = zonkers
|
||||
templinfo["KillCSRF"] = GetCSRF("killitwithfire", r)
|
||||
templinfo["KillCSRF"] = login.GetCSRF("killitwithfire", r)
|
||||
err = readviews.ExecuteTemplate(w, "zonkers.html", templinfo)
|
||||
if err != nil {
|
||||
log.Print(err)
|
||||
|
@ -1031,7 +1028,7 @@ func killzone(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")
|
||||
name := r.FormValue("name")
|
||||
if name == "" {
|
||||
|
@ -1099,7 +1096,7 @@ func servefile(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
func serve() {
|
||||
db := opendatabase()
|
||||
LoginInit(db)
|
||||
login.Init(db)
|
||||
|
||||
listener, err := openListener()
|
||||
if err != nil {
|
||||
|
@ -1126,7 +1123,7 @@ func serve() {
|
|||
}
|
||||
|
||||
mux := mux.NewRouter()
|
||||
mux.Use(LoginChecker)
|
||||
mux.Use(login.Checker)
|
||||
|
||||
posters := mux.Methods("POST").Subrouter()
|
||||
getters := mux.Methods("GET").Subrouter()
|
||||
|
@ -1147,22 +1144,22 @@ func serve() {
|
|||
getters.HandleFunc("/style.css", servecss)
|
||||
getters.HandleFunc("/local.css", servecss)
|
||||
getters.HandleFunc("/login", servehtml)
|
||||
posters.HandleFunc("/dologin", dologin)
|
||||
getters.HandleFunc("/logout", dologout)
|
||||
posters.HandleFunc("/dologin", login.LoginFunc)
|
||||
getters.HandleFunc("/logout", login.LogoutFunc)
|
||||
|
||||
loggedin := mux.NewRoute().Subrouter()
|
||||
loggedin.Use(LoginRequired)
|
||||
loggedin.Use(login.Required)
|
||||
loggedin.HandleFunc("/atme", homepage)
|
||||
loggedin.HandleFunc("/killzone", killzone)
|
||||
loggedin.Handle("/honk", CSRFWrap("honkhonk", http.HandlerFunc(savehonk)))
|
||||
loggedin.Handle("/bonk", CSRFWrap("honkhonk", http.HandlerFunc(savebonk)))
|
||||
loggedin.Handle("/zonkit", CSRFWrap("honkhonk", http.HandlerFunc(zonkit)))
|
||||
loggedin.Handle("/killitwithfire", CSRFWrap("killitwithfire", http.HandlerFunc(killitwithfire)))
|
||||
loggedin.Handle("/saveuser", CSRFWrap("saveuser", http.HandlerFunc(saveuser)))
|
||||
loggedin.Handle("/honk", login.CSRFWrap("honkhonk", http.HandlerFunc(savehonk)))
|
||||
loggedin.Handle("/bonk", login.CSRFWrap("honkhonk", http.HandlerFunc(savebonk)))
|
||||
loggedin.Handle("/zonkit", login.CSRFWrap("honkhonk", http.HandlerFunc(zonkit)))
|
||||
loggedin.Handle("/killitwithfire", login.CSRFWrap("killitwithfire", http.HandlerFunc(killitwithfire)))
|
||||
loggedin.Handle("/saveuser", login.CSRFWrap("saveuser", http.HandlerFunc(saveuser)))
|
||||
loggedin.HandleFunc("/honkers", viewhonkers)
|
||||
loggedin.HandleFunc("/h/{name:[[:alnum:]]+}", viewhonker)
|
||||
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)
|
||||
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 New Issue