1
0
mirror of https://github.com/osmarks/mycorrhiza.git synced 2024-12-11 21:10:26 +00:00

Implement login form

This commit is contained in:
bouncepaw 2020-11-14 15:39:18 +05:00
parent c83ea6f356
commit a0d1099b75
7 changed files with 314 additions and 4 deletions

View File

@ -1,6 +1,9 @@
run: build
./mycorrhiza metarrhiza
run_with_fixed_auth: build
./mycorrhiza -auth-method fixed metarrhiza
build:
go generate
go build .

View File

@ -3,7 +3,9 @@ A wiki engine.
Features planned for this release:
* [ ] Authorization
* [ ] User groups: `anon`, `editor`, `trusted`, `moderator`, `admin`
* [x] User groups: `anon`, `editor`, `trusted`, `moderator`, `admin`
* [ ] Login page
* [ ] Rights
* [ ] Mycomarkup improvements
* [x] Strike-through syntax
* [x] Formatting in headings

28
main.go
View File

@ -15,6 +15,7 @@ import (
"github.com/bouncepaw/mycorrhiza/history"
"github.com/bouncepaw/mycorrhiza/templates"
"github.com/bouncepaw/mycorrhiza/user"
"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() {
log.Println("Running MycorrhizaWiki β")
parseCliArgs()
@ -133,6 +159,8 @@ func main() {
http.ServeFile(w, rq, WikiDir+"/static/favicon.ico")
})
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.Redirect(w, rq, "/page/"+util.HomePage, http.StatusSeeOther)
})

45
templates/login.qtpl Normal file
View 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
View 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
}

View File

@ -4,15 +4,68 @@ import (
"encoding/json"
"io/ioutil"
"log"
"net/http"
"time"
"github.com/bouncepaw/mycorrhiza/util"
)
type FixedUserStorage struct {
Users []*User
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 ""
}
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() {
contents, err := ioutil.ReadFile(util.FixedCredentialsPath)
@ -97,3 +150,13 @@ func (ug UserGroup) CanAccessRoute(route string) bool {
}
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: "/",
}
}

View File

@ -1,6 +1,8 @@
package util
import (
"crypto/rand"
"encoding/hex"
"net/http"
"strings"
)
@ -44,3 +46,11 @@ func FindSubhyphae(hyphaName string, hyphaIterator func(func(string))) []string
})
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
}