i cant uncache chrome fuckit

master
Bel LaPointe 2022-02-18 10:53:45 -07:00
parent aeb5781ec9
commit 4657dd9505
7 changed files with 289 additions and 124 deletions

View File

@ -1,10 +1,12 @@
package main
import (
"context"
"encoding/base64"
"encoding/json"
"errors"
"hash/crc32"
"log"
"net/http"
"os"
"time"
@ -16,9 +18,21 @@ var cookieSecret = os.Getenv("COOKIE_SECRET")
type User struct {
User string
Group string
Groups []string
}
func (user User) Is(other User) bool {
for i := range user.Groups {
if i >= len(other.Groups) || user.Groups[i] != other.Groups[i] {
return false
}
}
return user.User == other.User &&
user.Group == other.Group &&
len(user.Groups) == len(other.Groups)
}
type Cookie struct {
Hash string
Salt string
@ -27,21 +41,30 @@ type Cookie struct {
func (server *Server) authenticate(w http.ResponseWriter, r *http.Request) (*Server, bool, error) {
if done, err := server.parseLogin(w, r); err != nil {
log.Printf("error parsing login: %v", err)
return nil, false, err
} else if done {
log.Printf("login rendered body")
return nil, true, nil
}
if ok, err := needsLogin(r); err != nil {
log.Printf("error checking if login needed: %v", err)
return nil, false, err
} else if ok {
log.Printf("needs login")
promptLogin(w)
return nil, true, nil
}
if done, err := changeNamespace(w, r); err != nil {
return nil, false, err
} else if done {
return nil, true, nil
}
if ok, err := needsLogin(r); err != nil {
return nil, false, err
} else if ok {
promptLogin(w)
return nil, true, nil
}
user, _ := loginCookie(r)
namespace, _ := namespaceCookie(r)
return server.WithLoggedIn(user.User, namespace, user.Groups), false, nil
return server.WithUser(user.User, user.Group, user.Groups), false, nil
}
func promptLogin(w http.ResponseWriter) {
@ -54,6 +77,7 @@ func (server *Server) parseLogin(w http.ResponseWriter, r *http.Request) (bool,
if !ok {
return false, nil
}
ok, err := server.auth.Login(username, password)
if err != nil {
return false, err
@ -62,6 +86,7 @@ func (server *Server) parseLogin(w http.ResponseWriter, r *http.Request) (bool,
promptLogin(w)
return true, nil
}
groups, err := server.auth.Groups(username)
if err != nil {
return false, err
@ -69,11 +94,47 @@ func (server *Server) parseLogin(w http.ResponseWriter, r *http.Request) (bool,
if len(groups) == 0 {
return false, errors.New("user has no groups")
}
setLoginCookie(w, r, User{
user := User{
User: username,
Groups: groups,
})
setNamespaceCookie(w, r, groups[0])
Group: groups[0],
}
olduser, _ := loginCookie(r)
for i := range groups {
if groups[i] == olduser.Group {
user.Group = olduser.Group
}
}
log.Printf("%+v => %+v", olduser, user)
setLoginCookie(w, r, user)
return false, nil
}
func changeNamespace(w http.ResponseWriter, r *http.Request) (bool, error) {
want := r.URL.Query().Get("namespace")
if want == "" {
return false, nil
}
user, ok := loginCookie(r)
if !ok {
promptLogin(w)
return true, nil
}
if user.Group == want {
return false, nil
}
for i := range user.Groups {
if want == user.Groups[i] {
user.Group = want
setLoginCookie(w, r, user)
return false, nil
}
}
return false, nil
}
@ -82,12 +143,8 @@ func needsLogin(r *http.Request) (bool, error) {
if !ok {
return true, nil
}
group, ok := namespaceCookie(r)
if !ok {
return true, nil
}
for i := range user.Groups {
if group == user.Groups[i] {
if user.Group == user.Groups[i] {
return false, nil
}
}
@ -99,41 +156,39 @@ func setLoginCookie(w http.ResponseWriter, r *http.Request, user User) {
Name: "login",
Value: encodeUserCookie(user),
Expires: time.Now().Add(time.Hour * 24),
Path: "/",
}
w.Header().Add("Set-Cookie", cookie.String())
r.AddCookie(cookie)
if was, ok := requestLoginCookie(r); !ok || !was.Is(user) {
w.Header().Set("Set-Cookie", cookie.String())
}
log.Printf("setting login cookie: %+v", user)
*r = *r.WithContext(context.WithValue(r.Context(), "LOGIN_COOKIE", cookie.Value))
}
func loginCookie(r *http.Request) (User, bool) {
if v := r.Context().Value("LOGIN_COOKIE"); v != nil {
log.Printf("login cookie from ctx")
return decodeUserCookie(v.(string))
}
return requestLoginCookie(r)
}
func requestLoginCookie(r *http.Request) (User, bool) {
c, ok := getCookie("login", r)
log.Printf("request login cookie: %v, %v", c, ok)
if !ok {
return User{}, false
}
return decodeUserCookie(c)
}
func setNamespaceCookie(w http.ResponseWriter, r *http.Request, s string) {
cookie := &http.Cookie{
Name: "namespace",
Value: s,
Expires: time.Now().Add(time.Hour * 24),
}
w.Header().Add("Set-Cookie", cookie.String())
r.AddCookie(cookie)
}
func namespaceCookie(r *http.Request) (string, bool) {
return getCookie("namespace", r)
}
func getCookie(key string, r *http.Request) (string, bool) {
cookies := r.Cookies()
for i := range cookies {
if cookies[i].Name == key && (cookies[i].Expires.IsZero() || time.Now().Before(cookies[i].Expires)) {
return cookies[i].Value, true
}
cookie, err := r.Cookie(key)
if err != nil {
log.Printf("err getting cookie %s: %v: %+v", key, err, r.Cookies())
return "", false
}
return "", false
return cookie.Value, cookie.Expires.IsZero() || time.Now().Before(cookie.Expires)
}
func decodeUserCookie(raw string) (User, bool) {

View File

@ -65,9 +65,6 @@ func TestGetSetLoginCookie(t *testing.T) {
if w.Header().Get("Set-Cookie") == "" {
t.Error(w.Header())
}
if r.Cookies()[0].Name != "login" {
t.Error(r.Cookies())
}
got, ok := loginCookie(r)
if !ok {
@ -78,9 +75,110 @@ func TestGetSetLoginCookie(t *testing.T) {
}
}
func TestChangeNamespace(t *testing.T) {
newTestServer(t)
user := User{
User: "user",
Groups: []string{"group", "othergroup"},
Group: "group",
}
t.Run("noop", func(t *testing.T) {
r := httptest.NewRequest(http.MethodGet, "/", nil)
w := httptest.NewRecorder()
done, err := changeNamespace(w, r)
if err != nil {
t.Error(err)
}
if done {
t.Error(done)
}
})
t.Run("change to ``", func(t *testing.T) {
r := httptest.NewRequest(http.MethodGet, "/?namespace=", nil)
w := httptest.NewRecorder()
done, err := changeNamespace(w, r)
if err != nil {
t.Error(err)
}
if done {
t.Error(done)
}
})
t.Run("change to bad", func(t *testing.T) {
r := httptest.NewRequest(http.MethodGet, "/?namespace=never", nil)
w := httptest.NewRecorder()
setLoginCookie(w, r, user)
done, err := changeNamespace(w, r)
if err != nil {
t.Error(err)
}
if done {
t.Error(done)
}
user, ok := loginCookie(r)
if !ok {
t.Error(ok)
}
if user.Group == "never" {
t.Error("change namespace acknowledged bad change")
}
})
t.Run("change without login", func(t *testing.T) {
r := httptest.NewRequest(http.MethodGet, "/?namespace="+user.Group, nil)
w := httptest.NewRecorder()
done, err := changeNamespace(w, r)
if err != nil {
t.Error(err)
}
if !done {
t.Error(done)
}
})
t.Run("change to same", func(t *testing.T) {
r := httptest.NewRequest(http.MethodGet, "/?namespace="+user.Group, nil)
w := httptest.NewRecorder()
setLoginCookie(w, r, user)
done, err := changeNamespace(w, r)
if err != nil {
t.Error(err)
}
if done {
t.Error(done)
}
})
t.Run("change to ok", func(t *testing.T) {
r := httptest.NewRequest(http.MethodGet, "/?namespace="+user.Groups[1], nil)
w := httptest.NewRecorder()
setLoginCookie(w, r, user)
done, err := changeNamespace(w, r)
if err != nil {
t.Error(err)
}
if done {
t.Error(done)
}
user, ok := loginCookie(r)
if !ok {
t.Error(ok)
}
if user.Group != user.Groups[1] {
t.Error(user.Group)
}
if w.Header().Get("Set-Cookie") == "" {
t.Error(w.Header())
}
})
}
func TestNeedsLogin(t *testing.T) {
w := httptest.NewRecorder()
user := User{User: "user", Groups: []string{"group0", "group1"}}
user := User{User: "user", Groups: []string{"group0", "group1"}, Group: "group0"}
t.Run("no login provided", func(t *testing.T) {
r := httptest.NewRequest(http.MethodGet, "/", nil)
@ -93,7 +191,9 @@ func TestNeedsLogin(t *testing.T) {
t.Run("no namespace provided", func(t *testing.T) {
r := httptest.NewRequest(http.MethodGet, "/", nil)
setLoginCookie(w, r, user)
u2 := user
u2.Group = ""
setLoginCookie(w, r, u2)
if ok, err := needsLogin(r); err != nil {
t.Fatal(err)
} else if !ok {
@ -104,7 +204,6 @@ func TestNeedsLogin(t *testing.T) {
t.Run("cookie tampered", func(t *testing.T) {
r := httptest.NewRequest(http.MethodGet, "/", nil)
setLoginCookie(w, r, user)
setNamespaceCookie(w, r, user.Groups[0])
cookieSecret += "modified"
if ok, err := needsLogin(r); err != nil {
t.Fatal(err)
@ -115,8 +214,9 @@ func TestNeedsLogin(t *testing.T) {
t.Run("bad namespace", func(t *testing.T) {
r := httptest.NewRequest(http.MethodGet, "/", nil)
setLoginCookie(w, r, user)
setNamespaceCookie(w, r, "teehee")
u2 := user
u2.Group = "teehee"
setLoginCookie(w, r, u2)
if ok, err := needsLogin(r); err != nil {
t.Fatal(err)
} else if !ok {
@ -127,7 +227,6 @@ func TestNeedsLogin(t *testing.T) {
t.Run("ok", func(t *testing.T) {
r := httptest.NewRequest(http.MethodGet, "/", nil)
setLoginCookie(w, r, user)
setNamespaceCookie(w, r, user.Groups[0])
if ok, err := needsLogin(r); err != nil {
t.Fatal(err)
} else if ok {
@ -172,15 +271,9 @@ func TestServerParseLogin(t *testing.T) {
if w.Code == http.StatusUnauthorized {
t.Error(w.Code)
}
if len(w.Header()["Set-Cookie"]) != 2 {
if len(w.Header()["Set-Cookie"]) != 1 {
t.Error(w.Header())
}
if len(r.Cookies()) != 2 {
t.Error(r.Cookies())
}
if v, ok := namespaceCookie(r); !ok || v != "group" {
t.Error(r.Cookies())
}
if user, ok := loginCookie(r); !ok || user.User != "user" || user.Groups[0] != "group" || user.Groups[1] != "othergroup" {
t.Error(user)
}
@ -192,8 +285,7 @@ func TestServerAuthenticate(t *testing.T) {
t.Run("ok: already logged in", func(t *testing.T) {
r := httptest.NewRequest(http.MethodGet, "/", nil)
setLoginCookie(httptest.NewRecorder(), r, User{User: "user", Groups: []string{"group", "othergroup"}})
setNamespaceCookie(httptest.NewRecorder(), r, "othergroup")
setLoginCookie(httptest.NewRecorder(), r, User{User: "user", Group: "othergroup", Groups: []string{"group", "othergroup"}})
s2, done, err := server.authenticate(nil, r)
if err != nil {
t.Error(err)
@ -204,20 +296,20 @@ func TestServerAuthenticate(t *testing.T) {
if server == s2 {
t.Error(done)
}
if server.loggedIn != nil {
t.Error(server.loggedIn)
if server.user != nil {
t.Error(server.user)
}
if s2.loggedIn == nil {
t.Error(s2.loggedIn)
if s2.user == nil {
t.Error(s2.user)
}
if s2.loggedIn.user != "user" {
t.Error(s2.loggedIn)
if s2.user.User != "user" {
t.Error(s2.user)
}
if s2.loggedIn.group != "othergroup" {
t.Error(s2.loggedIn)
if s2.user.Group != "othergroup" {
t.Error(s2.user)
}
if fmt.Sprint(s2.loggedIn.groups) != fmt.Sprint([]string{"group", "othergroup"}) {
t.Error(s2.loggedIn)
if fmt.Sprint(s2.user.Groups) != fmt.Sprint([]string{"group", "othergroup"}) {
t.Error(s2.user)
}
})
@ -235,25 +327,25 @@ func TestServerAuthenticate(t *testing.T) {
if server == s2 {
t.Error(done)
}
if server.loggedIn != nil {
t.Error(server.loggedIn)
if server.user != nil {
t.Error(server.user)
}
if s2.loggedIn == nil {
t.Error(s2.loggedIn)
if s2.user == nil {
t.Error(s2.user)
}
if s2.loggedIn.user != "user" {
t.Error(s2.loggedIn)
if s2.user.User != "user" {
t.Error(s2.user)
}
if s2.loggedIn.group != "group" {
t.Error(s2.loggedIn)
if s2.user.Group != "group" {
t.Error(s2.user)
}
if fmt.Sprint(s2.loggedIn.groups) != fmt.Sprint([]string{"group", "othergroup"}) {
t.Error(s2.loggedIn)
if fmt.Sprint(s2.user.Groups) != fmt.Sprint([]string{"group", "othergroup"}) {
t.Error(s2.user)
}
if w.Code != http.StatusOK {
t.Error(w.Code)
}
if len(w.Header()["Set-Cookie"]) != 2 {
if len(w.Header()["Set-Cookie"]) != 1 {
t.Error(w.Header())
}
})

View File

@ -3,6 +3,7 @@ package main
import (
"errors"
"local/args"
"log"
"net/http"
"os"
"path"
@ -26,6 +27,7 @@ func main() {
if err := s.Routes(); err != nil {
panic(err)
}
log.Printf("listening on %v with %s", as.GetInt("p"), as.GetString("auth"))
if err := http.ListenAndServe(":"+strconv.Itoa(as.GetInt("p")), s); err != nil {
panic(err)
}

View File

@ -36,13 +36,13 @@ func main() {
return oneT
}
data := map[string]interface{}{
"Namespaces": []string{"datastore", "dp-orchestration"},
"This": map[string]interface{}{
"Namespaces": []string{"datastore", "dp-orchestration"},
"ID": "id00/id11",
"Title": "title id11",
"ReadOnly": false,
"PID": "id00",
"PTitle": "title id00",
"ID": "id00/id11",
"Title": "title id11",
"ReadOnly": false,
"PID": "id00",
"PTitle": "title id00",
"Content": `# hello
## world

View File

@ -1,16 +1,16 @@
{{ define "_namespace" }}
<script>
function setNamespace() {
document.getElementById("namespace").disabled = true
document.cookie = "namespace=" + document.getElementById("namespace").value
window.location.href = `${window.location.protocol}`+"//"+`${window.location.host}/ui/files`
}
</script>
{{ if .This.Namespaces }}
<select id="namespace" onchange="setNamespace()" style="max-width: 7rem;">
{{ range .This.Namespaces }}
<option>{{ . }}</option>
{{ end }}
</select>
{{ end }}
<script>
function setNamespace() {
document.getElementById("namespace").disabled = true
window.location.href = `${window.location.protocol}`+"//"+`${window.location.host}/ui/files?namespace=${document.getElementById("namespace").value}`
}
</script>
{{ $cur := .Namespace }}
{{ if .Namespaces }}
<select id="namespace" onload="markNamespace()" onchange="setNamespace()" style="max-width: 7rem;">
{{ range .Namespaces }}
<option {{ if eq $cur . }}selected{{ end }}>{{ . }}</option>
{{ end }}
</select>
{{ end }}
{{ end }}

View File

@ -26,37 +26,31 @@ import (
)
type Server struct {
router *router.Router
root string
auth auth
loggedIn *loggedIn
}
type loggedIn struct {
user string
group string
groups []string
router *router.Router
root string
auth auth
user *User
}
func NewServer(root string, auth auth) *Server {
return &Server{
router: router.New(),
root: root,
auth: auth,
root: root,
auth: auth,
}
}
func (server *Server) WithLoggedIn(user, group string, groups []string) *Server {
func (server *Server) WithUser(user, group string, groups []string) *Server {
s2 := *server
s2.loggedIn = &loggedIn{
user: user,
group: group,
groups: groups,
s2.user = &User{
User: user,
Group: group,
Groups: groups,
}
return &s2
}
func (server *Server) Routes() error {
server.router = router.New()
wildcard := func(s string) string {
return strings.TrimSuffix(s, "/") + "/" + router.Wildcard
}
@ -75,7 +69,6 @@ func (server *Server) Routes() error {
"/ui/search": server.uiSearchHandler,
wildcards("/ui/files"): server.uiFilesHandler,
} {
log.Printf("listening for %s", path)
if err := server.router.Add(path, server.tryCatchHttpHandler(handler)); err != nil {
return err
}
@ -85,15 +78,21 @@ func (server *Server) Routes() error {
func (server *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if server.auth != nil {
if s2, done, err := server.authenticate(w, r); err != nil {
s2, done, err := server.authenticate(w, r)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
} else if done {
}
if done {
return
} else if s2 != nil {
}
if s2 != nil {
server = s2
}
}
if err := server.Routes(); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
server.router.ServeHTTP(w, r)
}
@ -264,11 +263,20 @@ func (server *Server) uiSearchHandler(w http.ResponseWriter, r *http.Request) er
return err
}
return t.Lookup("search").Execute(w, map[string]interface{}{
"Results": data,
"Tree": string(branchesJSON),
"Results": data,
"Tree": string(branchesJSON),
"Namespaces": server.getUser().Groups,
"Namespace": server.getUser().Group,
})
}
func (server *Server) getUser() User {
if server.user != nil {
return *server.user
}
return User{}
}
func (server *Server) uiFilesHandler(w http.ResponseWriter, r *http.Request) error {
id := NewID(strings.TrimPrefix(r.URL.Path, "/ui/files"))
t, err := server.uiSubTemplates()
@ -318,7 +326,9 @@ func (server *Server) uiFilesHandler(w http.ResponseWriter, r *http.Request) err
"PID": id.Pop().String(),
"PTitle": parent.Meta.Title,
},
"Tree": string(branchesJSON),
"Tree": string(branchesJSON),
"Namespaces": server.getUser().Groups,
"Namespace": server.getUser().Group,
}
return t.Lookup("files").Execute(w, data)
}
@ -356,7 +366,7 @@ func (server *Server) rootHandler(w http.ResponseWriter, r *http.Request) error
}
func (server *Server) tree() Tree {
return NewTree(path.Join(server.root, "files"))
return NewTree(path.Join(server.root, "files", server.getUser().Group))
}
func (server *Server) diskMediaPath(id string) string {

6
server/testdata/users.yaml vendored Normal file
View File

@ -0,0 +1,6 @@
users:
bel:
password: bel
groups:
- g1
- g2