mirror of
https://github.com/osmarks/mycorrhiza.git
synced 2024-12-12 05:20:26 +00:00
Make account system concurrent-safe and refactor it a little
This commit is contained in:
parent
184aa1ae32
commit
b1f33c872c
2
flag.go
2
flag.go
@ -51,7 +51,7 @@ func parseCliArgs() {
|
|||||||
case "none":
|
case "none":
|
||||||
case "fixed":
|
case "fixed":
|
||||||
user.AuthUsed = true
|
user.AuthUsed = true
|
||||||
user.PopulateFixedUserStorage()
|
user.ReadUsersFromFilesystem()
|
||||||
default:
|
default:
|
||||||
log.Fatal("Error: unknown auth method:", util.AuthMethod)
|
log.Fatal("Error: unknown auth method:", util.AuthMethod)
|
||||||
}
|
}
|
||||||
|
1
go.mod
1
go.mod
@ -6,5 +6,6 @@ require (
|
|||||||
github.com/adrg/xdg v0.2.2
|
github.com/adrg/xdg v0.2.2
|
||||||
github.com/gorilla/feeds v1.1.1
|
github.com/gorilla/feeds v1.1.1
|
||||||
github.com/hashicorp/go-memdb v1.3.0
|
github.com/hashicorp/go-memdb v1.3.0
|
||||||
|
github.com/kr/pretty v0.2.1 // indirect
|
||||||
github.com/valyala/quicktemplate v1.6.3
|
github.com/valyala/quicktemplate v1.6.3
|
||||||
)
|
)
|
||||||
|
11
go.sum
11
go.sum
@ -1,6 +1,7 @@
|
|||||||
github.com/adrg/xdg v0.2.2 h1:A7ZHKRz5KGOLJX/bg7IPzStryhvCzAE1wX+KWawPiAo=
|
github.com/adrg/xdg v0.2.2 h1:A7ZHKRz5KGOLJX/bg7IPzStryhvCzAE1wX+KWawPiAo=
|
||||||
github.com/adrg/xdg v0.2.2/go.mod h1:7I2hH/IT30IsupOpKZ5ue7/qNi3CoKzD6tL3HwpaRMQ=
|
github.com/adrg/xdg v0.2.2/go.mod h1:7I2hH/IT30IsupOpKZ5ue7/qNi3CoKzD6tL3HwpaRMQ=
|
||||||
github.com/andybalholm/brotli v1.0.0/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
|
github.com/andybalholm/brotli v1.0.0/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
|
||||||
|
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/gorilla/feeds v1.1.1 h1:HwKXxqzcRNg9to+BbvJog4+f3s/xzvtZXICcQGutYfY=
|
github.com/gorilla/feeds v1.1.1 h1:HwKXxqzcRNg9to+BbvJog4+f3s/xzvtZXICcQGutYfY=
|
||||||
github.com/gorilla/feeds v1.1.1/go.mod h1:Nk0jZrvPFZX1OBe5NPiddPw7CfwF6Q9eqzaBbaightA=
|
github.com/gorilla/feeds v1.1.1/go.mod h1:Nk0jZrvPFZX1OBe5NPiddPw7CfwF6Q9eqzaBbaightA=
|
||||||
@ -8,14 +9,22 @@ github.com/hashicorp/go-immutable-radix v1.3.0 h1:8exGP7ego3OmkfksihtSouGMZ+hQrh
|
|||||||
github.com/hashicorp/go-immutable-radix v1.3.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
github.com/hashicorp/go-immutable-radix v1.3.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
||||||
github.com/hashicorp/go-memdb v1.3.0 h1:xdXq34gBOMEloa9rlGStLxmfX/dyIK8htOv36dQUwHU=
|
github.com/hashicorp/go-memdb v1.3.0 h1:xdXq34gBOMEloa9rlGStLxmfX/dyIK8htOv36dQUwHU=
|
||||||
github.com/hashicorp/go-memdb v1.3.0/go.mod h1:Mluclgwib3R93Hk5fxEfiRhB+6Dar64wWh71LpNSe3g=
|
github.com/hashicorp/go-memdb v1.3.0/go.mod h1:Mluclgwib3R93Hk5fxEfiRhB+6Dar64wWh71LpNSe3g=
|
||||||
|
github.com/hashicorp/go-uuid v1.0.0 h1:RS8zrF7PhGwyNPOtxSClXXj9HA8feRnJzgnI1RJCSnM=
|
||||||
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||||
github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
|
github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
|
||||||
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
|
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
|
||||||
github.com/klauspost/compress v1.10.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
github.com/klauspost/compress v1.10.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
||||||
github.com/klauspost/compress v1.11.0/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
github.com/klauspost/compress v1.11.0/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
||||||
|
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
|
||||||
|
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||||
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
|
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||||
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
|
||||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||||
@ -29,5 +38,7 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h
|
|||||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/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=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
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/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
@ -121,8 +121,7 @@ func (hop *HistoryOp) WithMsg(userMsg string) *HistoryOp {
|
|||||||
|
|
||||||
// WithUser sets a user for the commit.
|
// WithUser sets a user for the commit.
|
||||||
func (hop *HistoryOp) WithUser(u *user.User) *HistoryOp {
|
func (hop *HistoryOp) WithUser(u *user.User) *HistoryOp {
|
||||||
u = u.OrAnon()
|
if u.Group != "anon" {
|
||||||
if u.Group != user.UserAnon {
|
|
||||||
hop.name = u.Name
|
hop.name = u.Name
|
||||||
hop.email = u.Name + "@mycorrhiza"
|
hop.email = u.Name + "@mycorrhiza"
|
||||||
}
|
}
|
||||||
|
@ -44,7 +44,7 @@ func handlerRenameConfirm(w http.ResponseWriter, rq *http.Request) {
|
|||||||
newName = CanonicalName(rq.PostFormValue("new-name"))
|
newName = CanonicalName(rq.PostFormValue("new-name"))
|
||||||
_, newNameIsUsed = HyphaStorage[newName]
|
_, newNameIsUsed = HyphaStorage[newName]
|
||||||
recursive = rq.PostFormValue("recursive") == "true"
|
recursive = rq.PostFormValue("recursive") == "true"
|
||||||
u = user.FromRequest(rq).OrAnon()
|
u = user.FromRequest(rq)
|
||||||
)
|
)
|
||||||
switch {
|
switch {
|
||||||
case !u.CanProceed("rename-confirm"):
|
case !u.CanProceed("rename-confirm"):
|
||||||
@ -151,7 +151,7 @@ func handlerUploadText(w http.ResponseWriter, rq *http.Request) {
|
|||||||
var (
|
var (
|
||||||
hyphaName = HyphaNameFromRq(rq, "upload-text")
|
hyphaName = HyphaNameFromRq(rq, "upload-text")
|
||||||
textData = rq.PostFormValue("text")
|
textData = rq.PostFormValue("text")
|
||||||
u = user.FromRequest(rq).OrAnon()
|
u = user.FromRequest(rq)
|
||||||
)
|
)
|
||||||
if ok := user.CanProceed(rq, "upload-text"); !ok {
|
if ok := user.CanProceed(rq, "upload-text"); !ok {
|
||||||
HttpErr(w, http.StatusForbidden, hyphaName, "Not enough rights", "You must be an editor to edit pages.")
|
HttpErr(w, http.StatusForbidden, hyphaName, "Not enough rights", "You must be an editor to edit pages.")
|
||||||
@ -174,7 +174,7 @@ func handlerUploadBinary(w http.ResponseWriter, rq *http.Request) {
|
|||||||
log.Println(rq.URL)
|
log.Println(rq.URL)
|
||||||
var (
|
var (
|
||||||
hyphaName = HyphaNameFromRq(rq, "upload-binary")
|
hyphaName = HyphaNameFromRq(rq, "upload-binary")
|
||||||
u = user.FromRequest(rq).OrAnon()
|
u = user.FromRequest(rq)
|
||||||
)
|
)
|
||||||
if !u.CanProceed("upload-binary") {
|
if !u.CanProceed("upload-binary") {
|
||||||
HttpErr(w, http.StatusForbidden, hyphaName, "Not enough rights", "You must be an editor to upload attachments.")
|
HttpErr(w, http.StatusForbidden, hyphaName, "Not enough rights", "You must be an editor to upload attachments.")
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package hyphae
|
package hyphae
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/hashicorp/go-memdb"
|
"github.com/bouncepaw/mycorrhiza/storage"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Hypha struct {
|
type Hypha struct {
|
||||||
@ -15,7 +15,7 @@ type Hypha struct {
|
|||||||
|
|
||||||
// AddHypha adds a hypha named `name` with such `textPath` and `binaryPath`. Both paths can be empty. Does //not// check for hypha's existence beforehand. Count is handled.
|
// AddHypha adds a hypha named `name` with such `textPath` and `binaryPath`. Both paths can be empty. Does //not// check for hypha's existence beforehand. Count is handled.
|
||||||
func AddHypha(name, textPath, binaryPath string) {
|
func AddHypha(name, textPath, binaryPath string) {
|
||||||
txn := db.Txn(true)
|
txn := storage.DB.Txn(true)
|
||||||
txn.Insert("hyphae",
|
txn.Insert("hyphae",
|
||||||
&Hypha{
|
&Hypha{
|
||||||
Name: name,
|
Name: name,
|
||||||
@ -31,54 +31,3 @@ func AddHypha(name, textPath, binaryPath string) {
|
|||||||
// DeleteHypha clears both paths and all out-links from the named hypha and marks it as non-existent. It does not actually delete it from the memdb. Count is handled.
|
// DeleteHypha clears both paths and all out-links from the named hypha and marks it as non-existent. It does not actually delete it from the memdb. Count is handled.
|
||||||
func DeleteHypha(name string) {
|
func DeleteHypha(name string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create the DB schema
|
|
||||||
var schema = &memdb.DBSchema{
|
|
||||||
Tables: map[string]*memdb.TableSchema{
|
|
||||||
"hyphae": &memdb.TableSchema{
|
|
||||||
Name: "hyphae",
|
|
||||||
Indexes: map[string]*memdb.IndexSchema{
|
|
||||||
"id": &memdb.IndexSchema{
|
|
||||||
Name: "id",
|
|
||||||
Unique: true,
|
|
||||||
Indexer: &memdb.StringFieldIndex{Field: "Name"},
|
|
||||||
},
|
|
||||||
"exists": &memdb.IndexSchema{
|
|
||||||
Name: "exists",
|
|
||||||
Unique: false,
|
|
||||||
Indexer: &memdb.BoolFieldIndex{Field: "Exists"},
|
|
||||||
},
|
|
||||||
"text-path": &memdb.IndexSchema{
|
|
||||||
Name: "text-path",
|
|
||||||
Unique: true,
|
|
||||||
Indexer: &memdb.StringFieldIndex{Field: "TextPath"},
|
|
||||||
},
|
|
||||||
"binary-path": &memdb.IndexSchema{
|
|
||||||
Name: "binary-path",
|
|
||||||
Unique: true,
|
|
||||||
Indexer: &memdb.StringFieldIndex{Field: "BinaryPath"},
|
|
||||||
},
|
|
||||||
"out-links": &memdb.IndexSchema{
|
|
||||||
Name: "out-links",
|
|
||||||
Unique: false,
|
|
||||||
Indexer: &memdb.StringSliceFieldIndex{Field: "OutLinks"},
|
|
||||||
},
|
|
||||||
"back-links": &memdb.IndexSchema{
|
|
||||||
Name: "back-links",
|
|
||||||
Unique: false,
|
|
||||||
Indexer: &memdb.StringSliceFieldIndex{Field: "BackLinks"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
var db *memdb.MemDB
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
var err error
|
|
||||||
db, err = memdb.NewMemDB(schema)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -120,6 +120,9 @@ func ParagraphToHtml(hyphaName, input string) string {
|
|||||||
startsWith = func(t string) bool {
|
startsWith = func(t string) bool {
|
||||||
return bytes.HasPrefix(p.Bytes(), []byte(t))
|
return bytes.HasPrefix(p.Bytes(), []byte(t))
|
||||||
}
|
}
|
||||||
|
noTagsActive = func() bool {
|
||||||
|
return !(tagState[spanItalic] || tagState[spanBold] || tagState[spanMono] || tagState[spanSuper] || tagState[spanSub] || tagState[spanMark] || tagState[spanLink])
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
for p.Len() != 0 {
|
for p.Len() != 0 {
|
||||||
@ -147,7 +150,7 @@ func ParagraphToHtml(hyphaName, input string) string {
|
|||||||
p.Next(2)
|
p.Next(2)
|
||||||
case startsWith("[["):
|
case startsWith("[["):
|
||||||
ret.WriteString(getLinkNode(p, hyphaName, true))
|
ret.WriteString(getLinkNode(p, hyphaName, true))
|
||||||
case startsWith("https://"), startsWith("http://"), startsWith("gemini://"), startsWith("gopher://"), startsWith("ftp://"):
|
case (startsWith("https://") || startsWith("http://") || startsWith("gemini://") || startsWith("gopher://") || startsWith("ftp://")) && noTagsActive():
|
||||||
ret.WriteString(getLinkNode(p, hyphaName, false))
|
ret.WriteString(getLinkNode(p, hyphaName, false))
|
||||||
default:
|
default:
|
||||||
ret.WriteString(html.EscapeString(getTextNode(p)))
|
ret.WriteString(html.EscapeString(getTextNode(p)))
|
||||||
|
65
storage/storage.go
Normal file
65
storage/storage.go
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
package storage
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/hashicorp/go-memdb"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Create the DB schema
|
||||||
|
var schema = &memdb.DBSchema{
|
||||||
|
Tables: map[string]*memdb.TableSchema{
|
||||||
|
"hyphae": &memdb.TableSchema{
|
||||||
|
Name: "hyphae",
|
||||||
|
Indexes: map[string]*memdb.IndexSchema{
|
||||||
|
"id": &memdb.IndexSchema{
|
||||||
|
Name: "id",
|
||||||
|
Unique: true,
|
||||||
|
Indexer: &memdb.StringFieldIndex{Field: "Name"},
|
||||||
|
},
|
||||||
|
"exists": &memdb.IndexSchema{
|
||||||
|
Name: "exists",
|
||||||
|
Unique: false,
|
||||||
|
Indexer: &memdb.BoolFieldIndex{Field: "Exists"},
|
||||||
|
},
|
||||||
|
"out-links": &memdb.IndexSchema{
|
||||||
|
Name: "out-links",
|
||||||
|
Unique: false,
|
||||||
|
Indexer: &memdb.StringSliceFieldIndex{Field: "OutLinks"},
|
||||||
|
},
|
||||||
|
"back-links": &memdb.IndexSchema{
|
||||||
|
Name: "back-links",
|
||||||
|
Unique: false,
|
||||||
|
Indexer: &memdb.StringSliceFieldIndex{Field: "BackLinks"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var DB *memdb.MemDB
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
var err error
|
||||||
|
DB, err = memdb.NewMemDB(schema)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ForEveryRecord(table string, λ func(obj interface{})) error {
|
||||||
|
txn := DB.Txn(false)
|
||||||
|
defer txn.Abort()
|
||||||
|
|
||||||
|
it, err := txn.Get(table, "id")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for obj := it.Next(); obj != nil; obj = it.Next() {
|
||||||
|
λ(obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TxnW() *memdb.Txn { return DB.Txn(true) }
|
||||||
|
func TxnR() *memdb.Txn { return DB.Txn(false) }
|
@ -6,7 +6,7 @@
|
|||||||
{% if user.AuthUsed %}
|
{% if user.AuthUsed %}
|
||||||
<h1>Login</h1>
|
<h1>Login</h1>
|
||||||
<form method="post" action="/login-data" id="login-form" enctype="multipart/form-data">
|
<form method="post" action="/login-data" id="login-form" enctype="multipart/form-data">
|
||||||
<p>Use the data you were given by the administrator.</p>
|
<p>Use the data you were given by an administrator.</p>
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend>Username</legend>
|
<legend>Username</legend>
|
||||||
<input type="text" required autofocus name="username" autocomplete="on">
|
<input type="text" required autofocus name="username" autocomplete="on">
|
||||||
@ -15,7 +15,7 @@
|
|||||||
<legend>Password</legend>
|
<legend>Password</legend>
|
||||||
<input type="password" required name="password" autocomplete="on">
|
<input type="password" required name="password" autocomplete="on">
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<p>By submitting this form you give this wiki a permission to store cookies in your browser. It lets the engine associate your edits with you.</p>
|
<p>By submitting this form you give this wiki a permission to store cookies in your browser. It lets the engine associate your edits with you. You will stay logged in until you log out.</p>
|
||||||
<input type="submit">
|
<input type="submit">
|
||||||
<a href="/">Cancel</a>
|
<a href="/">Cancel</a>
|
||||||
</form>
|
</form>
|
||||||
|
@ -33,7 +33,7 @@ func StreamLoginHTML(qw422016 *qt422016.Writer) {
|
|||||||
qw422016.N().S(`
|
qw422016.N().S(`
|
||||||
<h1>Login</h1>
|
<h1>Login</h1>
|
||||||
<form method="post" action="/login-data" id="login-form" enctype="multipart/form-data">
|
<form method="post" action="/login-data" id="login-form" enctype="multipart/form-data">
|
||||||
<p>Use the data you were given by the administrator.</p>
|
<p>Use the data you were given by an administrator.</p>
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend>Username</legend>
|
<legend>Username</legend>
|
||||||
<input type="text" required autofocus name="username" autocomplete="on">
|
<input type="text" required autofocus name="username" autocomplete="on">
|
||||||
@ -42,7 +42,7 @@ func StreamLoginHTML(qw422016 *qt422016.Writer) {
|
|||||||
<legend>Password</legend>
|
<legend>Password</legend>
|
||||||
<input type="password" required name="password" autocomplete="on">
|
<input type="password" required name="password" autocomplete="on">
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<p>By submitting this form you give this wiki a permission to store cookies in your browser. It lets the engine associate your edits with you.</p>
|
<p>By submitting this form you give this wiki a permission to store cookies in your browser. It lets the engine associate your edits with you. You will stay logged in until you log out.</p>
|
||||||
<input type="submit">
|
<input type="submit">
|
||||||
<a href="/">Cancel</a>
|
<a href="/">Cancel</a>
|
||||||
</form>
|
</form>
|
||||||
|
@ -21,8 +21,9 @@ var navEntries = []navEntry{
|
|||||||
|
|
||||||
{% func navHTML(rq *http.Request, hyphaName, navType string, revisionHash ...string) %}
|
{% func navHTML(rq *http.Request, hyphaName, navType string, revisionHash ...string) %}
|
||||||
{% code
|
{% code
|
||||||
u := user.FromRequest(rq).OrAnon()
|
u := user.FromRequest(rq)
|
||||||
%}
|
%}
|
||||||
|
|
||||||
<nav class="navlinks">
|
<nav class="navlinks">
|
||||||
<ul>
|
<ul>
|
||||||
{%- for _, entry := range navEntries -%}
|
{%- for _, entry := range navEntries -%}
|
||||||
@ -30,7 +31,7 @@ var navEntries = []navEntry{
|
|||||||
<li><b>{%s revisionHash[0] %}</b></li>
|
<li><b>{%s revisionHash[0] %}</b></li>
|
||||||
{%- elseif navType == entry.path -%}
|
{%- elseif navType == entry.path -%}
|
||||||
<li><b>{%s entry.title %}</b></li>
|
<li><b>{%s entry.title %}</b></li>
|
||||||
{%- elseif entry.path != "revision" && u.Group.CanAccessRoute(entry.path) -%}
|
{%- elseif entry.path != "revision" && u.CanProceed(entry.path) -%}
|
||||||
<li><a href="/{%s entry.path %}/{%s hyphaName %}">{%s entry.title %}</a></li>
|
<li><a href="/{%s entry.path %}/{%s hyphaName %}">{%s entry.title %}</a></li>
|
||||||
{%- endif -%}
|
{%- endif -%}
|
||||||
{%- endfor -%}
|
{%- endfor -%}
|
||||||
@ -42,7 +43,7 @@ var navEntries = []navEntry{
|
|||||||
{% func userMenuHTML(u *user.User) %}
|
{% func userMenuHTML(u *user.User) %}
|
||||||
{% if user.AuthUsed %}
|
{% if user.AuthUsed %}
|
||||||
<li class="navlinks__user">
|
<li class="navlinks__user">
|
||||||
{% if u.Group == user.UserAnon %}
|
{% if u.Group == "anon" %}
|
||||||
<a href="/login">Login</a>
|
<a href="/login">Login</a>
|
||||||
{% else %}
|
{% else %}
|
||||||
<a href="/page/{%s util.UserTree %}/{%s u.Name %}">{%s u.Name %}</a>
|
<a href="/page/{%s util.UserTree %}/{%s u.Name %}">{%s u.Name %}</a>
|
||||||
|
@ -50,163 +50,164 @@ func streamnavHTML(qw422016 *qt422016.Writer, rq *http.Request, hyphaName, navTy
|
|||||||
qw422016.N().S(`
|
qw422016.N().S(`
|
||||||
`)
|
`)
|
||||||
//line templates/common.qtpl:24
|
//line templates/common.qtpl:24
|
||||||
u := user.FromRequest(rq).OrAnon()
|
u := user.FromRequest(rq)
|
||||||
|
|
||||||
//line templates/common.qtpl:25
|
//line templates/common.qtpl:25
|
||||||
qw422016.N().S(`
|
qw422016.N().S(`
|
||||||
|
|
||||||
<nav class="navlinks">
|
<nav class="navlinks">
|
||||||
<ul>
|
<ul>
|
||||||
`)
|
`)
|
||||||
//line templates/common.qtpl:28
|
//line templates/common.qtpl:29
|
||||||
for _, entry := range navEntries {
|
for _, entry := range navEntries {
|
||||||
//line templates/common.qtpl:29
|
//line templates/common.qtpl:30
|
||||||
if navType == "revision" && entry.path == "revision" {
|
if navType == "revision" && entry.path == "revision" {
|
||||||
//line templates/common.qtpl:29
|
|
||||||
qw422016.N().S(` <li><b>`)
|
|
||||||
//line templates/common.qtpl:30
|
//line templates/common.qtpl:30
|
||||||
|
qw422016.N().S(` <li><b>`)
|
||||||
|
//line templates/common.qtpl:31
|
||||||
qw422016.E().S(revisionHash[0])
|
qw422016.E().S(revisionHash[0])
|
||||||
//line templates/common.qtpl:30
|
//line templates/common.qtpl:31
|
||||||
qw422016.N().S(`</b></li>
|
qw422016.N().S(`</b></li>
|
||||||
`)
|
`)
|
||||||
//line templates/common.qtpl:31
|
//line templates/common.qtpl:32
|
||||||
} else if navType == entry.path {
|
} else if navType == entry.path {
|
||||||
//line templates/common.qtpl:31
|
//line templates/common.qtpl:32
|
||||||
qw422016.N().S(` <li><b>`)
|
qw422016.N().S(` <li><b>`)
|
||||||
//line templates/common.qtpl:32
|
//line templates/common.qtpl:33
|
||||||
qw422016.E().S(entry.title)
|
qw422016.E().S(entry.title)
|
||||||
//line templates/common.qtpl:32
|
//line templates/common.qtpl:33
|
||||||
qw422016.N().S(`</b></li>
|
qw422016.N().S(`</b></li>
|
||||||
`)
|
`)
|
||||||
//line templates/common.qtpl:33
|
//line templates/common.qtpl:34
|
||||||
} else if entry.path != "revision" && u.Group.CanAccessRoute(entry.path) {
|
} else if entry.path != "revision" && u.CanProceed(entry.path) {
|
||||||
//line templates/common.qtpl:33
|
//line templates/common.qtpl:34
|
||||||
qw422016.N().S(` <li><a href="/`)
|
qw422016.N().S(` <li><a href="/`)
|
||||||
//line templates/common.qtpl:34
|
//line templates/common.qtpl:35
|
||||||
qw422016.E().S(entry.path)
|
qw422016.E().S(entry.path)
|
||||||
//line templates/common.qtpl:34
|
//line templates/common.qtpl:35
|
||||||
qw422016.N().S(`/`)
|
qw422016.N().S(`/`)
|
||||||
//line templates/common.qtpl:34
|
//line templates/common.qtpl:35
|
||||||
qw422016.E().S(hyphaName)
|
qw422016.E().S(hyphaName)
|
||||||
//line templates/common.qtpl:34
|
//line templates/common.qtpl:35
|
||||||
qw422016.N().S(`">`)
|
qw422016.N().S(`">`)
|
||||||
//line templates/common.qtpl:34
|
//line templates/common.qtpl:35
|
||||||
qw422016.E().S(entry.title)
|
qw422016.E().S(entry.title)
|
||||||
//line templates/common.qtpl:34
|
//line templates/common.qtpl:35
|
||||||
qw422016.N().S(`</a></li>
|
qw422016.N().S(`</a></li>
|
||||||
`)
|
`)
|
||||||
//line templates/common.qtpl:35
|
|
||||||
}
|
|
||||||
//line templates/common.qtpl:36
|
//line templates/common.qtpl:36
|
||||||
}
|
}
|
||||||
//line templates/common.qtpl:36
|
//line templates/common.qtpl:37
|
||||||
|
}
|
||||||
|
//line templates/common.qtpl:37
|
||||||
qw422016.N().S(` `)
|
qw422016.N().S(` `)
|
||||||
//line templates/common.qtpl:37
|
//line templates/common.qtpl:38
|
||||||
qw422016.N().S(userMenuHTML(u))
|
qw422016.N().S(userMenuHTML(u))
|
||||||
//line templates/common.qtpl:37
|
//line templates/common.qtpl:38
|
||||||
qw422016.N().S(`
|
qw422016.N().S(`
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
`)
|
`)
|
||||||
//line templates/common.qtpl:40
|
//line templates/common.qtpl:41
|
||||||
}
|
}
|
||||||
|
|
||||||
//line templates/common.qtpl:40
|
//line templates/common.qtpl:41
|
||||||
func writenavHTML(qq422016 qtio422016.Writer, rq *http.Request, hyphaName, navType string, revisionHash ...string) {
|
func writenavHTML(qq422016 qtio422016.Writer, rq *http.Request, hyphaName, navType string, revisionHash ...string) {
|
||||||
//line templates/common.qtpl:40
|
//line templates/common.qtpl:41
|
||||||
qw422016 := qt422016.AcquireWriter(qq422016)
|
qw422016 := qt422016.AcquireWriter(qq422016)
|
||||||
//line templates/common.qtpl:40
|
//line templates/common.qtpl:41
|
||||||
streamnavHTML(qw422016, rq, hyphaName, navType, revisionHash...)
|
streamnavHTML(qw422016, rq, hyphaName, navType, revisionHash...)
|
||||||
//line templates/common.qtpl:40
|
//line templates/common.qtpl:41
|
||||||
qt422016.ReleaseWriter(qw422016)
|
qt422016.ReleaseWriter(qw422016)
|
||||||
//line templates/common.qtpl:40
|
//line templates/common.qtpl:41
|
||||||
}
|
}
|
||||||
|
|
||||||
//line templates/common.qtpl:40
|
//line templates/common.qtpl:41
|
||||||
func navHTML(rq *http.Request, hyphaName, navType string, revisionHash ...string) string {
|
func navHTML(rq *http.Request, hyphaName, navType string, revisionHash ...string) string {
|
||||||
//line templates/common.qtpl:40
|
//line templates/common.qtpl:41
|
||||||
qb422016 := qt422016.AcquireByteBuffer()
|
qb422016 := qt422016.AcquireByteBuffer()
|
||||||
//line templates/common.qtpl:40
|
//line templates/common.qtpl:41
|
||||||
writenavHTML(qb422016, rq, hyphaName, navType, revisionHash...)
|
writenavHTML(qb422016, rq, hyphaName, navType, revisionHash...)
|
||||||
//line templates/common.qtpl:40
|
//line templates/common.qtpl:41
|
||||||
qs422016 := string(qb422016.B)
|
qs422016 := string(qb422016.B)
|
||||||
//line templates/common.qtpl:40
|
//line templates/common.qtpl:41
|
||||||
qt422016.ReleaseByteBuffer(qb422016)
|
qt422016.ReleaseByteBuffer(qb422016)
|
||||||
//line templates/common.qtpl:40
|
//line templates/common.qtpl:41
|
||||||
return qs422016
|
return qs422016
|
||||||
//line templates/common.qtpl:40
|
//line templates/common.qtpl:41
|
||||||
}
|
}
|
||||||
|
|
||||||
//line templates/common.qtpl:42
|
//line templates/common.qtpl:43
|
||||||
func streamuserMenuHTML(qw422016 *qt422016.Writer, u *user.User) {
|
func streamuserMenuHTML(qw422016 *qt422016.Writer, u *user.User) {
|
||||||
//line templates/common.qtpl:42
|
//line templates/common.qtpl:43
|
||||||
qw422016.N().S(`
|
qw422016.N().S(`
|
||||||
`)
|
`)
|
||||||
//line templates/common.qtpl:43
|
//line templates/common.qtpl:44
|
||||||
if user.AuthUsed {
|
if user.AuthUsed {
|
||||||
//line templates/common.qtpl:43
|
//line templates/common.qtpl:44
|
||||||
qw422016.N().S(`
|
qw422016.N().S(`
|
||||||
<li class="navlinks__user">
|
<li class="navlinks__user">
|
||||||
`)
|
`)
|
||||||
//line templates/common.qtpl:45
|
//line templates/common.qtpl:46
|
||||||
if u.Group == user.UserAnon {
|
if u.Group == "anon" {
|
||||||
//line templates/common.qtpl:45
|
//line templates/common.qtpl:46
|
||||||
qw422016.N().S(`
|
qw422016.N().S(`
|
||||||
<a href="/login">Login</a>
|
<a href="/login">Login</a>
|
||||||
`)
|
`)
|
||||||
//line templates/common.qtpl:47
|
//line templates/common.qtpl:48
|
||||||
} else {
|
} else {
|
||||||
//line templates/common.qtpl:47
|
//line templates/common.qtpl:48
|
||||||
qw422016.N().S(`
|
qw422016.N().S(`
|
||||||
<a href="/page/`)
|
<a href="/page/`)
|
||||||
//line templates/common.qtpl:48
|
//line templates/common.qtpl:49
|
||||||
qw422016.E().S(util.UserTree)
|
qw422016.E().S(util.UserTree)
|
||||||
//line templates/common.qtpl:48
|
//line templates/common.qtpl:49
|
||||||
qw422016.N().S(`/`)
|
qw422016.N().S(`/`)
|
||||||
//line templates/common.qtpl:48
|
//line templates/common.qtpl:49
|
||||||
qw422016.E().S(u.Name)
|
qw422016.E().S(u.Name)
|
||||||
//line templates/common.qtpl:48
|
//line templates/common.qtpl:49
|
||||||
qw422016.N().S(`">`)
|
qw422016.N().S(`">`)
|
||||||
//line templates/common.qtpl:48
|
//line templates/common.qtpl:49
|
||||||
qw422016.E().S(u.Name)
|
qw422016.E().S(u.Name)
|
||||||
//line templates/common.qtpl:48
|
//line templates/common.qtpl:49
|
||||||
qw422016.N().S(`</a>
|
qw422016.N().S(`</a>
|
||||||
`)
|
`)
|
||||||
//line templates/common.qtpl:49
|
//line templates/common.qtpl:50
|
||||||
}
|
}
|
||||||
//line templates/common.qtpl:49
|
//line templates/common.qtpl:50
|
||||||
qw422016.N().S(`
|
qw422016.N().S(`
|
||||||
</li>
|
</li>
|
||||||
`)
|
`)
|
||||||
//line templates/common.qtpl:51
|
//line templates/common.qtpl:52
|
||||||
}
|
}
|
||||||
//line templates/common.qtpl:51
|
//line templates/common.qtpl:52
|
||||||
qw422016.N().S(`
|
qw422016.N().S(`
|
||||||
`)
|
`)
|
||||||
//line templates/common.qtpl:52
|
//line templates/common.qtpl:53
|
||||||
}
|
}
|
||||||
|
|
||||||
//line templates/common.qtpl:52
|
//line templates/common.qtpl:53
|
||||||
func writeuserMenuHTML(qq422016 qtio422016.Writer, u *user.User) {
|
func writeuserMenuHTML(qq422016 qtio422016.Writer, u *user.User) {
|
||||||
//line templates/common.qtpl:52
|
//line templates/common.qtpl:53
|
||||||
qw422016 := qt422016.AcquireWriter(qq422016)
|
qw422016 := qt422016.AcquireWriter(qq422016)
|
||||||
//line templates/common.qtpl:52
|
//line templates/common.qtpl:53
|
||||||
streamuserMenuHTML(qw422016, u)
|
streamuserMenuHTML(qw422016, u)
|
||||||
//line templates/common.qtpl:52
|
//line templates/common.qtpl:53
|
||||||
qt422016.ReleaseWriter(qw422016)
|
qt422016.ReleaseWriter(qw422016)
|
||||||
//line templates/common.qtpl:52
|
//line templates/common.qtpl:53
|
||||||
}
|
}
|
||||||
|
|
||||||
//line templates/common.qtpl:52
|
//line templates/common.qtpl:53
|
||||||
func userMenuHTML(u *user.User) string {
|
func userMenuHTML(u *user.User) string {
|
||||||
//line templates/common.qtpl:52
|
//line templates/common.qtpl:53
|
||||||
qb422016 := qt422016.AcquireByteBuffer()
|
qb422016 := qt422016.AcquireByteBuffer()
|
||||||
//line templates/common.qtpl:52
|
//line templates/common.qtpl:53
|
||||||
writeuserMenuHTML(qb422016, u)
|
writeuserMenuHTML(qb422016, u)
|
||||||
//line templates/common.qtpl:52
|
//line templates/common.qtpl:53
|
||||||
qs422016 := string(qb422016.B)
|
qs422016 := string(qb422016.B)
|
||||||
//line templates/common.qtpl:52
|
//line templates/common.qtpl:53
|
||||||
qt422016.ReleaseByteBuffer(qb422016)
|
qt422016.ReleaseByteBuffer(qb422016)
|
||||||
//line templates/common.qtpl:52
|
//line templates/common.qtpl:53
|
||||||
return qs422016
|
return qs422016
|
||||||
//line templates/common.qtpl:52
|
//line templates/common.qtpl:53
|
||||||
}
|
}
|
||||||
|
@ -48,7 +48,7 @@ If `contents` == "", a helpful message is shown instead.
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</section>
|
</section>
|
||||||
<hr class="page-separator"/>
|
<hr class="page-separator"/>
|
||||||
{% if u := user.FromRequest(rq).OrAnon(); !user.AuthUsed || u.Group > user.UserAnon %}
|
{% if u := user.FromRequest(rq); !user.AuthUsed || u.Group != "anon" %}
|
||||||
<form action="/upload-binary/{%s hyphaName %}"
|
<form action="/upload-binary/{%s hyphaName %}"
|
||||||
method="post" enctype="multipart/form-data">
|
method="post" enctype="multipart/form-data">
|
||||||
<label for="upload-binary__input">Upload a new attachment</label>
|
<label for="upload-binary__input">Upload a new attachment</label>
|
||||||
|
@ -228,7 +228,7 @@ func StreamPageHTML(qw422016 *qt422016.Writer, rq *http.Request, hyphaName, navi
|
|||||||
<hr class="page-separator"/>
|
<hr class="page-separator"/>
|
||||||
`)
|
`)
|
||||||
//line templates/http_readers.qtpl:51
|
//line templates/http_readers.qtpl:51
|
||||||
if u := user.FromRequest(rq).OrAnon(); !user.AuthUsed || u.Group > user.UserAnon {
|
if u := user.FromRequest(rq); !user.AuthUsed || u.Group != "anon" {
|
||||||
//line templates/http_readers.qtpl:51
|
//line templates/http_readers.qtpl:51
|
||||||
qw422016.N().S(`
|
qw422016.N().S(`
|
||||||
<form action="/upload-binary/`)
|
<form action="/upload-binary/`)
|
||||||
|
@ -10,53 +10,55 @@ import (
|
|||||||
"github.com/bouncepaw/mycorrhiza/util"
|
"github.com/bouncepaw/mycorrhiza/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
func PopulateFixedUserStorage() {
|
// ReadUsersFromFilesystem reads all user information from filesystem and stores it internally. Call it during initialization.
|
||||||
|
func ReadUsersFromFilesystem() {
|
||||||
|
rememberUsers(usersFromFixedCredentials())
|
||||||
|
readTokensToUsers()
|
||||||
|
}
|
||||||
|
|
||||||
|
func usersFromFixedCredentials() []*User {
|
||||||
|
var users []*User
|
||||||
contents, err := ioutil.ReadFile(util.FixedCredentialsPath)
|
contents, err := ioutil.ReadFile(util.FixedCredentialsPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
err = json.Unmarshal(contents, &UserStorage.Users)
|
err = json.Unmarshal(contents, &users)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
for _, user := range UserStorage.Users {
|
log.Println("Found", len(users), "fixed users")
|
||||||
user.Group = groupFromString(user.GroupString)
|
return users
|
||||||
}
|
}
|
||||||
log.Println("Found", len(UserStorage.Users), "fixed users")
|
|
||||||
|
|
||||||
contents, err = ioutil.ReadFile(tokenStoragePath())
|
func rememberUsers(uu []*User) {
|
||||||
|
// uu is used to not shadow the `users` in `users.go`.
|
||||||
|
for _, user := range uu {
|
||||||
|
users.Store(user.Name, user)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func readTokensToUsers() {
|
||||||
|
contents, err := ioutil.ReadFile(tokenStoragePath())
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var tmp map[string]string
|
var tmp map[string]string
|
||||||
err = json.Unmarshal(contents, &tmp)
|
err = json.Unmarshal(contents, &tmp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for token, username := range tmp {
|
for token, username := range tmp {
|
||||||
user := UserStorage.userByName(username)
|
commenceSession(username, token)
|
||||||
UserStorage.Tokens[token] = user
|
|
||||||
}
|
}
|
||||||
log.Println("Found", len(tmp), "active sessions")
|
log.Println("Found", len(tmp), "active sessions")
|
||||||
}
|
}
|
||||||
|
|
||||||
func dumpTokens() {
|
// Return path to tokens.json. Creates folders if needed.
|
||||||
tmp := make(map[string]string)
|
|
||||||
for token, user := range UserStorage.Tokens {
|
|
||||||
tmp[token] = user.Name
|
|
||||||
}
|
|
||||||
blob, err := json.Marshal(tmp)
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
} else {
|
|
||||||
ioutil.WriteFile(tokenStoragePath(), blob, 0644)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return path to tokens.json.
|
|
||||||
func tokenStoragePath() string {
|
func tokenStoragePath() string {
|
||||||
dir, err := xdg.DataFile("mycorrhiza/tokens.json")
|
dir, err := xdg.DataFile("mycorrhiza/tokens.json")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -67,3 +69,21 @@ func tokenStoragePath() string {
|
|||||||
}
|
}
|
||||||
return dir
|
return dir
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func dumpTokens() {
|
||||||
|
tmp := make(map[string]string)
|
||||||
|
|
||||||
|
tokens.Range(func(k, v interface{}) bool {
|
||||||
|
token := k.(string)
|
||||||
|
username := v.(string)
|
||||||
|
tmp[token] = username
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
blob, err := json.Marshal(tmp)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
} else {
|
||||||
|
ioutil.WriteFile(tokenStoragePath(), blob, 0644)
|
||||||
|
}
|
||||||
|
}
|
@ -1,70 +0,0 @@
|
|||||||
package user
|
|
||||||
|
|
||||||
import (
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
func groupFromString(s string) UserGroup {
|
|
||||||
switch s {
|
|
||||||
case "admin":
|
|
||||||
return UserAdmin
|
|
||||||
case "moderator":
|
|
||||||
return UserModerator
|
|
||||||
case "trusted":
|
|
||||||
return UserTrusted
|
|
||||||
case "editor":
|
|
||||||
return UserEditor
|
|
||||||
default:
|
|
||||||
log.Fatal("Unknown user group", s)
|
|
||||||
return UserAnon
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// UserGroup represents a group that a user is part of.
|
|
||||||
type UserGroup int
|
|
||||||
|
|
||||||
const (
|
|
||||||
// UserAnon is the default user group which all unauthorized visitors have.
|
|
||||||
UserAnon UserGroup = iota
|
|
||||||
// UserEditor is a user who can edit and upload stuff.
|
|
||||||
UserEditor
|
|
||||||
// UserTrusted is a trusted editor who can also rename stuff.
|
|
||||||
UserTrusted
|
|
||||||
// UserModerator is a moderator who can also delete stuff.
|
|
||||||
UserModerator
|
|
||||||
// UserAdmin can do everything.
|
|
||||||
UserAdmin
|
|
||||||
)
|
|
||||||
|
|
||||||
var minimalRights = map[string]UserGroup{
|
|
||||||
"edit": UserEditor,
|
|
||||||
"upload-binary": UserEditor,
|
|
||||||
"upload-text": UserEditor,
|
|
||||||
"rename-ask": UserTrusted,
|
|
||||||
"rename-confirm": UserTrusted,
|
|
||||||
"delete-ask": UserModerator,
|
|
||||||
"delete-confirm": UserModerator,
|
|
||||||
"reindex": UserAdmin,
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ug UserGroup) CanAccessRoute(route string) bool {
|
|
||||||
if !AuthUsed {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if minimalRight, ok := minimalRights[route]; ok {
|
|
||||||
if ug >= minimalRight {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func CanProceed(rq *http.Request, route string) bool {
|
|
||||||
return FromRequest(rq).OrAnon().CanProceed(route)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *User) CanProceed(route string) bool {
|
|
||||||
return u.Group.CanAccessRoute(route)
|
|
||||||
}
|
|
75
user/net.go
Normal file
75
user/net.go
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
package user
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/bouncepaw/mycorrhiza/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CanProceed returns `true` if the user in `rq` has enough rights to access `route`.
|
||||||
|
func CanProceed(rq *http.Request, route string) bool {
|
||||||
|
return FromRequest(rq).CanProceed(route)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromRequest returns user from `rq`. If there is no user, an anon user is returned instead.
|
||||||
|
func FromRequest(rq *http.Request) *User {
|
||||||
|
cookie, err := rq.Cookie("mycorrhiza_token")
|
||||||
|
if err != nil {
|
||||||
|
return emptyUser()
|
||||||
|
}
|
||||||
|
return userByToken(cookie.Value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LogoutFromRequest logs the user in `rq` out and rewrites the cookie in `w`.
|
||||||
|
func LogoutFromRequest(w http.ResponseWriter, rq *http.Request) {
|
||||||
|
cookieFromUser, err := rq.Cookie("mycorrhiza_token")
|
||||||
|
if err == nil {
|
||||||
|
http.SetCookie(w, cookie("token", "", time.Unix(0, 0)))
|
||||||
|
terminateSession(cookieFromUser.Value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoginDataHTTP logs such user in and returns string representation of an error if there is any.
|
||||||
|
func LoginDataHTTP(w http.ResponseWriter, rq *http.Request, username, password string) string {
|
||||||
|
w.Header().Set("Content-Type", "text/html;charset=utf-8")
|
||||||
|
if !HasUsername(username) {
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
log.Println("Unknown username", username, "was entered")
|
||||||
|
return "unknown username"
|
||||||
|
}
|
||||||
|
if !CredentialsOK(username, password) {
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
log.Println("A wrong password was entered for username", username)
|
||||||
|
return "wrong password"
|
||||||
|
}
|
||||||
|
token, err := AddSession(username)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return err.Error()
|
||||||
|
}
|
||||||
|
http.SetCookie(w, cookie("token", token, time.Now().Add(365*24*time.Hour)))
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddSession saves a session for `username` and returns a token to use.
|
||||||
|
func AddSession(username string) (string, error) {
|
||||||
|
token, err := util.RandomString(16)
|
||||||
|
if err == nil {
|
||||||
|
commenceSession(username, token)
|
||||||
|
log.Println("New token for", username, "is", token)
|
||||||
|
}
|
||||||
|
return token, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// A handy cookie constructor
|
||||||
|
func cookie(name_suffix, val string, t time.Time) *http.Cookie {
|
||||||
|
return &http.Cookie{
|
||||||
|
Name: "mycorrhiza_" + name_suffix,
|
||||||
|
Value: val,
|
||||||
|
Expires: t,
|
||||||
|
Path: "/",
|
||||||
|
}
|
||||||
|
}
|
176
user/user.go
176
user/user.go
@ -1,138 +1,66 @@
|
|||||||
package user
|
package user
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
"sync"
|
||||||
"net/http"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/bouncepaw/mycorrhiza/util"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (u *User) OrAnon() *User {
|
|
||||||
if u == nil {
|
|
||||||
return &User{}
|
|
||||||
}
|
|
||||||
return u
|
|
||||||
}
|
|
||||||
|
|
||||||
func LogoutFromRequest(w http.ResponseWriter, rq *http.Request) {
|
|
||||||
cookieFromUser, err := rq.Cookie("mycorrhiza_token")
|
|
||||||
if err == nil {
|
|
||||||
http.SetCookie(w, cookie("token", "", time.Unix(0, 0)))
|
|
||||||
terminateSession(cookieFromUser.Value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (us *FixedUserStorage) userByToken(token string) *User {
|
|
||||||
if user, ok := us.Tokens[token]; ok {
|
|
||||||
return user
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (us *FixedUserStorage) userByName(username string) *User {
|
|
||||||
for _, user := range us.Users {
|
|
||||||
if user.Name == username {
|
|
||||||
return user
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func FromRequest(rq *http.Request) *User {
|
|
||||||
cookie, err := rq.Cookie("mycorrhiza_token")
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return UserStorage.userByToken(cookie.Value).OrAnon()
|
|
||||||
}
|
|
||||||
|
|
||||||
func LoginDataHTTP(w http.ResponseWriter, rq *http.Request, username, password string) string {
|
|
||||||
w.Header().Set("Content-Type", "text/html;charset=utf-8")
|
|
||||||
if !HasUsername(username) {
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
log.Println("Unknown username", username, "was entered")
|
|
||||||
return "unknown username"
|
|
||||||
}
|
|
||||||
if !CredentialsOK(username, password) {
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
log.Println("A wrong password was entered for username", username)
|
|
||||||
return "wrong password"
|
|
||||||
}
|
|
||||||
token, err := AddSession(username)
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
return err.Error()
|
|
||||||
}
|
|
||||||
http.SetCookie(w, cookie("token", token, time.Now().Add(14*24*time.Hour)))
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddSession saves a session for `username` and returns a token to use.
|
|
||||||
func AddSession(username string) (string, error) {
|
|
||||||
token, err := util.RandomString(16)
|
|
||||||
if err == nil {
|
|
||||||
for _, user := range UserStorage.Users {
|
|
||||||
if user.Name == username {
|
|
||||||
UserStorage.Tokens[token] = user
|
|
||||||
go dumpTokens()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
log.Println("New token for", username, "is", token)
|
|
||||||
}
|
|
||||||
return token, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func terminateSession(token string) {
|
|
||||||
delete(UserStorage.Tokens, token)
|
|
||||||
go dumpTokens()
|
|
||||||
}
|
|
||||||
|
|
||||||
func HasUsername(username string) bool {
|
|
||||||
for _, user := range UserStorage.Users {
|
|
||||||
if user.Name == username {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func CredentialsOK(username, password string) bool {
|
|
||||||
for _, user := range UserStorage.Users {
|
|
||||||
if user.Name == username && user.Password == password {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
type FixedUserStorage struct {
|
|
||||||
Users []*User
|
|
||||||
Tokens map[string]*User
|
|
||||||
}
|
|
||||||
|
|
||||||
var UserStorage = FixedUserStorage{Tokens: make(map[string]*User)}
|
|
||||||
|
|
||||||
// AuthUsed shows if a method of authentication is used. You should set it by yourself.
|
|
||||||
var AuthUsed bool
|
|
||||||
|
|
||||||
// User is a user.
|
// User is a user.
|
||||||
type User struct {
|
type User struct {
|
||||||
// Name is a username. It must follow hypha naming rules.
|
// Name is a username. It must follow hypha naming rules.
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
// Group the user is part of.
|
Group string `json:"group"`
|
||||||
Group UserGroup `json:"-"`
|
|
||||||
GroupString string `json:"group"`
|
|
||||||
Password string `json:"password"`
|
Password string `json:"password"`
|
||||||
|
sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
// A handy cookie constructor
|
// Route — Right (more is more right)
|
||||||
func cookie(name_suffix, val string, t time.Time) *http.Cookie {
|
var minimalRights = map[string]int{
|
||||||
return &http.Cookie{
|
"edit": 1,
|
||||||
Name: "mycorrhiza_" + name_suffix,
|
"upload-binary": 1,
|
||||||
Value: val,
|
"upload-text": 1,
|
||||||
Expires: t,
|
"rename-ask": 2,
|
||||||
Path: "/",
|
"rename-confirm": 2,
|
||||||
|
"delete-ask": 3,
|
||||||
|
"delete-confirm": 3,
|
||||||
|
"reindex": 4,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Group — Right
|
||||||
|
var groupRight = map[string]int{
|
||||||
|
"anon": 0,
|
||||||
|
"editor": 1,
|
||||||
|
"trusted": 2,
|
||||||
|
"moderator": 3,
|
||||||
|
"admin": 4,
|
||||||
|
}
|
||||||
|
|
||||||
|
func emptyUser() *User {
|
||||||
|
return &User{
|
||||||
|
Name: "anon",
|
||||||
|
Group: "anon",
|
||||||
|
Password: "",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (user *User) CanProceed(route string) bool {
|
||||||
|
if !AuthUsed {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
user.RLock()
|
||||||
|
defer user.RUnlock()
|
||||||
|
|
||||||
|
right, _ := groupRight[user.Group]
|
||||||
|
minimalRight, _ := minimalRights[route]
|
||||||
|
if right >= minimalRight {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (user *User) isCorrectPassword(password string) bool {
|
||||||
|
user.RLock()
|
||||||
|
defer user.RUnlock()
|
||||||
|
|
||||||
|
return password == user.Password
|
||||||
|
}
|
||||||
|
44
user/users.go
Normal file
44
user/users.go
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
package user
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
var AuthUsed bool
|
||||||
|
var users sync.Map
|
||||||
|
var tokens sync.Map
|
||||||
|
|
||||||
|
func HasUsername(username string) bool {
|
||||||
|
_, has := users.Load(username)
|
||||||
|
return has
|
||||||
|
}
|
||||||
|
|
||||||
|
func CredentialsOK(username, password string) bool {
|
||||||
|
return userByName(username).isCorrectPassword(password)
|
||||||
|
}
|
||||||
|
|
||||||
|
func userByToken(token string) *User {
|
||||||
|
if usernameUntyped, ok := tokens.Load(token); ok {
|
||||||
|
username := usernameUntyped.(string)
|
||||||
|
return userByName(username)
|
||||||
|
}
|
||||||
|
return emptyUser()
|
||||||
|
}
|
||||||
|
|
||||||
|
func userByName(username string) *User {
|
||||||
|
if userUntyped, ok := users.Load(username); ok {
|
||||||
|
user := userUntyped.(*User)
|
||||||
|
return user
|
||||||
|
}
|
||||||
|
return emptyUser()
|
||||||
|
}
|
||||||
|
|
||||||
|
func commenceSession(username, token string) {
|
||||||
|
tokens.Store(token, username)
|
||||||
|
go dumpTokens()
|
||||||
|
}
|
||||||
|
|
||||||
|
func terminateSession(token string) {
|
||||||
|
tokens.Delete(token)
|
||||||
|
go dumpTokens()
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user