Author | Jakob Wakeling <[email protected]> |
Date | 2023-07-21 07:22:31 |
Commit | 52b258d5f7839961411fef3021609a9a3c4c9c9b |
Parent | 0893c1e6ee7e521b4f36ee08c69daf4ce211cfd3 |
Implement user session page and session renewal
Diffstat
M | main.go | | | 2 | ++ |
D | res/base/header.html | | | 23 | ----------------------- |
M | res/index.html | | | 27 | ++++++++++++++++++++++++++- |
M | res/repo_log.html | | | 2 | +- |
M | res/repo_refs.html | | | 2 | +- |
M | res/repo_tree.html | | | 2 | +- |
M | res/res.go | | | 13 | ++++++++----- |
A | res/user/header.html | | | 15 | +++++++++++++++ |
A | res/user/sessions.html | | | 29 | +++++++++++++++++++++++++++++ |
M | src/admin.go | | | 12 | ++++++------ |
M | src/auth.go | | | 41 | +++++++++++++++++++++++++++++++---------- |
M | src/goit.go | | | 18 | ++++++++++-------- |
M | src/http.go | | | 7 | ++++--- |
M | src/repo.go | | | 4 | ++-- |
M | src/user.go | | | 51 | ++++++++++++++++++++++++++++++++++++++++++--------- |
15 files changed, 178 insertions, 70 deletions
diff --git a/main.go b/main.go index 7debc01..4783b59 100644 --- a/main.go +++ b/main.go @@ -24,8 +24,10 @@ func main() { h.StrictSlash(true) h.Path("/").HandlerFunc(goit.HandleIndex) + // h.Path("/user").Methods("GET").HandlerFunc(goit.HandleUserIndex) h.Path("/user/login").Methods("GET", "POST").HandlerFunc(goit.HandleUserLogin) h.Path("/user/logout").Methods("GET", "POST").HandlerFunc(goit.HandleUserLogout) + h.Path("/user/sessions").Methods("GET", "POST").HandlerFunc(goit.HandleUserSessions) h.Path("/repo/create").Methods("GET", "POST").HandlerFunc(goit.HandleRepoCreate) h.Path("/admin").Methods("GET").HandlerFunc(goit.HandleAdminIndex) h.Path("/admin/users").Methods("GET").HandlerFunc(goit.HandleAdminUsers) diff --git a/res/base/header.html b/res/base/header.html deleted file mode 100644 index da113bb..0000000 --- a/res/base/header.html +++ /dev/null @@ -1,23 +0,0 @@ -{{define "base/header"}} -<table> - <tr> - <td rowspan="2"> - <a href="/"><img style="max-height: 24px;" src="/static/favicon.png"></a> - </td> - <td><h1>{{.Title}}</h1></td> - </tr> - <tr> - <td> - <a href="/">Repositories</a> - {{if .Admin}} - | <a href="/admin">Admin</a> - {{end}} - {{if .Auth}} - | <a href="/user/logout">Logout</a>{{if .Username}} ({{.Username}}){{end}} - {{else}} - | <a href="/user/login">Login</a> - {{end}} - </td> - </tr> -</table> -{{end}} diff --git a/res/index.html b/res/index.html index 628c8f6..44b60d1 100644 --- a/res/index.html +++ b/res/index.html @@ -1,7 +1,32 @@ <!DOCTYPE html> <head>{{template "base/head" .}}</head> <body> - <header>{{template "base/header" .}}</header><hr> + <header> + <table> + <tr> + <td rowspan="2"> + <a href="/"><img style="max-height: 24px;" src="/static/favicon.png"></a> + </td> + <td><h1>{{.Title}}</h1></td> + </tr> + <tr> + <td> + <a href="/">Repositories</a> + {{if .Auth}} + | <a href="/user/sessions">User</a> + {{end}} + {{if .Admin}} + | <a href="/admin">Admin</a> + {{end}} + {{if .Auth}} + | <a href="/user/logout">Logout</a>{{if .Username}} ({{.Username}}){{end}} + {{else}} + | <a href="/user/login">Login</a> + {{end}} + </td> + </tr> + </table> + </header><hr> <main> <table> <thead> diff --git a/res/repo_log.html b/res/repo_log.html index 4c1aeac..155a31b 100644 --- a/res/repo_log.html +++ b/res/repo_log.html @@ -1,7 +1,7 @@ <!DOCTYPE html> <head>{{template "base/head" .}}</head> <body> - <header>{{template "base/repo_header" .}}</header><hr> + <header>{{template "repo/header" .}}</header><hr> <main> {{if .Commits}} <table> diff --git a/res/repo_refs.html b/res/repo_refs.html index b250660..4b9097c 100644 --- a/res/repo_refs.html +++ b/res/repo_refs.html @@ -1,7 +1,7 @@ <!DOCTYPE html> <head>{{template "base/head" .}}</head> <body> - <header>{{template "base/repo_header" .}}</header><hr> + <header>{{template "repo/header" .}}</header><hr> <main> {{if .Branches}} <h2>Branches</h2> diff --git a/res/repo_tree.html b/res/repo_tree.html index 4ca17bc..6dc6935 100644 --- a/res/repo_tree.html +++ b/res/repo_tree.html @@ -1,7 +1,7 @@ <!DOCTYPE html> <head>{{template "base/head" .}}</head> <body> - <header>{{template "base/repo_header" .}}</header><hr> + <header>{{template "repo/header" .}}</header><hr> <main> <table> <thead> diff --git a/res/res.go b/res/res.go index 837f577..ee395c2 100644 --- a/res/res.go +++ b/res/res.go @@ -11,9 +11,6 @@ var Index string //go:embed base/head.html var BaseHead string -//go:embed base/header.html -var BaseHeader string - //go:embed admin/index.html var AdminIndex string @@ -32,12 +29,18 @@ var AdminRepos string //go:embed admin/repo_edit.html var AdminRepoEdit string -//go:embed repo/header.html -var RepoHeader string +//go:embed user/header.html +var UserHeader string //go:embed user/login.html var UserLogin string +//go:embed user/sessions.html +var UserSessions string + +//go:embed repo/header.html +var RepoHeader string + //go:embed repo_create.html var RepoCreate string diff --git a/res/user/header.html b/res/user/header.html new file mode 100644 index 0000000..54e63d1 --- /dev/null +++ b/res/user/header.html @@ -0,0 +1,15 @@ +{{define "user/header"}} +<table> + <tr> + <td rowspan="2"> + <a href="/"><img style="max-height: 24px;" src="/static/favicon.png"></a> + </td> + <td><h1>{{.Title}}</h1></td> + </tr> + <tr> + <td> + <a href="/user/sessions">Sessions</a> + </td> + </tr> +</table> +{{end}} diff --git a/res/user/sessions.html b/res/user/sessions.html new file mode 100644 index 0000000..3fe4086 --- /dev/null +++ b/res/user/sessions.html @@ -0,0 +1,29 @@ +<!DOCTYPE html> +<head>{{template "base/head" .}}</head> +<body> + <header>{{template "user/header" .}}</header><hr> + <main> + <table> + <thead> + <tr> + <td><b>IP</b></td> + <td><b>Seen</b></td> + <td><b>Expiry</b></td> + <td></td> + <td></td> + </tr> + </thead> + <tbody> + {{range .Sessions}} + <tr> + <td>{{.Ip}}</a></td> + <td>{{.Seen}}</td> + <td>{{.Expiry}}</td> + <td><a href="">revoke</a></td> + <td>{{.Current}}</td> + </tr> + {{end}} + </tbody> + </table> + </main> +</body> diff --git a/src/admin.go b/src/admin.go index c14579a..9b98ca7 100644 --- a/src/admin.go +++ b/src/admin.go @@ -16,7 +16,7 @@ import ( ) func HandleAdminIndex(w http.ResponseWriter, r *http.Request) { - if _, admin, _ := AuthCookieAdmin(r); !admin { + if _, admin, _ := AuthCookieAdmin(w, r, true); !admin { HttpError(w, http.StatusNotFound) return } @@ -27,7 +27,7 @@ func HandleAdminIndex(w http.ResponseWriter, r *http.Request) { } func HandleAdminUsers(w http.ResponseWriter, r *http.Request) { - if _, admin, _ := AuthCookieAdmin(r); !admin { + if _, admin, _ := AuthCookieAdmin(w, r, true); !admin { HttpError(w, http.StatusNotFound) return } @@ -72,7 +72,7 @@ func HandleAdminUsers(w http.ResponseWriter, r *http.Request) { } func HandleAdminUserCreate(w http.ResponseWriter, r *http.Request) { - if _, admin, _ := AuthCookieAdmin(r); !admin { + if _, admin, _ := AuthCookieAdmin(w, r, true); !admin { HttpError(w, http.StatusNotFound) return } @@ -117,7 +117,7 @@ func HandleAdminUserCreate(w http.ResponseWriter, r *http.Request) { } func HandleAdminUserEdit(w http.ResponseWriter, r *http.Request) { - if _, admin, _ := AuthCookieAdmin(r); !admin { + if _, admin, _ := AuthCookieAdmin(w, r, true); !admin { HttpError(w, http.StatusNotFound) return } @@ -195,7 +195,7 @@ func HandleAdminUserEdit(w http.ResponseWriter, r *http.Request) { } func HandleAdminRepos(w http.ResponseWriter, r *http.Request) { - if _, admin, _ := AuthCookieAdmin(r); !admin { + if _, admin, _ := AuthCookieAdmin(w, r, true); !admin { HttpError(w, http.StatusNotFound) return } @@ -250,7 +250,7 @@ func HandleAdminRepos(w http.ResponseWriter, r *http.Request) { } func HandleAdminRepoEdit(w http.ResponseWriter, r *http.Request) { - if _, admin, _ := AuthCookieAdmin(r); !admin { + if _, admin, _ := AuthCookieAdmin(w, r, true); !admin { HttpError(w, http.StatusNotFound) return } diff --git a/src/auth.go b/src/auth.go index 8a28428..4cefd53 100644 --- a/src/auth.go +++ b/src/auth.go @@ -9,6 +9,7 @@ import ( "encoding/base64" "fmt" "log" + "net" "net/http" "strconv" "strings" @@ -19,8 +20,8 @@ import ( ) type Session struct { - Token, Ip string - Expiry time.Time + Token, Ip string + Seen, Expiry time.Time } var Sessions = map[int64]map[string]Session{} @@ -36,7 +37,7 @@ func NewSession(uid int64, ip string, expiry time.Time) (Session, error) { } t := base64.StdEncoding.EncodeToString(b) - Sessions[uid][t] = Session{t, ip, expiry} + Sessions[uid][t] = Session{Token: t, Ip: util.If(Conf.IpSessions, ip, ""), Seen: time.Now(), Expiry: expiry} return Sessions[uid][t], nil } @@ -65,9 +66,12 @@ func CleanupSessions() { } func SetSessionCookie(w http.ResponseWriter, uid int64, s Session) { - http.SetCookie(w, &http.Cookie{ - Name: "session", Value: fmt.Sprint(uid) + "." + s.Token, Path: "/", Expires: s.Expiry, - }) + c := &http.Cookie{Name: "session", Value: fmt.Sprint(uid) + "." + s.Token, Path: "/", Expires: s.Expiry} + if err := c.Valid(); err != nil { + log.Println("[Cookie]", err.Error()) + } + + http.SetCookie(w, c) } func GetSessionCookie(r *http.Request) (int64, Session) { @@ -82,7 +86,13 @@ func GetSessionCookie(r *http.Request) (int64, Session) { return -1, Session{} } - return id, Sessions[id][ss[1]] + s := Sessions[id][ss[1]] + if s != (Session{}) { + s.Seen = time.Now() + Sessions[id][ss[1]] = s + } + + return id, s } return -1, Session{} @@ -92,9 +102,20 @@ func EndSessionCookie(w http.ResponseWriter) { http.SetCookie(w, &http.Cookie{Name: "session", Path: "/", MaxAge: -1}) } -func AuthCookie(r *http.Request) (auth bool, uid int64) { +func AuthCookie(w http.ResponseWriter, r *http.Request, renew bool) (auth bool, uid int64) { if uid, s := GetSessionCookie(r); s != (Session{}) { if s.Expiry.After(time.Now()) { + if renew && time.Until(s.Expiry) < 24*time.Hour { + ip, _, _ := net.SplitHostPort(r.RemoteAddr) + s1, err := NewSession(uid, ip, time.Now().Add(2*24*time.Hour)) + if err != nil { + log.Println("[Renew Auth]", err.Error()) + } else { + SetSessionCookie(w, uid, s1) + EndSession(uid, s.Token) + } + } + return true, uid } @@ -104,8 +125,8 @@ func AuthCookie(r *http.Request) (auth bool, uid int64) { return false, -1 } -func AuthCookieAdmin(r *http.Request) (auth bool, admin bool, uid int64) { - if ok, uid := AuthCookie(r); ok { +func AuthCookieAdmin(w http.ResponseWriter, r *http.Request, renew bool) (auth bool, admin bool, uid int64) { + if ok, uid := AuthCookie(w, r, renew); ok { if user, err := GetUser(uid); err == nil && user.IsAdmin { return true, true, uid } diff --git a/src/goit.go b/src/goit.go index f9cb765..b71c9a1 100644 --- a/src/goit.go +++ b/src/goit.go @@ -18,17 +18,19 @@ import ( ) type Config struct { - DataPath string `json:"data_path"` - HttpAddr string `json:"http_addr"` - HttpPort string `json:"http_port"` - GitPath string `json:"git_path"` + DataPath string `json:"data_path"` + HttpAddr string `json:"http_addr"` + HttpPort string `json:"http_port"` + GitPath string `json:"git_path"` + IpSessions bool `json:"ip_sessions"` } var Conf = Config{ - DataPath: path.Join(xdg.DataHome, "goit"), - HttpAddr: "", - HttpPort: "8080", - GitPath: "git", + DataPath: path.Join(xdg.DataHome, "goit"), + HttpAddr: "", + HttpPort: "8080", + GitPath: "git", + IpSessions: true, } var db *sql.DB diff --git a/src/http.go b/src/http.go index 918646e..cb083cb 100644 --- a/src/http.go +++ b/src/http.go @@ -17,7 +17,6 @@ var tmpl = template.Must(template.New("error").Parse(res.Error)) func init() { template.Must(tmpl.New("index").Parse(res.Index)) template.Must(tmpl.New("base/head").Parse(res.BaseHead)) - template.Must(tmpl.New("base/header").Parse(res.BaseHeader)) template.Must(tmpl.New("admin/index").Parse(res.AdminIndex)) template.Must(tmpl.New("admin/users").Parse(res.AdminUsers)) @@ -26,9 +25,11 @@ func init() { template.Must(tmpl.New("admin/repos").Parse(res.AdminRepos)) template.Must(tmpl.New("admin/repo/edit").Parse(res.AdminRepoEdit)) - template.Must(tmpl.New("base/repo_header").Parse(res.RepoHeader)) - template.Must(tmpl.New("user_login").Parse(res.UserLogin)) + template.Must(tmpl.New("user/header").Parse(res.UserHeader)) + template.Must(tmpl.New("user/login").Parse(res.UserLogin)) + template.Must(tmpl.New("user/sessions").Parse(res.UserSessions)) + template.Must(tmpl.New("repo/header").Parse(res.RepoHeader)) template.Must(tmpl.New("repo_create").Parse(res.RepoCreate)) template.Must(tmpl.New("repo_log").Parse(res.RepoLog)) diff --git a/src/repo.go b/src/repo.go index 0f7b260..83fc769 100644 --- a/src/repo.go +++ b/src/repo.go @@ -31,7 +31,7 @@ type Repo struct { } func HandleIndex(w http.ResponseWriter, r *http.Request) { - auth, admin, uid := AuthCookieAdmin(r) + auth, admin, uid := AuthCookieAdmin(w, r, true) user, err := GetUser(uid) if err != nil { @@ -86,7 +86,7 @@ func HandleIndex(w http.ResponseWriter, r *http.Request) { } func HandleRepoCreate(w http.ResponseWriter, r *http.Request) { - if ok, uid := AuthCookie(r); !ok { + if ok, uid := AuthCookie(w, r, true); !ok { HttpError(w, http.StatusUnauthorized) } else if r.Method == http.MethodPost { name := r.FormValue("reponame") diff --git a/src/user.go b/src/user.go index cd6bec8..fc967c8 100644 --- a/src/user.go +++ b/src/user.go @@ -10,9 +10,12 @@ import ( "errors" "fmt" "log" + "net" "net/http" "strings" "time" + + "github.com/Jamozed/Goit/src/util" ) type User struct { @@ -28,7 +31,7 @@ type User struct { var reserved []string = []string{"admin", "repo", "static", "user"} func HandleUserLogin(w http.ResponseWriter, r *http.Request) { - if ok, _ := AuthCookie(r); ok { + if ok, _ := AuthCookie(w, r, true); ok { http.Redirect(w, r, "/", http.StatusFound) return } @@ -56,18 +59,21 @@ func HandleUserLogin(w http.ResponseWriter, r *http.Request) { return } else if !bytes.Equal(Hash(password, u.Salt), u.Pass) { data.Message = "Invalid credentials" - } else if s, err := NewSession(u.Id, r.RemoteAddr, time.Now().Add(15*time.Minute)); err != nil { - log.Println("[User:Login:Session]", err.Error()) - HttpError(w, http.StatusInternalServerError) - return } else { - SetSessionCookie(w, u.Id, s) - http.Redirect(w, r, "/", http.StatusFound) - return + ip, _, _ := net.SplitHostPort(r.RemoteAddr) + if s, err := NewSession(u.Id, ip, time.Now().Add(2*24*time.Hour)); err != nil { + log.Println("[User:Login:Session]", err.Error()) + HttpError(w, http.StatusInternalServerError) + return + } else { + SetSessionCookie(w, u.Id, s) + http.Redirect(w, r, "/", http.StatusFound) + return + } } } - if err := tmpl.ExecuteTemplate(w, "user_login", data); err != nil { + if err := tmpl.ExecuteTemplate(w, "user/login", data); err != nil { log.Println("[/user/login]", err.Error()) } } @@ -79,6 +85,33 @@ func HandleUserLogout(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, "/", http.StatusFound) } +func HandleUserSessions(w http.ResponseWriter, r *http.Request) { + auth, uid := AuthCookie(w, r, true) + if !auth { + HttpError(w, http.StatusUnauthorized) + return + } + + _, ss := GetSessionCookie(r) + + type row struct{ Ip, Seen, Expiry, Current string } + data := struct { + Title string + Sessions []row + }{Title: "User - Sessions"} + + for k, v := range Sessions[uid] { + data.Sessions = append(data.Sessions, row{ + Ip: v.Ip, Seen: v.Seen.Format(time.DateTime), Expiry: v.Expiry.Format(time.DateTime), + Current: util.If(k == ss.Token, "(current)", ""), + }) + } + + if err := tmpl.ExecuteTemplate(w, "user/sessions", data); err != nil { + log.Println("[/user/login]", err.Error()) + } +} + func GetUser(id int64) (*User, error) { u := User{}