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/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 {