mirror of
				https://github.com/osmarks/mycorrhiza.git
				synced 2025-10-26 05:07:40 +00:00 
			
		
		
		
	Make account system concurrent-safe and refactor it a little
This commit is contained in:
		
							
								
								
									
										2
									
								
								flag.go
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								flag.go
									
									
									
									
									
								
							| @@ -51,7 +51,7 @@ func parseCliArgs() { | ||||
| 	case "none": | ||||
| 	case "fixed": | ||||
| 		user.AuthUsed = true | ||||
| 		user.PopulateFixedUserStorage() | ||||
| 		user.ReadUsersFromFilesystem() | ||||
| 	default: | ||||
| 		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/gorilla/feeds v1.1.1 | ||||
| 	github.com/hashicorp/go-memdb v1.3.0 | ||||
| 	github.com/kr/pretty v0.2.1 // indirect | ||||
| 	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/go.mod h1:7I2hH/IT30IsupOpKZ5ue7/qNi3CoKzD6tL3HwpaRMQ= | ||||
| 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/gorilla/feeds v1.1.1 h1:HwKXxqzcRNg9to+BbvJog4+f3s/xzvtZXICcQGutYfY= | ||||
| 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-memdb v1.3.0 h1:xdXq34gBOMEloa9rlGStLxmfX/dyIK8htOv36dQUwHU= | ||||
| 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/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/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= | ||||
| 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/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/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/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= | ||||
| 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-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
| 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/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= | ||||
|   | ||||
| @@ -121,8 +121,7 @@ func (hop *HistoryOp) WithMsg(userMsg string) *HistoryOp { | ||||
|  | ||||
| // WithUser sets a user for the commit. | ||||
| func (hop *HistoryOp) WithUser(u *user.User) *HistoryOp { | ||||
| 	u = u.OrAnon() | ||||
| 	if u.Group != user.UserAnon { | ||||
| 	if u.Group != "anon" { | ||||
| 		hop.name = u.Name | ||||
| 		hop.email = u.Name + "@mycorrhiza" | ||||
| 	} | ||||
|   | ||||
| @@ -44,7 +44,7 @@ func handlerRenameConfirm(w http.ResponseWriter, rq *http.Request) { | ||||
| 		newName          = CanonicalName(rq.PostFormValue("new-name")) | ||||
| 		_, newNameIsUsed = HyphaStorage[newName] | ||||
| 		recursive        = rq.PostFormValue("recursive") == "true" | ||||
| 		u                = user.FromRequest(rq).OrAnon() | ||||
| 		u                = user.FromRequest(rq) | ||||
| 	) | ||||
| 	switch { | ||||
| 	case !u.CanProceed("rename-confirm"): | ||||
| @@ -151,7 +151,7 @@ func handlerUploadText(w http.ResponseWriter, rq *http.Request) { | ||||
| 	var ( | ||||
| 		hyphaName = HyphaNameFromRq(rq, "upload-text") | ||||
| 		textData  = rq.PostFormValue("text") | ||||
| 		u         = user.FromRequest(rq).OrAnon() | ||||
| 		u         = user.FromRequest(rq) | ||||
| 	) | ||||
| 	if ok := user.CanProceed(rq, "upload-text"); !ok { | ||||
| 		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) | ||||
| 	var ( | ||||
| 		hyphaName = HyphaNameFromRq(rq, "upload-binary") | ||||
| 		u         = user.FromRequest(rq).OrAnon() | ||||
| 		u         = user.FromRequest(rq) | ||||
| 	) | ||||
| 	if !u.CanProceed("upload-binary") { | ||||
| 		HttpErr(w, http.StatusForbidden, hyphaName, "Not enough rights", "You must be an editor to upload attachments.") | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| package hyphae | ||||
|  | ||||
| import ( | ||||
| 	"github.com/hashicorp/go-memdb" | ||||
| 	"github.com/bouncepaw/mycorrhiza/storage" | ||||
| ) | ||||
|  | ||||
| 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. | ||||
| func AddHypha(name, textPath, binaryPath string) { | ||||
| 	txn := db.Txn(true) | ||||
| 	txn := storage.DB.Txn(true) | ||||
| 	txn.Insert("hyphae", | ||||
| 		&Hypha{ | ||||
| 			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. | ||||
| 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 { | ||||
| 			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 { | ||||
| @@ -147,7 +150,7 @@ func ParagraphToHtml(hyphaName, input string) string { | ||||
| 			p.Next(2) | ||||
| 		case startsWith("[["): | ||||
| 			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)) | ||||
| 		default: | ||||
| 			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 %} | ||||
| 		<h1>Login</h1> | ||||
| 		<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> | ||||
| 				<legend>Username</legend> | ||||
| 				<input type="text" required autofocus name="username" autocomplete="on"> | ||||
| @@ -15,7 +15,7 @@ | ||||
| 				<legend>Password</legend> | ||||
| 				<input type="password" required name="password" autocomplete="on"> | ||||
| 			</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"> | ||||
| 			<a href="/">Cancel</a> | ||||
| 		</form> | ||||
|   | ||||
| @@ -33,7 +33,7 @@ func StreamLoginHTML(qw422016 *qt422016.Writer) { | ||||
| 		qw422016.N().S(` | ||||
| 		<h1>Login</h1> | ||||
| 		<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> | ||||
| 				<legend>Username</legend> | ||||
| 				<input type="text" required autofocus name="username" autocomplete="on"> | ||||
| @@ -42,7 +42,7 @@ func StreamLoginHTML(qw422016 *qt422016.Writer) { | ||||
| 				<legend>Password</legend> | ||||
| 				<input type="password" required name="password" autocomplete="on"> | ||||
| 			</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"> | ||||
| 			<a href="/">Cancel</a> | ||||
| 		</form> | ||||
|   | ||||
| @@ -20,9 +20,10 @@ var navEntries = []navEntry{ | ||||
| %} | ||||
|  | ||||
| {% func navHTML(rq *http.Request, hyphaName, navType string, revisionHash ...string) %} | ||||
| {% code | ||||
| 	u := user.FromRequest(rq).OrAnon() | ||||
| {% code  | ||||
| 	u := user.FromRequest(rq) | ||||
| %} | ||||
|  | ||||
| 	<nav class="navlinks"> | ||||
| 		<ul> | ||||
| 		{%- for _, entry := range navEntries -%} | ||||
| @@ -30,7 +31,7 @@ var navEntries = []navEntry{ | ||||
| 			<li><b>{%s revisionHash[0] %}</b></li> | ||||
| 		{%- elseif navType == entry.path -%} | ||||
| 			<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> | ||||
| 		{%- endif -%} | ||||
| 		{%- endfor -%} | ||||
| @@ -42,7 +43,7 @@ var navEntries = []navEntry{ | ||||
| {% func userMenuHTML(u *user.User) %} | ||||
| 	{% if user.AuthUsed %} | ||||
| 			<li class="navlinks__user"> | ||||
| 			{% if u.Group == user.UserAnon %} | ||||
| 			{% if u.Group == "anon" %} | ||||
| 				<a href="/login">Login</a> | ||||
| 			{% else %} | ||||
| 				<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(` | ||||
| `) | ||||
| //line templates/common.qtpl:24 | ||||
| 	u := user.FromRequest(rq).OrAnon() | ||||
| 	u := user.FromRequest(rq) | ||||
|  | ||||
| //line templates/common.qtpl:25 | ||||
| 	qw422016.N().S(` | ||||
|  | ||||
| 	<nav class="navlinks"> | ||||
| 		<ul> | ||||
| `) | ||||
| //line templates/common.qtpl:28 | ||||
| //line templates/common.qtpl:29 | ||||
| 	for _, entry := range navEntries { | ||||
| //line templates/common.qtpl:29 | ||||
| //line templates/common.qtpl:30 | ||||
| 		if navType == "revision" && entry.path == "revision" { | ||||
| //line templates/common.qtpl:29 | ||||
| 			qw422016.N().S(`			<li><b>`) | ||||
| //line templates/common.qtpl:30 | ||||
| 			qw422016.N().S(`			<li><b>`) | ||||
| //line templates/common.qtpl:31 | ||||
| 			qw422016.E().S(revisionHash[0]) | ||||
| //line templates/common.qtpl:30 | ||||
| //line templates/common.qtpl:31 | ||||
| 			qw422016.N().S(`</b></li> | ||||
| `) | ||||
| //line templates/common.qtpl:31 | ||||
| //line templates/common.qtpl:32 | ||||
| 		} else if navType == entry.path { | ||||
| //line templates/common.qtpl:31 | ||||
| //line templates/common.qtpl:32 | ||||
| 			qw422016.N().S(`			<li><b>`) | ||||
| //line templates/common.qtpl:32 | ||||
| //line templates/common.qtpl:33 | ||||
| 			qw422016.E().S(entry.title) | ||||
| //line templates/common.qtpl:32 | ||||
| //line templates/common.qtpl:33 | ||||
| 			qw422016.N().S(`</b></li> | ||||
| `) | ||||
| //line templates/common.qtpl:33 | ||||
| 		} else if entry.path != "revision" && u.Group.CanAccessRoute(entry.path) { | ||||
| //line templates/common.qtpl:33 | ||||
| //line templates/common.qtpl:34 | ||||
| 		} else if entry.path != "revision" && u.CanProceed(entry.path) { | ||||
| //line templates/common.qtpl:34 | ||||
| 			qw422016.N().S(`			<li><a href="/`) | ||||
| //line templates/common.qtpl:34 | ||||
| //line templates/common.qtpl:35 | ||||
| 			qw422016.E().S(entry.path) | ||||
| //line templates/common.qtpl:34 | ||||
| //line templates/common.qtpl:35 | ||||
| 			qw422016.N().S(`/`) | ||||
| //line templates/common.qtpl:34 | ||||
| //line templates/common.qtpl:35 | ||||
| 			qw422016.E().S(hyphaName) | ||||
| //line templates/common.qtpl:34 | ||||
| //line templates/common.qtpl:35 | ||||
| 			qw422016.N().S(`">`) | ||||
| //line templates/common.qtpl:34 | ||||
| //line templates/common.qtpl:35 | ||||
| 			qw422016.E().S(entry.title) | ||||
| //line templates/common.qtpl:34 | ||||
| //line templates/common.qtpl:35 | ||||
| 			qw422016.N().S(`</a></li> | ||||
| `) | ||||
| //line templates/common.qtpl:35 | ||||
| //line templates/common.qtpl:36 | ||||
| 		} | ||||
| //line templates/common.qtpl:36 | ||||
| //line templates/common.qtpl:37 | ||||
| 	} | ||||
| //line templates/common.qtpl:36 | ||||
| //line templates/common.qtpl:37 | ||||
| 	qw422016.N().S(`		`) | ||||
| //line templates/common.qtpl:37 | ||||
| //line templates/common.qtpl:38 | ||||
| 	qw422016.N().S(userMenuHTML(u)) | ||||
| //line templates/common.qtpl:37 | ||||
| //line templates/common.qtpl:38 | ||||
| 	qw422016.N().S(` | ||||
| 		</ul> | ||||
| 	</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) { | ||||
| //line templates/common.qtpl:40 | ||||
| //line templates/common.qtpl:41 | ||||
| 	qw422016 := qt422016.AcquireWriter(qq422016) | ||||
| //line templates/common.qtpl:40 | ||||
| //line templates/common.qtpl:41 | ||||
| 	streamnavHTML(qw422016, rq, hyphaName, navType, revisionHash...) | ||||
| //line templates/common.qtpl:40 | ||||
| //line templates/common.qtpl:41 | ||||
| 	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 { | ||||
| //line templates/common.qtpl:40 | ||||
| //line templates/common.qtpl:41 | ||||
| 	qb422016 := qt422016.AcquireByteBuffer() | ||||
| //line templates/common.qtpl:40 | ||||
| //line templates/common.qtpl:41 | ||||
| 	writenavHTML(qb422016, rq, hyphaName, navType, revisionHash...) | ||||
| //line templates/common.qtpl:40 | ||||
| //line templates/common.qtpl:41 | ||||
| 	qs422016 := string(qb422016.B) | ||||
| //line templates/common.qtpl:40 | ||||
| //line templates/common.qtpl:41 | ||||
| 	qt422016.ReleaseByteBuffer(qb422016) | ||||
| //line templates/common.qtpl:40 | ||||
| //line templates/common.qtpl:41 | ||||
| 	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) { | ||||
| //line templates/common.qtpl:42 | ||||
| //line templates/common.qtpl:43 | ||||
| 	qw422016.N().S(` | ||||
| 	`) | ||||
| //line templates/common.qtpl:43 | ||||
| //line templates/common.qtpl:44 | ||||
| 	if user.AuthUsed { | ||||
| //line templates/common.qtpl:43 | ||||
| //line templates/common.qtpl:44 | ||||
| 		qw422016.N().S(` | ||||
| 			<li class="navlinks__user"> | ||||
| 			`) | ||||
| //line templates/common.qtpl:45 | ||||
| 		if u.Group == user.UserAnon { | ||||
| //line templates/common.qtpl:45 | ||||
| //line templates/common.qtpl:46 | ||||
| 		if u.Group == "anon" { | ||||
| //line templates/common.qtpl:46 | ||||
| 			qw422016.N().S(` | ||||
| 				<a href="/login">Login</a> | ||||
| 			`) | ||||
| //line templates/common.qtpl:47 | ||||
| //line templates/common.qtpl:48 | ||||
| 		} else { | ||||
| //line templates/common.qtpl:47 | ||||
| //line templates/common.qtpl:48 | ||||
| 			qw422016.N().S(` | ||||
| 				<a href="/page/`) | ||||
| //line templates/common.qtpl:48 | ||||
| //line templates/common.qtpl:49 | ||||
| 			qw422016.E().S(util.UserTree) | ||||
| //line templates/common.qtpl:48 | ||||
| //line templates/common.qtpl:49 | ||||
| 			qw422016.N().S(`/`) | ||||
| //line templates/common.qtpl:48 | ||||
| //line templates/common.qtpl:49 | ||||
| 			qw422016.E().S(u.Name) | ||||
| //line templates/common.qtpl:48 | ||||
| //line templates/common.qtpl:49 | ||||
| 			qw422016.N().S(`">`) | ||||
| //line templates/common.qtpl:48 | ||||
| //line templates/common.qtpl:49 | ||||
| 			qw422016.E().S(u.Name) | ||||
| //line templates/common.qtpl:48 | ||||
| //line templates/common.qtpl:49 | ||||
| 			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(` | ||||
| 			</li> | ||||
| 	`) | ||||
| //line templates/common.qtpl:51 | ||||
| //line templates/common.qtpl:52 | ||||
| 	} | ||||
| //line templates/common.qtpl:51 | ||||
| //line templates/common.qtpl:52 | ||||
| 	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) { | ||||
| //line templates/common.qtpl:52 | ||||
| //line templates/common.qtpl:53 | ||||
| 	qw422016 := qt422016.AcquireWriter(qq422016) | ||||
| //line templates/common.qtpl:52 | ||||
| //line templates/common.qtpl:53 | ||||
| 	streamuserMenuHTML(qw422016, u) | ||||
| //line templates/common.qtpl:52 | ||||
| //line templates/common.qtpl:53 | ||||
| 	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 { | ||||
| //line templates/common.qtpl:52 | ||||
| //line templates/common.qtpl:53 | ||||
| 	qb422016 := qt422016.AcquireByteBuffer() | ||||
| //line templates/common.qtpl:52 | ||||
| //line templates/common.qtpl:53 | ||||
| 	writeuserMenuHTML(qb422016, u) | ||||
| //line templates/common.qtpl:52 | ||||
| //line templates/common.qtpl:53 | ||||
| 	qs422016 := string(qb422016.B) | ||||
| //line templates/common.qtpl:52 | ||||
| //line templates/common.qtpl:53 | ||||
| 	qt422016.ReleaseByteBuffer(qb422016) | ||||
| //line templates/common.qtpl:52 | ||||
| //line templates/common.qtpl:53 | ||||
| 	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 %} | ||||
| 	</section> | ||||
| 	<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 %}" | ||||
| 			method="post" enctype="multipart/form-data"> | ||||
| 		<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"/> | ||||
| `) | ||||
| //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 | ||||
| 		qw422016.N().S(` | ||||
| 	<form action="/upload-binary/`) | ||||
|   | ||||
| @@ -10,53 +10,55 @@ import ( | ||||
| 	"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) | ||||
| 	if err != nil { | ||||
| 		log.Fatal(err) | ||||
| 	} | ||||
| 	err = json.Unmarshal(contents, &UserStorage.Users) | ||||
| 	err = json.Unmarshal(contents, &users) | ||||
| 	if err != nil { | ||||
| 		log.Fatal(err) | ||||
| 	} | ||||
| 	for _, user := range UserStorage.Users { | ||||
| 		user.Group = groupFromString(user.GroupString) | ||||
| 	} | ||||
| 	log.Println("Found", len(UserStorage.Users), "fixed users") | ||||
| 	log.Println("Found", len(users), "fixed users") | ||||
| 	return 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) { | ||||
| 		return | ||||
| 	} | ||||
| 	if err != nil { | ||||
| 		log.Fatal(err) | ||||
| 	} | ||||
| 
 | ||||
| 	var tmp map[string]string | ||||
| 	err = json.Unmarshal(contents, &tmp) | ||||
| 	if err != nil { | ||||
| 		log.Fatal(err) | ||||
| 	} | ||||
| 
 | ||||
| 	for token, username := range tmp { | ||||
| 		user := UserStorage.userByName(username) | ||||
| 		UserStorage.Tokens[token] = user | ||||
| 		commenceSession(username, token) | ||||
| 	} | ||||
| 	log.Println("Found", len(tmp), "active sessions") | ||||
| } | ||||
| 
 | ||||
| func dumpTokens() { | ||||
| 	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. | ||||
| // Return path to tokens.json. Creates folders if needed. | ||||
| func tokenStoragePath() string { | ||||
| 	dir, err := xdg.DataFile("mycorrhiza/tokens.json") | ||||
| 	if err != nil { | ||||
| @@ -67,3 +69,21 @@ func tokenStoragePath() string { | ||||
| 	} | ||||
| 	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:    "/", | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										180
									
								
								user/user.go
									
									
									
									
									
								
							
							
						
						
									
										180
									
								
								user/user.go
									
									
									
									
									
								
							| @@ -1,138 +1,66 @@ | ||||
| package user | ||||
|  | ||||
| import ( | ||||
| 	"log" | ||||
| 	"net/http" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/bouncepaw/mycorrhiza/util" | ||||
| 	"sync" | ||||
| ) | ||||
|  | ||||
| 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. | ||||
| type User struct { | ||||
| 	// Name is a username. It must follow hypha naming rules. | ||||
| 	Name string `json:"name"` | ||||
| 	// Group the user is part of. | ||||
| 	Group       UserGroup `json:"-"` | ||||
| 	GroupString string    `json:"group"` | ||||
| 	Password    string    `json:"password"` | ||||
| 	Name     string `json:"name"` | ||||
| 	Group    string `json:"group"` | ||||
| 	Password string `json:"password"` | ||||
| 	sync.RWMutex | ||||
| } | ||||
|  | ||||
| // 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:    "/", | ||||
| // Route — Right (more is more right) | ||||
| var minimalRights = map[string]int{ | ||||
| 	"edit":           1, | ||||
| 	"upload-binary":  1, | ||||
| 	"upload-text":    1, | ||||
| 	"rename-ask":     2, | ||||
| 	"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() | ||||
| } | ||||
		Reference in New Issue
	
	Block a user
	 bouncepaw
					bouncepaw