diff options
author | Julien Dessaux | 2021-04-21 17:23:07 +0200 |
---|---|---|
committer | Julien Dessaux | 2021-04-21 17:23:07 +0200 |
commit | 4a2fb7e82d5d617298cb28b66485fc6f30c55781 (patch) | |
tree | 4ae55d8208e3ebf603f3a17ac56f6efd0e011efc | |
parent | Implemented the ResumeSession function (diff) | |
download | trains-4a2fb7e82d5d617298cb28b66485fc6f30c55781.tar.gz trains-4a2fb7e82d5d617298cb28b66485fc6f30c55781.tar.bz2 trains-4a2fb7e82d5d617298cb28b66485fc6f30c55781.zip |
Reworked the webui package, added authentication feature and tests
-rw-r--r-- | cmd/trains-webui/main.go | 12 | ||||
-rw-r--r-- | internal/webui/app.go | 78 | ||||
-rw-r--r-- | internal/webui/html/base.html | 18 | ||||
-rw-r--r-- | internal/webui/html/index.html | 24 | ||||
-rw-r--r-- | internal/webui/html/login.html | 13 | ||||
-rw-r--r-- | internal/webui/html/root.html | 15 | ||||
-rw-r--r-- | internal/webui/login.go | 85 | ||||
-rw-r--r-- | internal/webui/login_test.go | 136 | ||||
-rw-r--r-- | internal/webui/root.go | 58 | ||||
-rw-r--r-- | internal/webui/root_test.go | 54 | ||||
-rw-r--r-- | internal/webui/session.go | 19 | ||||
-rw-r--r-- | internal/webui/utils.go | 60 | ||||
-rw-r--r-- | internal/webui/utils_test.go | 71 | ||||
-rw-r--r-- | internal/webui/webui.go | 25 | ||||
-rw-r--r-- | pkg/model/users.go | 10 |
15 files changed, 570 insertions, 108 deletions
diff --git a/cmd/trains-webui/main.go b/cmd/trains-webui/main.go index 0ea3b57..c33cbbf 100644 --- a/cmd/trains-webui/main.go +++ b/cmd/trains-webui/main.go @@ -7,6 +7,7 @@ import ( "git.adyxax.org/adyxax/trains/internal/webui" "git.adyxax.org/adyxax/trains/pkg/config" + "git.adyxax.org/adyxax/trains/pkg/database" ) func main() { @@ -23,5 +24,14 @@ func main() { if err != nil { log.Fatal(err) } - webui.Run(c) + + db, err := database.InitDB("sqlite3", "file:test.db?_foreign_keys=on") + if err != nil { + log.Fatal(err) + } + if err := db.Migrate(); err != nil { + log.Fatal(err) + } + + webui.Run(c, db) } diff --git a/internal/webui/app.go b/internal/webui/app.go deleted file mode 100644 index f6082dc..0000000 --- a/internal/webui/app.go +++ /dev/null @@ -1,78 +0,0 @@ -package webui - -import ( - "embed" - "html/template" - "log" - "net/http" - "time" - - "git.adyxax.org/adyxax/trains/pkg/config" - "git.adyxax.org/adyxax/trains/pkg/navitia_api_client" -) - -// the api client object -var client *navitia_api_client.Client - -// the webui configuration -var conf *config.Config - -//go:embed html/* -var templatesFS embed.FS - -//go:embed static/* -var staticFS embed.FS - -// The page template variable -type Page struct { - Departures []Departure - Title string -} -type Departure struct { - DisplayName string - Arrival string - Odd bool -} - -// The root handler of the webui -func rootHandler(w http.ResponseWriter, r *http.Request) { - if r.URL.Path == "/" { - var p Page - if d, err := client.GetDepartures(conf.TrainStop); err != nil { - log.Printf("%+v\n%s\n", d, err) - } else { - for i := 0; i < len(d.Departures); i++ { - t, err := time.Parse("20060102T150405", d.Departures[i].StopDateTime.ArrivalDateTime) - if err != nil { - panic(err) - } - p.Departures = append(p.Departures, Departure{d.Departures[i].DisplayInformations.Direction, t.Format("Mon, 02 Jan 2006 15:04:05"), i%2 == 1}) - } - w.Header().Set("Cache-Control", "no-store, no-cache") - } - p.Title = "Horaires des prochains trains à Crépieux la Pape" - renderTemplate(w, "index", p) - } else { - http.Error(w, "404 Not Found", http.StatusNotFound) - } -} - -var templates = template.Must(template.ParseFS(templatesFS, "html/index.html")) - -func renderTemplate(w http.ResponseWriter, tmpl string, p Page) { - err := templates.ExecuteTemplate(w, tmpl+".html", p) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - } -} - -func Run(c *config.Config) { - conf = c - client = navitia_api_client.NewClient(c.Token) - http.Handle("/static/", http.FileServer(http.FS(staticFS))) - http.HandleFunc("/", rootHandler) - - listenStr := c.Address + ":" + c.Port - log.Printf("Starting webui on %s", listenStr) - log.Fatal(http.ListenAndServe(listenStr, nil)) -} diff --git a/internal/webui/html/base.html b/internal/webui/html/base.html new file mode 100644 index 0000000..7522e94 --- /dev/null +++ b/internal/webui/html/base.html @@ -0,0 +1,18 @@ +{{ define "base" }} +<!doctype html> +<html lang="en"> + <head> + <meta charset="utf-8"> + <title>{{ .Title }}</title> + <meta name="viewport" content="width=device-width, initial-scale=1"> + + <link rel="icon" type="image/png" href="/static/favicon.png" /> + <link rel="stylesheet" href="/static/main.css"> + </head> + <body> + <main> + {{ template "main" . }} + </main> + </body> +</html> +{{ end }} diff --git a/internal/webui/html/index.html b/internal/webui/html/index.html deleted file mode 100644 index a0a7d82..0000000 --- a/internal/webui/html/index.html +++ /dev/null @@ -1,24 +0,0 @@ -<!doctype html> -<html lang="en"> - <head> - <meta charset="utf-8"> - <title>{{ .Title }}</title> - <meta name="viewport" content="width=device-width, initial-scale=1"> - - <link rel="icon" type="image/png" href="/static/favicon.png" /> - <link rel="stylesheet" href="/static/main.css"> - </head> - <body> - <h3>Horaires des prochains trains à Crépieux la Pape</h3> - <table> - <thead> - <tr><th>Arrivée en gare</th><th>Direction</th></tr> - </thead> - <tbody> - {{ range .Departures }} - <tr{{ if .Odd }} style="color:#111111;"{{ end }}><td>{{ .Arrival }}</td><td>{{ .DisplayName }}</td></tr> - {{ end }} - </tbody> - </table> - </body> -</html> diff --git a/internal/webui/html/login.html b/internal/webui/html/login.html new file mode 100644 index 0000000..33e9f02 --- /dev/null +++ b/internal/webui/html/login.html @@ -0,0 +1,13 @@ +{{ template "base" . }} + +{{ define "main" }} +<form action="/login" method="post"> + <label for="username"><b>Username</b></label> + <input type="text" placeholder="Enter Username" name="username" required> + + <label for="password"><b>Password</b></label> + <input type="password" placeholder="Enter Password" name="password" required> + + <button type="submit">Login</button> +</form> +{{ end }} diff --git a/internal/webui/html/root.html b/internal/webui/html/root.html new file mode 100644 index 0000000..a293ee7 --- /dev/null +++ b/internal/webui/html/root.html @@ -0,0 +1,15 @@ +{{ template "base" . }} + +{{ define "main" }} +<h3>Horaires des prochains trains à Crépieux la Pape</h3> +<table> + <thead> + <tr><th>Arrivée en gare</th><th>Direction</th></tr> + </thead> + <tbody> + {{ range .Departures }} + <tr{{ if .Odd }} style="color:#111111;"{{ end }}><td>{{ .Arrival }}</td><td>{{ .DisplayName }}</td></tr> + {{ end }} + </tbody> +</table> +{{ end }} diff --git a/internal/webui/login.go b/internal/webui/login.go new file mode 100644 index 0000000..c0eb109 --- /dev/null +++ b/internal/webui/login.go @@ -0,0 +1,85 @@ +package webui + +import ( + "fmt" + "html/template" + "net/http" + "regexp" + + "git.adyxax.org/adyxax/trains/pkg/database" + "git.adyxax.org/adyxax/trains/pkg/model" +) + +const sessionCookieName = "session-trains-webui" + +var validUsername = regexp.MustCompile(`^[a-zA-Z]\w*$`) +var validPassword = regexp.MustCompile(`^.+$`) + +var loginTemplate = template.Must(template.ParseFS(templatesFS, "html/base.html", "html/login.html")) + +// The login handler of the webui +func loginHandler(e *env, w http.ResponseWriter, r *http.Request) error { + if r.URL.Path == "/login" { + _, err := tryAndResumeSession(e, r) + if err == nil { + // already logged in + http.Redirect(w, r, "/", http.StatusFound) + return nil + } + if r.Method == http.MethodPost { + r.ParseForm() + // username + username, ok := r.Form["username"] + if !ok { + return newStatusError(http.StatusBadRequest, fmt.Errorf("No username field in POST")) + } + if len(username) != 1 { + return newStatusError(http.StatusBadRequest, fmt.Errorf("Invalid multiple username fields in POST")) + } + if ok := validUsername.MatchString(username[0]); !ok { + return newStatusError(http.StatusBadRequest, fmt.Errorf("Invalid username field in POST")) + } + // password + password, ok := r.Form["password"] + if !ok { + return newStatusError(http.StatusBadRequest, fmt.Errorf("No password field in POST")) + } + if len(password) != 1 { + return newStatusError(http.StatusBadRequest, fmt.Errorf("Invalid multiple password fields in POST")) + } + if ok := validPassword.MatchString(password[0]); !ok { + return newStatusError(http.StatusBadRequest, fmt.Errorf("Invalid password field in POST")) + } + // try to login + user, err := e.dbEnv.Login(&model.UserLogin{Username: username[0], Password: password[0]}) + if err != nil { + switch e := err.(type) { + case *database.PasswordError: + // TODO : handle in page + return e + case *database.QueryError: + return e + default: + return e + } + } + token, err := e.dbEnv.CreateSession(user) + if err != nil { + return newStatusError(http.StatusInternalServerError, err) + } + cookie := http.Cookie{Name: sessionCookieName, Value: *token, Path: "/", HttpOnly: true, SameSite: http.SameSiteStrictMode, MaxAge: 3600000} + http.SetCookie(w, &cookie) + http.Redirect(w, r, "/", http.StatusFound) + return nil + } else { + p := Page{Title: "Login"} + err := loginTemplate.ExecuteTemplate(w, "login.html", p) + if err != nil { + return newStatusError(http.StatusInternalServerError, err) + } + return nil + } + } else { + return newStatusError(http.StatusNotFound, fmt.Errorf("Invalid path in loginHandler")) + } +} diff --git a/internal/webui/login_test.go b/internal/webui/login_test.go new file mode 100644 index 0000000..562095f --- /dev/null +++ b/internal/webui/login_test.go @@ -0,0 +1,136 @@ +package webui + +import ( + "net/http" + "net/url" + "testing" + + "git.adyxax.org/adyxax/trains/pkg/database" + "git.adyxax.org/adyxax/trains/pkg/model" + "github.com/stretchr/testify/require" +) + +func TestLoginHandler(t *testing.T) { + // test environment setup + dbEnv, err := database.InitDB("sqlite3", "file::memory:?_foreign_keys=on") + require.Nil(t, err) + err = dbEnv.Migrate() + require.Nil(t, err) + user1, err := dbEnv.CreateUser(&model.UserRegistration{Username: "user1", Password: "password1", Email: "julien@adyxax.org"}) + require.Nil(t, err) + _, err = dbEnv.Login(&model.UserLogin{Username: "user1", Password: "password1"}) + require.Nil(t, err) + token1, err := dbEnv.CreateSession(user1) + require.Nil(t, err) + e := &env{dbEnv: dbEnv} + // test GET requests + runHttpTest(t, e, loginHandler, &httpTestCase{ + name: "a simple get should display the login page", + input: httpTestInput{ + method: http.MethodGet, + path: "/login", + }, + expect: httpTestExpect{ + code: http.StatusOK, + bodyString: "<form action=\"/login\"", + }, + }) + runHttpTest(t, e, loginHandler, &httpTestCase{ + name: "an invalid or expired token should also just display the login page", + input: httpTestInput{ + method: http.MethodGet, + path: "/login", + cookie: &http.Cookie{Name: sessionCookieName, Value: "graou"}, + }, + expect: httpTestExpect{ + code: http.StatusOK, + bodyString: "<form action=\"/login\"", + }, + }) + runHttpTest(t, e, loginHandler, &httpTestCase{ + name: "if already logged in we should be redirected to /", + input: httpTestInput{ + method: http.MethodGet, + path: "/login", + cookie: &http.Cookie{Name: sessionCookieName, Value: *token1}, + }, + expect: httpTestExpect{ + code: http.StatusFound, + location: "/", + }, + }) + runHttpTest(t, e, loginHandler, &httpTestCase{ + name: "an invalid path should get a 404", + input: httpTestInput{ + method: http.MethodGet, + path: "/login/non_existent", + }, + expect: httpTestExpect{ + code: http.StatusNotFound, + err: &statusError{http.StatusNotFound, simpleErrorMessage}, + }, + }) + runHttpTest(t, e, loginHandler, &httpTestCase{ + name: "an invalid path should get a 404 even if we are already logged in", + input: httpTestInput{ + method: http.MethodGet, + path: "/login/non_existent", + cookie: &http.Cookie{Name: sessionCookieName, Value: *token1}, + }, + expect: httpTestExpect{ + code: http.StatusNotFound, + err: &statusError{http.StatusNotFound, simpleErrorMessage}, + }, + }) + // Test POST requests + runHttpTest(t, e, loginHandler, &httpTestCase{ + name: "a valid login attempt should succeed and redirect to /", + input: httpTestInput{ + method: http.MethodPost, + path: "/login", + data: url.Values{ + "username": []string{"user1"}, + "password": []string{"password1"}, + }, + }, + expect: httpTestExpect{ + code: http.StatusFound, + location: "/", + setsCookie: true, + }, + }) + //errorNoUsername := newTestRequest(t, http.MethodPost, "/login", nil) + //// too many username fields + //dataWtfUsername := url.Values{"username": []string{"user1", "user2"}} + //errorWtfUsername, err := http.NewRequest(http.MethodPost, "/login", strings.NewReader(dataWtfUsername.Encode())) + //require.Nil(t, err) + //errorWtfUsername.Header.Add("Content-Type", "application/x-www-form-urlencoded") + //// Invalid username + //dataInvalidUsername := url.Values{"username": []string{"%"}} + //errorInvalidUsername, err := http.NewRequest(http.MethodPost, "/login", strings.NewReader(dataInvalidUsername.Encode())) + //require.Nil(t, err) + //errorInvalidUsername.Header.Add("Content-Type", "application/x-www-form-urlencoded") + //// no password field + //dataNoPassword := url.Values{"username": []string{"user1"}} + //errorNoPassword, err := http.NewRequest(http.MethodPost, "/login", strings.NewReader(dataNoPassword.Encode())) + //require.Nil(t, err) + //errorNoPassword.Header.Add("Content-Type", "application/x-www-form-urlencoded") + //// too many password fields + //dataWtfPassword := url.Values{"username": []string{"user1"}, "password": []string{"user1", "user2"}} + //errorWtfPassword, err := http.NewRequest(http.MethodPost, "/login", strings.NewReader(dataWtfPassword.Encode())) + //require.Nil(t, err) + //errorWtfPassword.Header.Add("Content-Type", "application/x-www-form-urlencoded") + //// Invalid password + //dataInvalidPassword := url.Values{"username": []string{"user1"}, "password": []string{""}} + //errorInvalidPassword, err := http.NewRequest(http.MethodPost, "/login", strings.NewReader(dataInvalidPassword.Encode())) + //require.Nil(t, err) + //errorInvalidPassword.Header.Add("Content-Type", "application/x-www-form-urlencoded") + //// run the tests + //// {"error no username", &env{dbEnv: dbEnv}, errorNoUsername, &expected{err: &statusError{code: 500, err: simpleError}}}, + //// {"error wtf username", &env{dbEnv: dbEnv}, errorWtfUsername, &expected{err: &statusError{code: 500, err: simpleError}}}, + //// {"error invalid username", &env{dbEnv: dbEnv}, errorInvalidUsername, &expected{err: &statusError{code: 500, err: simpleError}}}, + //// {"error no password", &env{dbEnv: dbEnv}, errorNoPassword, &expected{err: &statusError{code: 500, err: simpleError}}}, + //// {"error wtf password", &env{dbEnv: dbEnv}, errorWtfPassword, &expected{err: &statusError{code: 500, err: simpleError}}}, + //// {"error invalid password", &env{dbEnv: dbEnv}, errorInvalidPassword, &expected{err: &statusError{code: 500, err: simpleError}}}, + ////} +} diff --git a/internal/webui/root.go b/internal/webui/root.go new file mode 100644 index 0000000..c3a0ec5 --- /dev/null +++ b/internal/webui/root.go @@ -0,0 +1,58 @@ +package webui + +import ( + "fmt" + "html/template" + "log" + "net/http" + "time" + + "git.adyxax.org/adyxax/trains/pkg/model" +) + +var rootTemplate = template.Must(template.ParseFS(templatesFS, "html/base.html", "html/root.html")) + +// The page template variable +type Page struct { + User *model.User + Departures []Departure + Title string +} +type Departure struct { + DisplayName string + Arrival string + Odd bool +} + +// The root handler of the webui +func rootHandler(e *env, w http.ResponseWriter, r *http.Request) error { + if r.URL.Path == "/" { + var p Page + user, err := tryAndResumeSession(e, r) + if err != nil { + http.Redirect(w, r, "/login", http.StatusFound) + return nil + } + p.User = user + if d, err := e.navitia.GetDepartures(e.conf.TrainStop); err != nil { + log.Printf("%+v\n%s\n", d, err) + } else { + for i := 0; i < len(d.Departures); i++ { + t, err := time.Parse("20060102T150405", d.Departures[i].StopDateTime.ArrivalDateTime) + if err != nil { + panic(err) + } + p.Departures = append(p.Departures, Departure{d.Departures[i].DisplayInformations.Direction, t.Format("Mon, 02 Jan 2006 15:04:05"), i%2 == 1}) + } + w.Header().Set("Cache-Control", "no-store, no-cache") + } + p.Title = "Horaires des prochains trains à Crépieux la Pape" + err = rootTemplate.ExecuteTemplate(w, "root.html", p) + if err != nil { + return newStatusError(http.StatusInternalServerError, err) + } + return nil + } else { + return newStatusError(http.StatusNotFound, fmt.Errorf("Invalid path in rootHandler")) + } +} diff --git a/internal/webui/root_test.go b/internal/webui/root_test.go new file mode 100644 index 0000000..8faee77 --- /dev/null +++ b/internal/webui/root_test.go @@ -0,0 +1,54 @@ +package webui + +import ( + "net/http" + "testing" + + "git.adyxax.org/adyxax/trains/pkg/database" + "git.adyxax.org/adyxax/trains/pkg/model" + "github.com/stretchr/testify/require" +) + +func TestRootHandler(t *testing.T) { + // test environment setup + dbEnv, err := database.InitDB("sqlite3", "file::memory:?_foreign_keys=on") + require.Nil(t, err) + err = dbEnv.Migrate() + require.Nil(t, err) + user1, err := dbEnv.CreateUser(&model.UserRegistration{Username: "user1", Password: "password1", Email: "julien@adyxax.org"}) + require.Nil(t, err) + _, err = dbEnv.Login(&model.UserLogin{Username: "user1", Password: "password1"}) + require.Nil(t, err) + token1, err := dbEnv.CreateSession(user1) + require.Nil(t, err) + e := &env{ + dbEnv: dbEnv, + // TODO mock navitia + } + // test GET requests + runHttpTest(t, e, rootHandler, &httpTestCase{ + name: "a simple get when not logged in should redirect to the login page", + input: httpTestInput{ + method: http.MethodGet, + path: "/", + }, + expect: httpTestExpect{ + code: http.StatusFound, + location: "/login", + }, + }) + // TODO mock navitia + _ = token1 + //runHttpTest(t, e, rootHandler, &httpTestCase{ + // name: "a simple get when logged in should display the departure times", + // input: httpTestInput{ + // method: http.MethodGet, + // path: "/", + // cookie: &http.Cookie{Name: sessionCookieName, Value: *token1}, + // }, + // expect: httpTestExpect{ + // code: http.StatusOK, + // bodyString: "Horaires des prochains trains", + // }, + //}) +} diff --git a/internal/webui/session.go b/internal/webui/session.go new file mode 100644 index 0000000..58256f2 --- /dev/null +++ b/internal/webui/session.go @@ -0,0 +1,19 @@ +package webui + +import ( + "net/http" + + "git.adyxax.org/adyxax/trains/pkg/model" +) + +func tryAndResumeSession(e *env, r *http.Request) (*model.User, error) { + cookie, err := r.Cookie(sessionCookieName) + if err != nil { + return nil, err + } + user, err := e.dbEnv.ResumeSession(cookie.Value) + if err != nil { + return nil, err + } + return user, nil +} diff --git a/internal/webui/utils.go b/internal/webui/utils.go new file mode 100644 index 0000000..7152401 --- /dev/null +++ b/internal/webui/utils.go @@ -0,0 +1,60 @@ +package webui + +import ( + "embed" + "log" + "net/http" + + "git.adyxax.org/adyxax/trains/pkg/config" + "git.adyxax.org/adyxax/trains/pkg/database" + "git.adyxax.org/adyxax/trains/pkg/navitia_api_client" +) + +//go:embed html/* +var templatesFS embed.FS + +//go:embed static/* +var staticFS embed.FS + +// the environment that will be passed to our handlers +type env struct { + conf *config.Config + dbEnv *database.DBEnv + navitia *navitia_api_client.Client +} + +type handlerError interface { + error + Status() int +} + +type statusError struct { + code int + err error +} + +func (e *statusError) Error() string { return e.err.Error() } +func (e *statusError) Status() int { return e.code } +func newStatusError(code int, err error) error { return &statusError{code: code, err: err} } + +type handler struct { + e *env + h func(e *env, w http.ResponseWriter, r *http.Request) error +} + +// ServeHTTP allows our handler type to satisfy http.Handler +func (h handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + path := r.URL.Path + err := h.h(h.e, w, r) + if err != nil { + switch e := err.(type) { + case handlerError: + log.Printf("HTTP %d - %s", e.Status(), e) + http.Error(w, e.Error(), e.Status()) + default: + // Any error types we don't specifically look out for default to serving a HTTP 500 + log.Printf("%s : handler returned an unexpected error : %+v", path, e) + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) + } + } +} diff --git a/internal/webui/utils_test.go b/internal/webui/utils_test.go new file mode 100644 index 0000000..1e07aa2 --- /dev/null +++ b/internal/webui/utils_test.go @@ -0,0 +1,71 @@ +package webui + +import ( + "fmt" + "net/http" + "net/http/httptest" + "net/url" + "reflect" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +var simpleErrorMessage = fmt.Errorf("") + +type httpTestCase struct { + name string + input httpTestInput + expect httpTestExpect +} +type httpTestInput struct { + method string + path string + cookie *http.Cookie + data url.Values +} +type httpTestExpect struct { + code int + bodyString string + location string + setsCookie bool + err interface{} +} + +func runHttpTest(t *testing.T, e *env, h func(e *env, w http.ResponseWriter, r *http.Request) error, tc *httpTestCase) { + req, err := http.NewRequest(tc.input.method, tc.input.path, nil) + require.Nil(t, err) + if tc.input.data != nil { + req, err = http.NewRequest(tc.input.method, tc.input.path, strings.NewReader(tc.input.data.Encode())) + req.Header.Add("Content-Type", "application/x-www-form-urlencoded") + } + if tc.input.cookie != nil { + req.AddCookie(tc.input.cookie) + } + t.Run(tc.name, func(t *testing.T) { + rr := httptest.NewRecorder() + err := h(e, rr, req) + if tc.expect.err != nil { + require.Error(t, err) + assert.Equalf(t, reflect.TypeOf(err), reflect.TypeOf(tc.expect.err), "Invalid error type. Got %s but expected %s", reflect.TypeOf(err), reflect.TypeOf(tc.expect.err)) + } else { + require.NoError(t, err) + assert.Equal(t, tc.expect.code, rr.Code) + if tc.expect.bodyString != "" { + assert.Contains(t, rr.Body.String(), tc.expect.bodyString) + } + if tc.expect.location != "" { + assert.Contains(t, rr.HeaderMap, "Location") + assert.Len(t, rr.HeaderMap["Location"], 1) + assert.Equal(t, rr.HeaderMap["Location"][0], tc.expect.location) + } + if tc.expect.setsCookie { + assert.Contains(t, rr.HeaderMap, "Set-Cookie") + } else { + assert.NotContains(t, rr.HeaderMap, "Set-Cookie") + } + } + }) +} diff --git a/internal/webui/webui.go b/internal/webui/webui.go new file mode 100644 index 0000000..6ce5bb4 --- /dev/null +++ b/internal/webui/webui.go @@ -0,0 +1,25 @@ +package webui + +import ( + "log" + "net/http" + + "git.adyxax.org/adyxax/trains/pkg/config" + "git.adyxax.org/adyxax/trains/pkg/database" + "git.adyxax.org/adyxax/trains/pkg/navitia_api_client" +) + +func Run(c *config.Config, dbEnv *database.DBEnv) { + e := env{ + conf: c, + dbEnv: dbEnv, + navitia: navitia_api_client.NewClient(c.Token), + } + http.Handle("/", handler{&e, rootHandler}) + http.Handle("/login", handler{&e, loginHandler}) + http.Handle("/static/", http.FileServer(http.FS(staticFS))) + + listenStr := c.Address + ":" + c.Port + log.Printf("Starting webui on %s", listenStr) + log.Fatal(http.ListenAndServe(listenStr, nil)) +} diff --git a/pkg/model/users.go b/pkg/model/users.go index e4b4bf0..943d161 100644 --- a/pkg/model/users.go +++ b/pkg/model/users.go @@ -11,12 +11,12 @@ type User struct { } type UserLogin struct { - Username string `json:"username"` - Password string `json:"password"` + Username string + Password string } type UserRegistration struct { - Username string `json:"username"` - Password string `json:"password"` - Email string `json:"email"` + Username string + Password string + Email string } |