Auth: implement consent logic
diff --git a/core/auth/ui/main.go b/core/auth/ui/main.go
index 92ca885..a9b349d 100644
--- a/core/auth/ui/main.go
+++ b/core/auth/ui/main.go
@@ -2,7 +2,6 @@
 
 import (
 	"bytes"
-	"crypto/tls"
 	"embed"
 	"encoding/json"
 	"errors"
@@ -15,7 +14,6 @@
 	"net/http"
 	"net/http/cookiejar"
 	"net/url"
-	"strings"
 
 	"github.com/gorilla/mux"
 	"github.com/itaysk/regogo"
@@ -23,6 +21,7 @@
 
 var port = flag.Int("port", 8080, "Port to listen on")
 var kratos = flag.String("kratos", "https://accounts.lekva.me", "Kratos URL")
+var hydra = flag.String("hydra", "hydra.pcloud", "Hydra admin server address")
 
 var ErrNotLoggedIn = errors.New("Not logged in")
 
@@ -33,9 +32,14 @@
 	WhoAmI       *template.Template
 	Registration *template.Template
 	Login        *template.Template
+	Consent      *template.Template
 }
 
 func ParseTemplates(fs embed.FS) (*Templates, error) {
+	whoami, err := template.ParseFS(fs, "templates/whoami.html")
+	if err != nil {
+		return nil, err
+	}
 	registration, err := template.ParseFS(fs, "templates/registration.html")
 	if err != nil {
 		return nil, err
@@ -44,15 +48,16 @@
 	if err != nil {
 		return nil, err
 	}
-	whoami, err := template.ParseFS(fs, "templates/whoami.html")
+	consent, err := template.ParseFS(fs, "templates/consent.html")
 	if err != nil {
 		return nil, err
 	}
-	return &Templates{whoami, registration, login}, nil
+	return &Templates{whoami, registration, login, consent}, nil
 }
 
 type Server struct {
 	kratos string
+	hydra  *HydraClient
 	tmpls  *Templates
 }
 
@@ -63,6 +68,8 @@
 	r.Path("/registration").Methods(http.MethodPost).HandlerFunc(s.registration)
 	r.Path("/login").Methods(http.MethodGet).HandlerFunc(s.loginInitiate)
 	r.Path("/login").Methods(http.MethodPost).HandlerFunc(s.login)
+	r.Path("/consent").Methods(http.MethodGet).HandlerFunc(s.consent)
+	r.Path("/consent").Methods(http.MethodPost).HandlerFunc(s.processConsent)
 	r.Path("/logout").Methods(http.MethodGet).HandlerFunc(s.logout)
 	r.Path("/").HandlerFunc(s.whoami)
 	fmt.Printf("Starting HTTP server on port: %d\n", port)
@@ -112,7 +119,6 @@
 		http.Error(w, err.Error(), http.StatusInternalServerError)
 		return
 	}
-	log.Println(csrfToken)
 	w.Header().Set("Content-Type", "text/html")
 	if err := s.tmpls.Registration.Execute(w, csrfToken); err != nil {
 		http.Error(w, err.Error(), http.StatusInternalServerError)
@@ -172,17 +178,23 @@
 		http.Error(w, err.Error(), http.StatusInternalServerError)
 		return
 	}
+	if challenge, ok := r.Form["login_challenge"]; ok {
+		// TODO(giolekva): encrypt
+		http.SetCookie(w, &http.Cookie{
+			Name:     "login_challenge",
+			Value:    challenge[0],
+			HttpOnly: true,
+		})
+	} else {
+		// http.SetCookie(w, &http.Cookie{
+		// 	Name:     "login_challenge",
+		// 	Value:    "",
+		// 	Expires:  time.Unix(0, 0),
+		// 	HttpOnly: true,
+		// })
+	}
 	flow, ok := r.Form["flow"]
 	if !ok {
-		challenge, ok := r.Form["login_challenge"]
-		if ok {
-			// TODO(giolekva): encrypt
-			http.SetCookie(w, &http.Cookie{
-				Name:     "login_challenge",
-				Value:    challenge[0],
-				HttpOnly: true,
-			})
-		}
 		http.Redirect(w, r, s.kratos+"/self-service/login/browser", http.StatusSeeOther)
 		return
 	}
@@ -191,7 +203,6 @@
 		http.Error(w, err.Error(), http.StatusInternalServerError)
 		return
 	}
-	log.Println(csrfToken)
 	w.Header().Set("Content-Type", "text/html")
 	if err := s.tmpls.Login.Execute(w, csrfToken); err != nil {
 		http.Error(w, err.Error(), http.StatusInternalServerError)
@@ -286,6 +297,25 @@
 
 }
 
+func extractError(r io.Reader) error {
+	respBody, err := ioutil.ReadAll(r)
+	if err != nil {
+		return err
+	}
+	t, err := regogo.Get(string(respBody), "input.ui.messages[0].type")
+	if err != nil {
+		return err
+	}
+	if t.String() == "error" {
+		message, err := regogo.Get(string(respBody), "input.ui.messages[0].text")
+		if err != nil {
+			return err
+		}
+		return errors.New(message.String())
+	}
+	return nil
+}
+
 func (s *Server) login(w http.ResponseWriter, r *http.Request) {
 	if err := r.ParseForm(); err != nil {
 		http.Error(w, err.Error(), http.StatusInternalServerError)
@@ -307,52 +337,41 @@
 		http.Error(w, err.Error(), http.StatusInternalServerError)
 		return
 	}
-	if resp, err := postToKratos("login", flow[0], r.Cookies(), &reqBody); err != nil {
-		http.Error(w, err.Error(), http.StatusInternalServerError)
-		return
-	} else {
-		for _, c := range resp.Cookies() {
-			http.SetCookie(w, c)
-		}
+	resp, err := postToKratos("login", flow[0], r.Cookies(), &reqBody)
+	if err == nil {
+		err = extractError(resp.Body)
+	}
+	if err != nil {
 		if challenge, _ := r.Cookie("login_challenge"); challenge != nil {
-			username, err := getWhoAmIFromKratos(resp.Cookies())
+			redirectTo, err := s.hydra.LoginRejectChallenge(challenge.Value, err.Error())
 			if err != nil {
 				http.Error(w, err.Error(), http.StatusInternalServerError)
 				return
 			}
-			req := &http.Request{
-				Method: http.MethodPut,
-				URL: &url.URL{
-					Scheme:   "https",
-					Host:     "hydra.pcloud",
-					Path:     "/oauth2/auth/requests/login/accept",
-					RawQuery: fmt.Sprintf("login_challenge=%s", challenge.Value),
-				},
-				Header: map[string][]string{
-					"Content-Type": []string{"text/html"},
-				},
-				// TODO(giolekva): user stable userid instead
-				Body: io.NopCloser(strings.NewReader(fmt.Sprintf(`
-{
-    "subject": "%s",
-    "remember": true,
-    "remember_for": 3600
-}`, username))),
-			}
-			client := &http.Client{
-				Transport: &http.Transport{
-					TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
-				},
-			}
-			resp, err := client.Do(req)
-			if err != nil {
-				http.Error(w, err.Error(), http.StatusInternalServerError)
-			} else {
-				io.Copy(w, resp.Body)
-			}
+			http.Redirect(w, r, redirectTo, http.StatusSeeOther)
+			return
 		}
-		// http.Redirect(w, r, "/", http.StatusSeeOther)
+		http.Error(w, err.Error(), http.StatusInternalServerError)
+		return
 	}
+	for _, c := range resp.Cookies() {
+		http.SetCookie(w, c)
+	}
+	if challenge, _ := r.Cookie("login_challenge"); challenge != nil {
+		username, err := getWhoAmIFromKratos(resp.Cookies())
+		if err != nil {
+			http.Error(w, err.Error(), http.StatusInternalServerError)
+			return
+		}
+		redirectTo, err := s.hydra.LoginAcceptChallenge(challenge.Value, username)
+		if err != nil {
+			http.Error(w, err.Error(), http.StatusInternalServerError)
+			return
+		}
+		http.Redirect(w, r, redirectTo, http.StatusSeeOther)
+		return
+	}
+	http.Redirect(w, r, "/", http.StatusSeeOther)
 }
 
 func (s *Server) logout(w http.ResponseWriter, r *http.Request) {
@@ -378,6 +397,56 @@
 	}
 }
 
+// TODO(giolekva): verify if logged in
+func (s *Server) consent(w http.ResponseWriter, r *http.Request) {
+	if err := r.ParseForm(); err != nil {
+		http.Error(w, err.Error(), http.StatusBadRequest)
+		return
+	}
+	challenge, ok := r.Form["consent_challenge"]
+	if !ok {
+		http.Error(w, "Consent challenge not provided", http.StatusBadRequest)
+		return
+	}
+	consent, err := s.hydra.GetConsentChallenge(challenge[0])
+	if err != nil {
+		http.Error(w, err.Error(), http.StatusInternalServerError)
+		return
+	}
+	w.Header().Set("Content-Type", "text/html")
+	if err := s.tmpls.Consent.Execute(w, consent.RequestedScopes); err != nil {
+		http.Error(w, err.Error(), http.StatusInternalServerError)
+		return
+	}
+}
+
+func (s *Server) processConsent(w http.ResponseWriter, r *http.Request) {
+	if err := r.ParseForm(); err != nil {
+		http.Error(w, err.Error(), http.StatusBadRequest)
+		return
+	}
+	username, err := getWhoAmIFromKratos(r.Cookies())
+	if err != nil {
+		http.Error(w, err.Error(), http.StatusInternalServerError)
+		return
+	}
+	if _, accepted := r.Form["allow"]; accepted {
+		acceptedScopes, _ := r.Form["scope"]
+		idToken := map[string]string{
+			"username": username,
+			"email":    username + "@lekva.me",
+		}
+		if redirectTo, err := s.hydra.ConsentAccept(r.FormValue("consent_challenge"), acceptedScopes, idToken); err != nil {
+			http.Error(w, err.Error(), http.StatusInternalServerError)
+		} else {
+			http.Redirect(w, r, redirectTo, http.StatusSeeOther)
+		}
+		return
+	} else {
+		// TODO(giolekva): implement rejection logic
+	}
+}
+
 func main() {
 	flag.Parse()
 	t, err := ParseTemplates(tmpls)
@@ -386,6 +455,7 @@
 	}
 	s := &Server{
 		kratos: *kratos,
+		hydra:  NewHydraClient(*hydra),
 		tmpls:  t,
 	}
 	log.Fatal(s.Start(*port))