Auth: implement consent logic
diff --git a/core/auth/ui/hydra.go b/core/auth/ui/hydra.go
new file mode 100644
index 0000000..8a0dea0
--- /dev/null
+++ b/core/auth/ui/hydra.go
@@ -0,0 +1,162 @@
+package main
+
+import (
+ "bytes"
+ "crypto/tls"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "io"
+ "net/http"
+ "net/url"
+ "strings"
+)
+
+type HydraClient struct {
+ httpClient *http.Client
+ host string
+}
+
+func NewHydraClient(host string) *HydraClient {
+ return &HydraClient{
+ // TODO(giolekva): trust selfsigned-root-ca automatically on pods
+ &http.Client{
+ Transport: &http.Transport{
+ TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
+ },
+ },
+ host,
+ }
+}
+
+type loginResp struct {
+ RedirectTo string `json:"redirect_to"`
+ Error string `json:"error"`
+ ErrorDebug string `json:"error_debug"`
+ ErrorDescription string `json:"error_description"`
+ StatusCode int `json:"status_code"`
+}
+
+func (c *HydraClient) LoginAcceptChallenge(challenge, subject string) (string, error) {
+ req := &http.Request{
+ Method: http.MethodPut,
+ URL: &url.URL{
+ Scheme: "https",
+ Host: c.host,
+ Path: "/oauth2/auth/requests/login/accept",
+ RawQuery: fmt.Sprintf("login_challenge=%s", challenge),
+ },
+ Header: map[string][]string{
+ "Content-Type": []string{"application/json"},
+ },
+ // TODO(giolekva): user stable userid instead
+ Body: io.NopCloser(strings.NewReader(fmt.Sprintf(`
+{
+ "subject": "%s",
+ "remember": true,
+ "remember_for": 3600
+}`, subject))),
+ }
+ resp, err := c.httpClient.Do(req)
+ if err != nil {
+ return "", err
+ }
+ var r loginResp
+ if err := json.NewDecoder(resp.Body).Decode(&r); err != nil {
+ return "", err
+ }
+ if r.Error != "" {
+ return "", errors.New(r.Error)
+ }
+ return r.RedirectTo, nil
+}
+
+func (c *HydraClient) LoginRejectChallenge(challenge, message string) (string, error) {
+ req := &http.Request{
+ Method: http.MethodPut,
+ URL: &url.URL{
+ Scheme: "https",
+ Host: c.host,
+ Path: "/oauth2/auth/requests/login/reject",
+ RawQuery: fmt.Sprintf("login_challenge=%s", challenge),
+ },
+ Header: map[string][]string{
+ "Content-Type": []string{"application/json"},
+ },
+ Body: io.NopCloser(strings.NewReader(fmt.Sprintf(`
+{
+ "error": "login_required %s"
+}`, message))),
+ }
+ resp, err := c.httpClient.Do(req)
+ if err != nil {
+ return "", err
+ }
+ var r loginResp
+ if err := json.NewDecoder(resp.Body).Decode(&r); err != nil {
+ return "", err
+ }
+ if r.Error != "" {
+ return "", errors.New(r.Error)
+ }
+ return r.RedirectTo, nil
+}
+
+type RequestedConsent struct {
+ Challenge string `json:"challenge"`
+ Subject string `json:"subject"`
+ RequestedScopes []string `json:"requested_scope"`
+}
+
+func (c *HydraClient) GetConsentChallenge(challenge string) (RequestedConsent, error) {
+ var consent RequestedConsent
+ resp, err := c.httpClient.Get(fmt.Sprintf("https://%s/oauth2/auth/requests/consent?consent_challenge=%s", c.host, challenge))
+ if err != nil {
+ return consent, err
+ }
+ err = json.NewDecoder(resp.Body).Decode(&consent)
+ return consent, err
+}
+
+type consentAcceptReq struct {
+ GrantScope []string `json:"grant_scope"`
+ Session session `json:"session"`
+}
+
+type session struct {
+ IDToken map[string]string `json:"id_token"`
+}
+
+type consentAcceptResp struct {
+ RedirectTo string `json:"redirect_to"`
+}
+
+func (c *HydraClient) ConsentAccept(challenge string, scopes []string, idToken map[string]string) (string, error) {
+ accept := consentAcceptReq{scopes, session{idToken}}
+ var data bytes.Buffer
+ if err := json.NewEncoder(&data).Encode(accept); err != nil {
+ return "", err
+ }
+ req := &http.Request{
+ Method: http.MethodPut,
+ URL: &url.URL{
+ Scheme: "https",
+ Host: c.host,
+ Path: "/oauth2/auth/requests/consent/accept",
+ RawQuery: fmt.Sprintf("challenge=%s", challenge),
+ },
+ Header: map[string][]string{
+ "Content-Type": []string{"application/json"},
+ },
+ Body: io.NopCloser(&data),
+ }
+ resp, err := c.httpClient.Do(req)
+ if err != nil {
+ return "", err
+ }
+ var r consentAcceptResp
+ if err := json.NewDecoder(resp.Body).Decode(&r); err != nil {
+ return "", err
+ }
+ return r.RedirectTo, err
+}