mirror of
https://github.com/osmarks/mycorrhiza.git
synced 2024-12-12 05:20:26 +00:00
Implement login form
This commit is contained in:
parent
c83ea6f356
commit
a0d1099b75
3
Makefile
3
Makefile
@ -1,6 +1,9 @@
|
|||||||
run: build
|
run: build
|
||||||
./mycorrhiza metarrhiza
|
./mycorrhiza metarrhiza
|
||||||
|
|
||||||
|
run_with_fixed_auth: build
|
||||||
|
./mycorrhiza -auth-method fixed metarrhiza
|
||||||
|
|
||||||
build:
|
build:
|
||||||
go generate
|
go generate
|
||||||
go build .
|
go build .
|
||||||
|
@ -3,7 +3,9 @@ A wiki engine.
|
|||||||
|
|
||||||
Features planned for this release:
|
Features planned for this release:
|
||||||
* [ ] Authorization
|
* [ ] Authorization
|
||||||
* [ ] User groups: `anon`, `editor`, `trusted`, `moderator`, `admin`
|
* [x] User groups: `anon`, `editor`, `trusted`, `moderator`, `admin`
|
||||||
|
* [ ] Login page
|
||||||
|
* [ ] Rights
|
||||||
* [ ] Mycomarkup improvements
|
* [ ] Mycomarkup improvements
|
||||||
* [x] Strike-through syntax
|
* [x] Strike-through syntax
|
||||||
* [x] Formatting in headings
|
* [x] Formatting in headings
|
||||||
|
28
main.go
28
main.go
@ -15,6 +15,7 @@ import (
|
|||||||
|
|
||||||
"github.com/bouncepaw/mycorrhiza/history"
|
"github.com/bouncepaw/mycorrhiza/history"
|
||||||
"github.com/bouncepaw/mycorrhiza/templates"
|
"github.com/bouncepaw/mycorrhiza/templates"
|
||||||
|
"github.com/bouncepaw/mycorrhiza/user"
|
||||||
"github.com/bouncepaw/mycorrhiza/util"
|
"github.com/bouncepaw/mycorrhiza/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -109,6 +110,31 @@ func handlerStyle(w http.ResponseWriter, rq *http.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func handlerLoginData(w http.ResponseWriter, rq *http.Request) {
|
||||||
|
log.Println(rq.URL)
|
||||||
|
var (
|
||||||
|
username = CanonicalName(rq.PostFormValue("username"))
|
||||||
|
password = rq.PostFormValue("password")
|
||||||
|
err = user.LoginDataHTTP(w, rq, username, password)
|
||||||
|
)
|
||||||
|
if err != "" {
|
||||||
|
w.Write([]byte(base(err, templates.LoginErrorHTML(err))))
|
||||||
|
} else {
|
||||||
|
http.Redirect(w, rq, "/", http.StatusSeeOther)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func handlerLogin(w http.ResponseWriter, rq *http.Request) {
|
||||||
|
log.Println(rq.URL)
|
||||||
|
w.Header().Set("Content-Type", "text/html;charset=utf-8")
|
||||||
|
if user.AuthUsed {
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
} else {
|
||||||
|
w.WriteHeader(http.StatusForbidden)
|
||||||
|
}
|
||||||
|
w.Write([]byte(base("Login", templates.LoginHTML())))
|
||||||
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
log.Println("Running MycorrhizaWiki β")
|
log.Println("Running MycorrhizaWiki β")
|
||||||
parseCliArgs()
|
parseCliArgs()
|
||||||
@ -133,6 +159,8 @@ func main() {
|
|||||||
http.ServeFile(w, rq, WikiDir+"/static/favicon.ico")
|
http.ServeFile(w, rq, WikiDir+"/static/favicon.ico")
|
||||||
})
|
})
|
||||||
http.HandleFunc("/static/common.css", handlerStyle)
|
http.HandleFunc("/static/common.css", handlerStyle)
|
||||||
|
http.HandleFunc("/login", handlerLogin)
|
||||||
|
http.HandleFunc("/login-data", handlerLoginData)
|
||||||
http.HandleFunc("/", func(w http.ResponseWriter, rq *http.Request) {
|
http.HandleFunc("/", func(w http.ResponseWriter, rq *http.Request) {
|
||||||
http.Redirect(w, rq, "/page/"+util.HomePage, http.StatusSeeOther)
|
http.Redirect(w, rq, "/page/"+util.HomePage, http.StatusSeeOther)
|
||||||
})
|
})
|
||||||
|
45
templates/login.qtpl
Normal file
45
templates/login.qtpl
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
{% import "github.com/bouncepaw/mycorrhiza/user" %}
|
||||||
|
|
||||||
|
{% func LoginHTML() %}
|
||||||
|
<main>
|
||||||
|
<section>
|
||||||
|
{% 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>
|
||||||
|
<fieldset>
|
||||||
|
<legend>Username</legend>
|
||||||
|
<input type="text" required autofocus name="username" autocomplete="on">
|
||||||
|
</fieldset>
|
||||||
|
<fieldset>
|
||||||
|
<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>
|
||||||
|
<input type="submit">
|
||||||
|
<a href="/">Cancel</a>
|
||||||
|
</form>
|
||||||
|
{% else %}
|
||||||
|
<p>Administrator of this wiki have not configured any authorization method. You can make edits anonymously.</p>
|
||||||
|
<p><a href="/">← Go home</a></p>
|
||||||
|
{% endif %}
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
{% endfunc %}
|
||||||
|
|
||||||
|
{% func LoginErrorHTML(err string) %}
|
||||||
|
<main>
|
||||||
|
<section>
|
||||||
|
{% switch err %}
|
||||||
|
{% case "unknown username" %}
|
||||||
|
<p class="error">Unknown username.</p>
|
||||||
|
{% case "wrong password" %}
|
||||||
|
<p class="error">Wrong password.</p>
|
||||||
|
{% default %}
|
||||||
|
<p class="error">{%s err %}</p>
|
||||||
|
{% endswitch %}
|
||||||
|
<p><a href="/login">← Try again</a></p>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
{% endfunc %}
|
||||||
|
|
159
templates/login.qtpl.go
Normal file
159
templates/login.qtpl.go
Normal file
@ -0,0 +1,159 @@
|
|||||||
|
// Code generated by qtc from "login.qtpl". DO NOT EDIT.
|
||||||
|
// See https://github.com/valyala/quicktemplate for details.
|
||||||
|
|
||||||
|
//line templates/login.qtpl:1
|
||||||
|
package templates
|
||||||
|
|
||||||
|
//line templates/login.qtpl:1
|
||||||
|
import "github.com/bouncepaw/mycorrhiza/user"
|
||||||
|
|
||||||
|
//line templates/login.qtpl:3
|
||||||
|
import (
|
||||||
|
qtio422016 "io"
|
||||||
|
|
||||||
|
qt422016 "github.com/valyala/quicktemplate"
|
||||||
|
)
|
||||||
|
|
||||||
|
//line templates/login.qtpl:3
|
||||||
|
var (
|
||||||
|
_ = qtio422016.Copy
|
||||||
|
_ = qt422016.AcquireByteBuffer
|
||||||
|
)
|
||||||
|
|
||||||
|
//line templates/login.qtpl:3
|
||||||
|
func StreamLoginHTML(qw422016 *qt422016.Writer) {
|
||||||
|
//line templates/login.qtpl:3
|
||||||
|
qw422016.N().S(`
|
||||||
|
<main>
|
||||||
|
<section>
|
||||||
|
`)
|
||||||
|
//line templates/login.qtpl:6
|
||||||
|
if user.AuthUsed {
|
||||||
|
//line templates/login.qtpl:6
|
||||||
|
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>
|
||||||
|
<fieldset>
|
||||||
|
<legend>Username</legend>
|
||||||
|
<input type="text" required autofocus name="username" autocomplete="on">
|
||||||
|
</fieldset>
|
||||||
|
<fieldset>
|
||||||
|
<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>
|
||||||
|
<input type="submit">
|
||||||
|
<a href="/">Cancel</a>
|
||||||
|
</form>
|
||||||
|
`)
|
||||||
|
//line templates/login.qtpl:22
|
||||||
|
} else {
|
||||||
|
//line templates/login.qtpl:22
|
||||||
|
qw422016.N().S(`
|
||||||
|
<p>Administrator of this wiki have not configured any authorization method. You can make edits anonymously.</p>
|
||||||
|
<p><a href="/">← Go home</a></p>
|
||||||
|
`)
|
||||||
|
//line templates/login.qtpl:25
|
||||||
|
}
|
||||||
|
//line templates/login.qtpl:25
|
||||||
|
qw422016.N().S(`
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
`)
|
||||||
|
//line templates/login.qtpl:28
|
||||||
|
}
|
||||||
|
|
||||||
|
//line templates/login.qtpl:28
|
||||||
|
func WriteLoginHTML(qq422016 qtio422016.Writer) {
|
||||||
|
//line templates/login.qtpl:28
|
||||||
|
qw422016 := qt422016.AcquireWriter(qq422016)
|
||||||
|
//line templates/login.qtpl:28
|
||||||
|
StreamLoginHTML(qw422016)
|
||||||
|
//line templates/login.qtpl:28
|
||||||
|
qt422016.ReleaseWriter(qw422016)
|
||||||
|
//line templates/login.qtpl:28
|
||||||
|
}
|
||||||
|
|
||||||
|
//line templates/login.qtpl:28
|
||||||
|
func LoginHTML() string {
|
||||||
|
//line templates/login.qtpl:28
|
||||||
|
qb422016 := qt422016.AcquireByteBuffer()
|
||||||
|
//line templates/login.qtpl:28
|
||||||
|
WriteLoginHTML(qb422016)
|
||||||
|
//line templates/login.qtpl:28
|
||||||
|
qs422016 := string(qb422016.B)
|
||||||
|
//line templates/login.qtpl:28
|
||||||
|
qt422016.ReleaseByteBuffer(qb422016)
|
||||||
|
//line templates/login.qtpl:28
|
||||||
|
return qs422016
|
||||||
|
//line templates/login.qtpl:28
|
||||||
|
}
|
||||||
|
|
||||||
|
//line templates/login.qtpl:30
|
||||||
|
func StreamLoginErrorHTML(qw422016 *qt422016.Writer, err string) {
|
||||||
|
//line templates/login.qtpl:30
|
||||||
|
qw422016.N().S(`
|
||||||
|
<main>
|
||||||
|
<section>
|
||||||
|
`)
|
||||||
|
//line templates/login.qtpl:33
|
||||||
|
switch err {
|
||||||
|
//line templates/login.qtpl:34
|
||||||
|
case "unknown username":
|
||||||
|
//line templates/login.qtpl:34
|
||||||
|
qw422016.N().S(`
|
||||||
|
<p class="error">Unknown username.</p>
|
||||||
|
`)
|
||||||
|
//line templates/login.qtpl:36
|
||||||
|
case "wrong password":
|
||||||
|
//line templates/login.qtpl:36
|
||||||
|
qw422016.N().S(`
|
||||||
|
<p class="error">Wrong password.</p>
|
||||||
|
`)
|
||||||
|
//line templates/login.qtpl:38
|
||||||
|
default:
|
||||||
|
//line templates/login.qtpl:38
|
||||||
|
qw422016.N().S(`
|
||||||
|
<p class="error">`)
|
||||||
|
//line templates/login.qtpl:39
|
||||||
|
qw422016.E().S(err)
|
||||||
|
//line templates/login.qtpl:39
|
||||||
|
qw422016.N().S(`</p>
|
||||||
|
`)
|
||||||
|
//line templates/login.qtpl:40
|
||||||
|
}
|
||||||
|
//line templates/login.qtpl:40
|
||||||
|
qw422016.N().S(`
|
||||||
|
<p><a href="/login">← Try again</a></p>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
`)
|
||||||
|
//line templates/login.qtpl:44
|
||||||
|
}
|
||||||
|
|
||||||
|
//line templates/login.qtpl:44
|
||||||
|
func WriteLoginErrorHTML(qq422016 qtio422016.Writer, err string) {
|
||||||
|
//line templates/login.qtpl:44
|
||||||
|
qw422016 := qt422016.AcquireWriter(qq422016)
|
||||||
|
//line templates/login.qtpl:44
|
||||||
|
StreamLoginErrorHTML(qw422016, err)
|
||||||
|
//line templates/login.qtpl:44
|
||||||
|
qt422016.ReleaseWriter(qw422016)
|
||||||
|
//line templates/login.qtpl:44
|
||||||
|
}
|
||||||
|
|
||||||
|
//line templates/login.qtpl:44
|
||||||
|
func LoginErrorHTML(err string) string {
|
||||||
|
//line templates/login.qtpl:44
|
||||||
|
qb422016 := qt422016.AcquireByteBuffer()
|
||||||
|
//line templates/login.qtpl:44
|
||||||
|
WriteLoginErrorHTML(qb422016, err)
|
||||||
|
//line templates/login.qtpl:44
|
||||||
|
qs422016 := string(qb422016.B)
|
||||||
|
//line templates/login.qtpl:44
|
||||||
|
qt422016.ReleaseByteBuffer(qb422016)
|
||||||
|
//line templates/login.qtpl:44
|
||||||
|
return qs422016
|
||||||
|
//line templates/login.qtpl:44
|
||||||
|
}
|
69
user/user.go
69
user/user.go
@ -4,15 +4,68 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/bouncepaw/mycorrhiza/util"
|
"github.com/bouncepaw/mycorrhiza/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
type FixedUserStorage struct {
|
func LoginDataHTTP(w http.ResponseWriter, rq *http.Request, username, password string) string {
|
||||||
Users []*User
|
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 ""
|
||||||
}
|
}
|
||||||
|
|
||||||
var UserStorage = FixedUserStorage{}
|
// 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 {
|
||||||
|
UserStorage.Tokens[token] = username
|
||||||
|
log.Println("New token for", username, "is", token)
|
||||||
|
}
|
||||||
|
return token, err
|
||||||
|
}
|
||||||
|
|
||||||
|
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]string
|
||||||
|
}
|
||||||
|
|
||||||
|
var UserStorage = FixedUserStorage{Tokens: make(map[string]string)}
|
||||||
|
|
||||||
func PopulateFixedUserStorage() {
|
func PopulateFixedUserStorage() {
|
||||||
contents, err := ioutil.ReadFile(util.FixedCredentialsPath)
|
contents, err := ioutil.ReadFile(util.FixedCredentialsPath)
|
||||||
@ -97,3 +150,13 @@ func (ug UserGroup) CanAccessRoute(route string) bool {
|
|||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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: "/",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
10
util/util.go
10
util/util.go
@ -1,6 +1,8 @@
|
|||||||
package util
|
package util
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/hex"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
@ -44,3 +46,11 @@ func FindSubhyphae(hyphaName string, hyphaIterator func(func(string))) []string
|
|||||||
})
|
})
|
||||||
return subhyphae
|
return subhyphae
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func RandomString(n int) (string, error) {
|
||||||
|
bytes := make([]byte, n)
|
||||||
|
if _, err := rand.Read(bytes); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return hex.EncodeToString(bytes), nil
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user