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/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)
+}