Auth: Add page to change password.
Configure launcher as a default return to address.
Use standard X-Forwarded-User instead of custom X-User header.
Add X-Forwarded-UserId header holding user unique identificator.
Change-Id: Ib2e6329ba9fb91d2cc9a86b0c5fc78898769e3b8
diff --git a/core/auth/memberships/main.go b/core/auth/memberships/main.go
index e72a163..955e783 100644
--- a/core/auth/memberships/main.go
+++ b/core/auth/memberships/main.go
@@ -654,7 +654,7 @@
}
func getLoggedInUser(r *http.Request) (string, error) {
- if user := r.Header.Get("X-User"); user != "" {
+ if user := r.Header.Get("X-Forwarded-User"); user != "" {
return user, nil
} else {
return "", fmt.Errorf("unauthenticated")
diff --git a/core/auth/memberships/store_test.go b/core/auth/memberships/store_test.go
index bc2e9c6..aba707c 100644
--- a/core/auth/memberships/store_test.go
+++ b/core/auth/memberships/store_test.go
@@ -214,7 +214,7 @@
router := mux.NewRouter()
router.HandleFunc("/group/{parent-group}/remove-child-group/{child-group}", server.removeChildGroupHandler).Methods(http.MethodPost)
req, err := http.NewRequest("POST", "/group/bb/remove-child-group/aa", nil)
- req.Header.Set("X-User", "testuser")
+ req.Header.Set("X-Forwarded-User", "testuser")
if err != nil {
t.Fatal(err)
}
@@ -328,7 +328,7 @@
// case when group present or exist
router.HandleFunc("/api/users", server.apiGetAllUsers).Methods(http.MethodGet)
req, err := http.NewRequest("GET", "/api/users?groups=b,e,t", nil)
- req.Header.Set("X-User", "testuser1")
+ req.Header.Set("X-Forwarded-User", "testuser1")
if err != nil {
t.Fatal(err)
}
@@ -353,7 +353,7 @@
// case when no group present
req, err = http.NewRequest("GET", "/api/users?groups=", nil)
- req.Header.Set("X-User", "testuser1")
+ req.Header.Set("X-Forwarded-User", "testuser1")
if err != nil {
t.Fatal(err)
}
@@ -380,7 +380,7 @@
// case when wrong groups
req, err = http.NewRequest("GET", "/api/users?groups=x,y", nil)
- req.Header.Set("X-User", "testuser1")
+ req.Header.Set("X-Forwarded-User", "testuser1")
if err != nil {
t.Fatal(err)
}
diff --git a/core/auth/proxy/main.go b/core/auth/proxy/main.go
index 2c10258..8c98d20 100644
--- a/core/auth/proxy/main.go
+++ b/core/auth/proxy/main.go
@@ -44,6 +44,7 @@
type user struct {
Identity struct {
+ Id string `json:"id"`
Traits struct {
Username string `json:"username"`
} `json:"traits"`
@@ -95,7 +96,8 @@
func handle(w http.ResponseWriter, r *http.Request) {
reqAuth := true
for _, p := range strings.Split(*noAuthPathPrefixes, ",") {
- if strings.HasPrefix(r.URL.Path, p) {
+ t := strings.TrimSpace(p)
+ if len(t) > 0 && strings.HasPrefix(r.URL.Path, t) {
reqAuth = false
break
}
@@ -104,6 +106,7 @@
if reqAuth {
var err error
user, err = queryWhoAmI(r.Cookies())
+ fmt.Printf("--- %+v\n", user)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
@@ -142,11 +145,10 @@
}
}
}
+ fmt.Printf("%+v\n", user)
rc := r.Clone(context.Background())
- if user != nil {
- // TODO(gio): Rename to X-Forwarded-User
- rc.Header.Add("X-User", user.Identity.Traits.Username)
- }
+ rc.Header.Add("X-Forwarded-User", user.Identity.Traits.Username)
+ rc.Header.Add("X-Forwarded-UserId", user.Identity.Id)
ru, err := url.Parse(fmt.Sprintf("http://%s%s", *upstream, r.URL.RequestURI()))
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
diff --git a/core/auth/ui/api.go b/core/auth/ui/api.go
index 6fb7426..b8c5bad 100644
--- a/core/auth/ui/api.go
+++ b/core/auth/ui/api.go
@@ -4,7 +4,9 @@
"bytes"
"encoding/json"
"fmt"
+ "io"
"net/http"
+ "net/url"
"strings"
"unicode"
@@ -111,7 +113,7 @@
}
}
if !digit || !lowerCase || !upperCase || !special {
- errors = append(errors, ValidationError{"password", "Password must contain at least one ditig, lower/upper and special character"})
+ errors = append(errors, ValidationError{"password", "Password must contain at least one digit, lower&upper case and special characters"})
}
// TODO other validations
return errors
@@ -167,6 +169,74 @@
}
}
+type changePasswordReq struct {
+ Id string `json:"id,omitempty"`
+ Username string `json:"username,omitempty"`
+ Password string `json:"password,omitempty"`
+}
+
+func (s *APIServer) apiPasswordChange(id, username, password string) ([]ValidationError, error) {
+ var usernameErrors []ValidationError
+ passwordErrors := validatePassword(password)
+ allErrors := append(usernameErrors, passwordErrors...)
+ if len(allErrors) > 0 {
+ return allErrors, nil
+ }
+ var kreq kratosIdentityCreateReq
+ kreq.Credentials.Password.Config.Password = password
+ kreq.SchemaID = "user"
+ kreq.State = "active"
+ kreq.Traits.Username = username
+ var buf bytes.Buffer
+ if err := json.NewEncoder(&buf).Encode(kreq); err != nil {
+ return nil, err
+ }
+ c := http.Client{}
+ addr, err := url.Parse(s.identityEndpoint(id))
+ if err != nil {
+ return nil, err
+ }
+ hreq := &http.Request{
+ Method: http.MethodPut,
+ URL: addr,
+ Header: http.Header{"Content-Type": []string{"application/json"}},
+ Body: io.NopCloser(&buf),
+ }
+ resp, err := c.Do(hreq)
+ if err != nil {
+ return nil, err
+ }
+ if resp.StatusCode != http.StatusOK {
+ var buf bytes.Buffer
+ io.Copy(&buf, resp.Body)
+ respS := buf.String()
+ fmt.Printf("PASSWORD CHANGE ERROR: %s\n", respS)
+ var e ErrorResponse
+ if err := json.NewDecoder(bytes.NewReader([]byte(respS))).Decode(&e); err != nil {
+ return nil, err
+ }
+ return extractKratosErrorMessage(e), nil
+ }
+ return nil, nil
+}
+
+func (s *APIServer) passwordChange(w http.ResponseWriter, r *http.Request) {
+ var req changePasswordReq
+ if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
+ http.Error(w, err.Error(), http.StatusBadRequest)
+ return
+ }
+ if verr, err := s.apiPasswordChange(req.Id, req.Username, req.Password); err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ } else if len(verr) > 0 {
+ replyWithErrors(w, verr)
+ }
+}
+
func (s *APIServer) identitiesEndpoint() string {
return fmt.Sprintf("%s/admin/identities", s.kratosAddr)
}
+
+func (s *APIServer) identityEndpoint(id string) string {
+ return fmt.Sprintf("%s/admin/identities/%s", s.kratosAddr, id)
+}
diff --git a/core/auth/ui/main.go b/core/auth/ui/main.go
index 3f77bc8..73561e7 100644
--- a/core/auth/ui/main.go
+++ b/core/auth/ui/main.go
@@ -24,11 +24,10 @@
var kratos = flag.String("kratos", "https://accounts.lekva.me", "Kratos URL")
var hydra = flag.String("hydra", "hydra.pcloud", "Hydra admin server address")
var emailDomain = flag.String("email-domain", "lekva.me", "Email domain")
-
var apiPort = flag.Int("api-port", 8081, "API Port to listen on")
var kratosAPI = flag.String("kratos-api", "", "Kratos API address")
-
var enableRegistration = flag.Bool("enable-registration", false, "If true account registration will be enabled")
+var defaultReturnTo = flag.String("default-return-to", "", "Default redirect address after login")
var ErrNotLoggedIn = errors.New("Not logged in")
@@ -39,10 +38,12 @@
var static embed.FS
type Templates struct {
- WhoAmI *template.Template
- Register *template.Template
- Login *template.Template
- Consent *template.Template
+ WhoAmI *template.Template
+ Register *template.Template
+ Login *template.Template
+ Consent *template.Template
+ ChangePassword *template.Template
+ ChangePasswordSuccess *template.Template
}
func ParseTemplates(fs embed.FS) (*Templates, error) {
@@ -73,7 +74,15 @@
if err != nil {
return nil, err
}
- return &Templates{whoami, register, login, consent}, nil
+ changePassword, err := parse("templates/change-password.html")
+ if err != nil {
+ return nil, err
+ }
+ changePasswordSuccess, err := parse("templates/change-password-success.html")
+ if err != nil {
+ return nil, err
+ }
+ return &Templates{whoami, register, login, consent, changePassword, changePasswordSuccess}, nil
}
type Server struct {
@@ -83,15 +92,25 @@
hydra *HydraClient
tmpls *Templates
enableRegistration bool
+ api *APIServer
+ defaultReturnTo string
}
-func NewServer(port int, kratos string, hydra *HydraClient, tmpls *Templates, enableRegistration bool) *Server {
+func NewServer(
+ port int,
+ kratos string,
+ hydra *HydraClient,
+ tmpls *Templates,
+ enableRegistration bool,
+ api *APIServer,
+ defaultReturnTo string,
+) *Server {
r := mux.NewRouter()
serv := &http.Server{
Addr: fmt.Sprintf(":%d", port),
Handler: r,
}
- return &Server{r, serv, kratos, hydra, tmpls, enableRegistration}
+ return &Server{r, serv, kratos, hydra, tmpls, enableRegistration, api, defaultReturnTo}
}
func cacheControlWrapper(h http.Handler) http.Handler {
@@ -115,6 +134,8 @@
s.r.Path("/consent").Methods(http.MethodGet).HandlerFunc(s.consent)
s.r.Path("/consent").Methods(http.MethodPost).HandlerFunc(s.processConsent)
s.r.Path("/logout").Methods(http.MethodGet).HandlerFunc(s.logout)
+ s.r.Path("/change-password").Methods("POST").HandlerFunc(s.changePassword)
+ s.r.Path("/change-password").Methods("GET").HandlerFunc(s.changePasswordForm)
s.r.Path("/").HandlerFunc(s.whoami)
return s.serv.ListenAndServe()
}
@@ -225,7 +246,7 @@
return
}
if challenge, ok := r.Form["login_challenge"]; ok {
- username, err := getWhoAmIFromKratos(r.Cookies())
+ _, username, err := getWhoAmIFromKratos(r.Cookies())
if err != nil && err != ErrNotLoggedIn {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
@@ -246,7 +267,10 @@
HttpOnly: true,
})
}
- returnTo := r.Form.Get("return_to")
+ returnTo := r.FormValue("return_to")
+ if returnTo == "" && s.defaultReturnTo != "" {
+ returnTo = s.defaultReturnTo
+ }
flow, ok := r.Form["flow"]
if !ok {
addr := s.kratos + "/self-service/login/browser"
@@ -358,10 +382,10 @@
return lr.LogoutURL, nil
}
-func getWhoAmIFromKratos(cookies []*http.Cookie) (string, error) {
+func getWhoAmIFromKratos(cookies []*http.Cookie) (string, string, error) {
jar, err := cookiejar.New(nil)
if err != nil {
- return "", err
+ return "", "", err
}
client := &http.Client{
Jar: jar,
@@ -371,25 +395,32 @@
}
b, err := url.Parse(*kratos + "/sessions/whoami")
if err != nil {
- return "", err
+ return "", "", err
}
client.Jar.SetCookies(b, cookies)
resp, err := client.Get(*kratos + "/sessions/whoami")
if err != nil {
- return "", err
+ return "", "", err
}
respBody, err := ioutil.ReadAll(resp.Body)
if err != nil {
- return "", err
+ return "", "", err
}
username, err := regogo.Get(string(respBody), "input.identity.traits.username")
if err != nil {
- return "", err
+ return "", "", err
}
if username.String() == "" {
- return "", ErrNotLoggedIn
+ return "", "", ErrNotLoggedIn
}
- return username.String(), nil
+ id, err := regogo.Get(string(respBody), "input.identity.id")
+ if err != nil {
+ return "", "", err
+ }
+ if id.String() == "" {
+ return "", "", ErrNotLoggedIn
+ }
+ return id.String(), username.String(), nil
}
@@ -430,7 +461,6 @@
"identifier": []string{r.FormValue("username")},
}
resp, err := postFormToKratos("login", flow[0], r.Cookies(), req)
- fmt.Printf("--- %d\n", resp.StatusCode)
var vv bytes.Buffer
io.Copy(&vv, resp.Body)
fmt.Println(vv.String())
@@ -451,7 +481,7 @@
http.SetCookie(w, c)
}
if challenge, _ := r.Cookie("login_challenge"); challenge != nil {
- username, err := getWhoAmIFromKratos(resp.Cookies())
+ _, username, err := getWhoAmIFromKratos(resp.Cookies())
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
@@ -481,7 +511,7 @@
}
func (s *Server) whoami(w http.ResponseWriter, r *http.Request) {
- if username, err := getWhoAmIFromKratos(r.Cookies()); err != nil {
+ if _, username, err := getWhoAmIFromKratos(r.Cookies()); err != nil {
if errors.Is(err, ErrNotLoggedIn) {
http.Redirect(w, r, "/login", http.StatusSeeOther)
return
@@ -510,7 +540,7 @@
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
- username, err := getWhoAmIFromKratos(r.Cookies())
+ _, username, err := getWhoAmIFromKratos(r.Cookies())
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
@@ -538,7 +568,7 @@
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
- username, err := getWhoAmIFromKratos(r.Cookies())
+ _, username, err := getWhoAmIFromKratos(r.Cookies())
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
@@ -560,15 +590,67 @@
}
}
+type changePasswordData struct {
+ Username string
+ Password string
+ PasswordErrors []ValidationError
+}
+
+func (s *Server) changePasswordForm(w http.ResponseWriter, r *http.Request) {
+ _, username, err := getWhoAmIFromKratos(r.Cookies())
+ if err != nil {
+ if errors.Is(err, ErrNotLoggedIn) {
+ http.Redirect(w, r, "/", http.StatusSeeOther)
+ } else {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ }
+ return
+ }
+ if err := s.tmpls.ChangePassword.Execute(w, changePasswordData{Username: username}); err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+}
+
+func (s *Server) changePassword(w http.ResponseWriter, r *http.Request) {
+ if err := r.ParseForm(); err != nil {
+ http.Error(w, err.Error(), http.StatusBadRequest)
+ return
+ }
+ password := r.FormValue("password")
+ id, username, err := getWhoAmIFromKratos(r.Cookies())
+ if err != nil {
+ if errors.Is(err, ErrNotLoggedIn) {
+ http.Redirect(w, r, "/", http.StatusSeeOther)
+ } else {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ }
+ return
+ }
+ if verr, err := s.api.apiPasswordChange(id, username, password); err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ } else if len(verr) > 0 {
+ if err := s.tmpls.ChangePassword.Execute(w, changePasswordData{username, password, verr}); err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ } else {
+ if err := s.tmpls.ChangePasswordSuccess.Execute(w, nil); err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ }
+}
+
func main() {
flag.Parse()
t, err := ParseTemplates(tmpls)
if err != nil {
log.Fatal(err)
}
+ api := NewAPIServer(*apiPort, *kratosAPI)
go func() {
- s := NewAPIServer(*apiPort, *kratosAPI)
- log.Fatal(s.Start())
+ log.Fatal(api.Start())
}()
func() {
s := NewServer(
@@ -577,6 +659,8 @@
NewHydraClient(*hydra),
t,
*enableRegistration,
+ api,
+ *defaultReturnTo,
)
log.Fatal(s.Start())
}()
diff --git a/core/auth/ui/static/main.css b/core/auth/ui/static/main.css
index 454eb2d..a261094 100644
--- a/core/auth/ui/static/main.css
+++ b/core/auth/ui/static/main.css
@@ -83,3 +83,7 @@
label {
color: white;
}
+
+.error-message {
+ color: var(--pico-primary-hover);
+}
diff --git a/core/auth/ui/templates/base.html b/core/auth/ui/templates/base.html
index 26817d3..624f908 100644
--- a/core/auth/ui/templates/base.html
+++ b/core/auth/ui/templates/base.html
@@ -3,7 +3,7 @@
<head>
<link rel="stylesheet" href="/static/pico.2.0.6.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/hack-font/3.3.0/web/hack.min.css">
- <link rel="stylesheet" href="/static/main.css?v=0.0.1">
+ <link rel="stylesheet" href="/static/main.css?v=0.0.2">
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>{{ block "title" . }}Title{{ end }}</title>
diff --git a/core/auth/ui/templates/change-password-success.html b/core/auth/ui/templates/change-password-success.html
new file mode 100644
index 0000000..61c8e74
--- /dev/null
+++ b/core/auth/ui/templates/change-password-success.html
@@ -0,0 +1,6 @@
+{{ define "title" }}dodo: password changed{{ end }}
+{{ define "main" }}
+<div>
+ <p>Password changed successfully.</p>
+</div>
+{{ end }}
diff --git a/core/auth/ui/templates/change-password.html b/core/auth/ui/templates/change-password.html
new file mode 100644
index 0000000..4d0b487
--- /dev/null
+++ b/core/auth/ui/templates/change-password.html
@@ -0,0 +1,23 @@
+{{ define "title" }}dodo: change password{{ end }}
+{{ define "main" }}
+<div class="form-container">
+ <div class="logo">
+ <span>do</span><span>do:</span>
+ </div>
+ <form action="" method="POST">
+ <label>
+ new password
+ <input type="password" name="password" aria-label="Password" value="{{ .Password }}" aria-invalid="{{ if .PasswordErrors }}true{{ else }}undefined{{ end }}" required/>
+ </label>
+ {{ if .PasswordErrors }}
+ {{ range .PasswordErrors }}
+ <small class="error-message" aria-live="assertive">
+ {{ .Message }}
+ </small>
+ {{ end }}
+ {{ end }}
+ <button type="submit">change password</button>
+ <input type="hidden" name="username" value="{{ .Username }}" />
+ </form>
+</div>
+{{ end }}
diff --git a/core/auth/ui/templates/consent.html b/core/auth/ui/templates/consent.html
index 3a504a5..703434b 100644
--- a/core/auth/ui/templates/consent.html
+++ b/core/auth/ui/templates/consent.html
@@ -1,4 +1,4 @@
-{{ define "title" }}Consent{{ end }}
+{{ define "title" }}dodo: consent{{ end }}
{{ define "main" }}
<form action="" method="POST">
{{ range . }}
diff --git a/core/auth/ui/templates/login.html b/core/auth/ui/templates/login.html
index a80ecc7..0d961b5 100644
--- a/core/auth/ui/templates/login.html
+++ b/core/auth/ui/templates/login.html
@@ -1,4 +1,4 @@
-{{ define "title" }}Sign in{{ end }}
+{{ define "title" }}dodo: sign in{{ end }}
{{ define "main" }}
<div>
<div class="logo">
diff --git a/core/auth/ui/templates/register.html b/core/auth/ui/templates/register.html
index 22d69a5..626dc52 100644
--- a/core/auth/ui/templates/register.html
+++ b/core/auth/ui/templates/register.html
@@ -1,4 +1,4 @@
-{{ define "title" }}Create Account{{ end }}
+{{ define "title" }}dodo: create account{{ end }}
{{ define "main" }}
<form action="" method="POST">
<input type="text" name="username" placeholder="Username" autofocus required />
diff --git a/core/auth/ui/templates/whoami.html b/core/auth/ui/templates/whoami.html
index 2001c9f..944ed28 100644
--- a/core/auth/ui/templates/whoami.html
+++ b/core/auth/ui/templates/whoami.html
@@ -1,4 +1,4 @@
-{{ define "title" }}Who Am I{{ end }}
+{{ define "title" }}dodo: who am i{{ end }}
{{ define "main" }}
Hello {{.}}!
<a href="/logout" role="button">logout</a>
diff --git a/core/installer/cmd/launcher.go b/core/installer/cmd/launcher.go
index 85e811f..71decb6 100644
--- a/core/installer/cmd/launcher.go
+++ b/core/installer/cmd/launcher.go
@@ -3,6 +3,7 @@
import (
"fmt"
"log"
+ "net/url"
"os"
"github.com/giolekva/pcloud/core/installer"
@@ -14,6 +15,7 @@
)
var launcherFlags struct {
+ // TODO(gio): rename to auth-base-addr
logoutURL string
port int
repoAddr string
@@ -78,9 +80,13 @@
if err != nil {
return err
}
+ authBaseAddr, err := url.Parse(launcherFlags.logoutURL)
+ if err != nil {
+ return err
+ }
s, err := welcome.NewLauncherServer(
launcherFlags.port,
- launcherFlags.logoutURL,
+ fmt.Sprintf("https://%s", authBaseAddr.Host),
&welcome.AppManagerDirectory{AppManager: appManager},
)
if err != nil {
diff --git a/core/installer/cmd/welcome.go b/core/installer/cmd/welcome.go
index 30ed48f..d754ad3 100644
--- a/core/installer/cmd/welcome.go
+++ b/core/installer/cmd/welcome.go
@@ -88,7 +88,7 @@
if err != nil {
return err
}
- s := welcome.NewServer(
+ s, err := welcome.NewServer(
welcomeFlags.port,
repoIO,
nsCreator,
@@ -97,6 +97,9 @@
welcomeFlags.loginAddr,
welcomeFlags.membershipsAddr,
)
+ if err != nil {
+ return err
+ }
s.Start()
return nil
}
diff --git a/core/installer/values-tmpl/core-auth.cue b/core/installer/values-tmpl/core-auth.cue
index d535c97..b3146f8 100644
--- a/core/installer/values-tmpl/core-auth.cue
+++ b/core/installer/values-tmpl/core-auth.cue
@@ -428,6 +428,7 @@
domain: input.network.domain
hydra: "hydra-admin.\(global.namespacePrefix)core-auth.svc.cluster.local"
enableRegistration: false
+ defaultReturnTo: "https://launcher.\(global.domain)"
image: {
repository: images.ui.fullName
tag: images.ui.tag
diff --git a/core/installer/welcome/create-account.html b/core/installer/welcome/create-account.html
deleted file mode 100644
index cb9351e..0000000
--- a/core/installer/welcome/create-account.html
+++ /dev/null
@@ -1,48 +0,0 @@
-<!DOCTYPE html>
-<html lang="en" data-theme="light">
-<head>
- <link rel="stylesheet" href="/stat/pico.2.0.6.min.css">
- <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/hack-font/3.3.0/web/hack.min.css">
- <link rel="stylesheet" href="/stat/welcome.css?v=0.0.1">
- <meta charset="utf-8" />
- <meta name="viewport" content="width=device-width, initial-scale=1" />
-</head>
-<body>
- <main class="container">
- <div class="form-container">
- <div class="logo">
- <span>do</span><span>do:</span>
- </div>
- <form action="" method="POST">
- <label>
- username
- <input type="text" name="username" aria-label="Username" value="{{ .Data.Username }}" aria-invalid="{{ if .UsernameErrors }}true{{ else }}undefined{{ end }}" required/>
- </label>
- {{ if .UsernameErrors }}
- {{ range .UsernameErrors }}
- <small class="error-message" aria-live="assertive">
- {{ . }}
- </small>
- {{ end }}
- {{ end }}
- <label>
- password
- <input type="password" name="password" aria-label="Password" value="{{ .Data.Password }}" aria-invalid="{{ if .PasswordErrors }}true{{ else }}undefined{{ end }}" required/>
- </label>
- {{ if .PasswordErrors }}
- {{ range .PasswordErrors }}
- <small class="error-message" aria-live="assertive">
- {{ . }}
- </small>
- {{ end }}
- {{ end }}
- <label>
- secret token
- <input type="text" name="secret-token" aria-label="Secret Token" value="{{ .Data.SecretToken }}" required/>
- </label>
- <button type="submit">create account</button>
- </form>
- </div>
- </main>
-</body>
-</html>
diff --git a/core/installer/welcome/dodo_app.go b/core/installer/welcome/dodo_app.go
index d71bf83..256007e 100644
--- a/core/installer/welcome/dodo_app.go
+++ b/core/installer/welcome/dodo_app.go
@@ -316,7 +316,7 @@
}
func (ug internalUserGetter) Get(r *http.Request) string {
- return r.Header.Get("X-User")
+ return r.Header.Get("X-Forwarded-User")
}
func (ug internalUserGetter) Encode(w http.ResponseWriter, user string) error {
diff --git a/core/installer/welcome/launcher-tmpl/launcher.html b/core/installer/welcome/launcher-tmpl/launcher.html
index e75a616..76f7dd5 100644
--- a/core/installer/welcome/launcher-tmpl/launcher.html
+++ b/core/installer/welcome/launcher-tmpl/launcher.html
@@ -5,7 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>dodo: Launcher</title>
<link rel="stylesheet" type="text/css" href="/stat/pico.2.0.6.min.css">
- <link rel="stylesheet" type="text/css" href="/stat/launcher.css?v=0.0.20">
+ <link rel="stylesheet" type="text/css" href="/stat/launcher.css?v=0.0.21">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/hack-font/3.3.0/web/hack.min.css">
</head>
<body class="container-fluid">
@@ -15,7 +15,8 @@
<p id="user-initial">{{ GetUserInitials .LoggedInUsername }}</p>
<div class="tooltip-user" id="tooltip-user">
<p>{{ .LoggedInUsername }}</p>
- <a href="{{ .LogoutURL }}" role="button" id="logout-button">Log Out</a>
+ <a href="{{ .AuthBaseAddr }}/change-password" role="button" class="profile-button" target="_blank">change password</a>
+ <a href="{{ .AuthBaseAddr }}/logout" role="button" class="profile-button">log out</a>
</div>
</div>
</div>
diff --git a/core/installer/welcome/launcher.go b/core/installer/welcome/launcher.go
index 88047d8..bab9163 100644
--- a/core/installer/welcome/launcher.go
+++ b/core/installer/welcome/launcher.go
@@ -80,14 +80,14 @@
type LauncherServer struct {
port int
- logoutURL string
+ authBaseAddr string
appDirectory AppDirectory
homeTmpl *template.Template
}
func NewLauncherServer(
port int,
- logoutURL string,
+ authBaseAddr string,
appDirectory AppDirectory,
) (*LauncherServer, error) {
tmpl, err := indexHTML.ReadFile("launcher-tmpl/launcher.html")
@@ -104,7 +104,7 @@
}
return &LauncherServer{
port,
- logoutURL,
+ authBaseAddr,
appDirectory,
t,
}, nil
@@ -128,7 +128,7 @@
}
func getLoggedInUser(r *http.Request) (string, error) {
- if user := r.Header.Get("X-User"); user != "" {
+ if user := r.Header.Get("X-Forwarded-User"); user != "" {
return user, nil
} else {
return "", fmt.Errorf("unauthenticated")
@@ -145,7 +145,7 @@
type homeHandlerData struct {
LoggedInUsername string
AllAppsInfo []AppLauncherInfo
- LogoutURL string
+ AuthBaseAddr string
}
func (s *LauncherServer) homeHandler(w http.ResponseWriter, r *http.Request) {
@@ -161,7 +161,7 @@
data := homeHandlerData{
LoggedInUsername: loggedInUsername,
AllAppsInfo: allAppsInfo,
- LogoutURL: s.logoutURL,
+ AuthBaseAddr: s.authBaseAddr,
}
if err := s.homeTmpl.Execute(w, data); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
diff --git a/core/installer/welcome/stat/hi.txt b/core/installer/welcome/stat/hi.txt
deleted file mode 100644
index 45b983b..0000000
--- a/core/installer/welcome/stat/hi.txt
+++ /dev/null
@@ -1 +0,0 @@
-hi
diff --git a/core/installer/welcome/stat/launcher.css b/core/installer/welcome/stat/launcher.css
index c3a49bf..636c0c0 100644
--- a/core/installer/welcome/stat/launcher.css
+++ b/core/installer/welcome/stat/launcher.css
@@ -338,9 +338,8 @@
.tooltip-user {
position: absolute;
- top: 38.7px;
+ top: 0px;
left: 80px;
- transform: translateY(-50%);
width: 234px;
background-color: var(--bodyBg);
padding: 5px;
@@ -354,7 +353,7 @@
box-shadow: 2px 2px 5px var(--bodyBg);
}
-#logout-button {
+.profile-button {
margin-top: 5px !important;
padding: 0 !important;
border: 0 !important;
diff --git a/core/installer/welcome/create-account-success.html b/core/installer/welcome/welcome-tmpl/base.html
similarity index 73%
rename from core/installer/welcome/create-account-success.html
rename to core/installer/welcome/welcome-tmpl/base.html
index 86563c6..44861de 100644
--- a/core/installer/welcome/create-account-success.html
+++ b/core/installer/welcome/welcome-tmpl/base.html
@@ -10,10 +10,7 @@
</head>
<body>
<main class="container">
- <div>
- <p>Your account has been successfully created.</p>
- <p>Click <a href="{{ .LoginAddr }}">here</a> to open up the Launcher.</p>
- </div>
+ {{ block "content" . }}{{ end }}
</main>
</body>
</html>
diff --git a/core/installer/welcome/welcome-tmpl/create-account-success.html b/core/installer/welcome/welcome-tmpl/create-account-success.html
new file mode 100644
index 0000000..8de510e
--- /dev/null
+++ b/core/installer/welcome/welcome-tmpl/create-account-success.html
@@ -0,0 +1,6 @@
+{{ define "content" }}
+<div>
+ <p>Your account has been successfully created.</p>
+ <p>Click <a href="{{ .LoginAddr }}">here</a> to open up the Launcher.</p>
+</div>
+{{ end }}
diff --git a/core/installer/welcome/welcome-tmpl/create-account.html b/core/installer/welcome/welcome-tmpl/create-account.html
new file mode 100644
index 0000000..ecd547f
--- /dev/null
+++ b/core/installer/welcome/welcome-tmpl/create-account.html
@@ -0,0 +1,36 @@
+{{ define "content" }}
+<div class="form-container">
+ <div class="logo">
+ <span>do</span><span>do:</span>
+ </div>
+ <form action="" method="POST">
+ <label>
+ username
+ <input type="text" name="username" aria-label="Username" value="{{ .Data.Username }}" aria-invalid="{{ if .UsernameErrors }}true{{ else }}undefined{{ end }}" required/>
+ </label>
+ {{ if .UsernameErrors }}
+ {{ range .UsernameErrors }}
+ <small class="error-message" aria-live="assertive">
+ {{ . }}
+ </small>
+ {{ end }}
+ {{ end }}
+ <label>
+ password
+ <input type="password" name="password" aria-label="Password" value="{{ .Data.Password }}" aria-invalid="{{ if .PasswordErrors }}true{{ else }}undefined{{ end }}" required/>
+ </label>
+ {{ if .PasswordErrors }}
+ {{ range .PasswordErrors }}
+ <small class="error-message" aria-live="assertive">
+ {{ . }}
+ </small>
+ {{ end }}
+ {{ end }}
+ <label>
+ secret token
+ <input type="text" name="secret-token" aria-label="Secret Token" value="{{ .Data.SecretToken }}" required/>
+ </label>
+ <button type="submit">create account</button>
+ </form>
+</div>
+{{ end }}
diff --git a/core/installer/welcome/welcome.go b/core/installer/welcome/welcome.go
index b6436c7..7aa6d3a 100644
--- a/core/installer/welcome/welcome.go
+++ b/core/installer/welcome/welcome.go
@@ -18,11 +18,8 @@
"github.com/giolekva/pcloud/core/installer/soft"
)
-//go:embed create-account.html
-var indexHtml []byte
-
-//go:embed create-account-success.html
-var successHtml []byte
+//go:embed welcome-tmpl/*
+var welcomeTmpls embed.FS
//go:embed static/*
var staticAssets embed.FS
@@ -30,6 +27,34 @@
//go:embed stat/*
var statAssets embed.FS
+type welcomeTmplts struct {
+ createAccount *template.Template
+ createAccountSuccess *template.Template
+}
+
+func parseTemplatesWelcome(fs embed.FS) (welcomeTmplts, error) {
+ base, err := template.New("base.html").ParseFS(fs, "welcome-tmpl/base.html")
+ if err != nil {
+ return welcomeTmplts{}, err
+ }
+ parse := func(path string) (*template.Template, error) {
+ if b, err := base.Clone(); err != nil {
+ return nil, err
+ } else {
+ return b.ParseFS(fs, path)
+ }
+ }
+ createAccount, err := parse("welcome-tmpl/create-account.html")
+ if err != nil {
+ return welcomeTmplts{}, err
+ }
+ createAccountSuccess, err := parse("welcome-tmpl/create-account-success.html")
+ if err != nil {
+ return welcomeTmplts{}, err
+ }
+ return welcomeTmplts{createAccount, createAccountSuccess}, nil
+}
+
type Server struct {
port int
repo soft.RepoIO
@@ -38,6 +63,7 @@
createAccountAddr string
loginAddr string
membershipsAddr string
+ tmpl welcomeTmplts
}
func NewServer(
@@ -48,7 +74,11 @@
createAccountAddr string,
loginAddr string,
membershipsAddr string,
-) *Server {
+) (*Server, error) {
+ tmplts, err := parseTemplatesWelcome(welcomeTmpls)
+ if err != nil {
+ return nil, err
+ }
return &Server{
port,
repo,
@@ -57,7 +87,8 @@
createAccountAddr,
loginAddr,
membershipsAddr,
- }
+ tmplts,
+ }, nil
}
func (s *Server) Start() {
@@ -70,7 +101,7 @@
}
func (s *Server) createAccountForm(w http.ResponseWriter, r *http.Request) {
- renderRegistrationForm(w, formData{})
+ s.renderRegistrationForm(w, formData{})
}
type formData struct {
@@ -79,6 +110,12 @@
Data createAccountReq
}
+type cpFormData struct {
+ UsernameErrors []string
+ PasswordErrors []string
+ Password string
+}
+
type createAccountReq struct {
Username string `json:"username,omitempty"`
Password string `json:"password,omitempty"`
@@ -132,30 +169,20 @@
return req, nil
}
-func renderRegistrationForm(w http.ResponseWriter, data formData) {
- tmpl, err := template.New("create-account").Parse(string(indexHtml))
- if err != nil {
- http.Error(w, err.Error(), http.StatusInternalServerError)
- return
- }
- if err := tmpl.Execute(w, data); err != nil {
+func (s *Server) renderRegistrationForm(w http.ResponseWriter, data formData) {
+ if err := s.tmpl.createAccount.Execute(w, data); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
-func renderRegistrationSuccess(w http.ResponseWriter, loginAddr string) {
+func (s *Server) renderRegistrationSuccess(w http.ResponseWriter, loginAddr string) {
data := struct {
LoginAddr string
}{
LoginAddr: loginAddr,
}
- tmpl, err := template.New("create-account-success").Parse(string(successHtml))
- if err != nil {
- http.Error(w, err.Error(), http.StatusInternalServerError)
- return
- }
- if err := tmpl.Execute(w, data); err != nil {
+ if err := s.tmpl.createAccountSuccess.Execute(w, data); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
@@ -200,7 +227,7 @@
passwordErrors = append(passwordErrors, err.Message)
}
}
- renderRegistrationForm(w, formData{
+ s.renderRegistrationForm(w, formData{
usernameErrors,
passwordErrors,
req,
@@ -212,7 +239,7 @@
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
- renderRegistrationSuccess(w, s.loginAddr)
+ s.renderRegistrationSuccess(w, s.loginAddr)
}
type firstAccount struct {