| giolekva | 788dc6e | 2021-10-25 20:40:53 +0400 | [diff] [blame^] | 1 | package main |
| 2 | |
| 3 | import ( |
| 4 | "bytes" |
| 5 | "crypto/tls" |
| 6 | "encoding/json" |
| 7 | "errors" |
| 8 | "fmt" |
| 9 | "io" |
| 10 | "net/http" |
| 11 | "net/url" |
| 12 | "strings" |
| 13 | ) |
| 14 | |
| 15 | type HydraClient struct { |
| 16 | httpClient *http.Client |
| 17 | host string |
| 18 | } |
| 19 | |
| 20 | func NewHydraClient(host string) *HydraClient { |
| 21 | return &HydraClient{ |
| 22 | // TODO(giolekva): trust selfsigned-root-ca automatically on pods |
| 23 | &http.Client{ |
| 24 | Transport: &http.Transport{ |
| 25 | TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, |
| 26 | }, |
| 27 | }, |
| 28 | host, |
| 29 | } |
| 30 | } |
| 31 | |
| 32 | type loginResp struct { |
| 33 | RedirectTo string `json:"redirect_to"` |
| 34 | Error string `json:"error"` |
| 35 | ErrorDebug string `json:"error_debug"` |
| 36 | ErrorDescription string `json:"error_description"` |
| 37 | StatusCode int `json:"status_code"` |
| 38 | } |
| 39 | |
| 40 | func (c *HydraClient) LoginAcceptChallenge(challenge, subject string) (string, error) { |
| 41 | req := &http.Request{ |
| 42 | Method: http.MethodPut, |
| 43 | URL: &url.URL{ |
| 44 | Scheme: "https", |
| 45 | Host: c.host, |
| 46 | Path: "/oauth2/auth/requests/login/accept", |
| 47 | RawQuery: fmt.Sprintf("login_challenge=%s", challenge), |
| 48 | }, |
| 49 | Header: map[string][]string{ |
| 50 | "Content-Type": []string{"application/json"}, |
| 51 | }, |
| 52 | // TODO(giolekva): user stable userid instead |
| 53 | Body: io.NopCloser(strings.NewReader(fmt.Sprintf(` |
| 54 | { |
| 55 | "subject": "%s", |
| 56 | "remember": true, |
| 57 | "remember_for": 3600 |
| 58 | }`, subject))), |
| 59 | } |
| 60 | resp, err := c.httpClient.Do(req) |
| 61 | if err != nil { |
| 62 | return "", err |
| 63 | } |
| 64 | var r loginResp |
| 65 | if err := json.NewDecoder(resp.Body).Decode(&r); err != nil { |
| 66 | return "", err |
| 67 | } |
| 68 | if r.Error != "" { |
| 69 | return "", errors.New(r.Error) |
| 70 | } |
| 71 | return r.RedirectTo, nil |
| 72 | } |
| 73 | |
| 74 | func (c *HydraClient) LoginRejectChallenge(challenge, message string) (string, error) { |
| 75 | req := &http.Request{ |
| 76 | Method: http.MethodPut, |
| 77 | URL: &url.URL{ |
| 78 | Scheme: "https", |
| 79 | Host: c.host, |
| 80 | Path: "/oauth2/auth/requests/login/reject", |
| 81 | RawQuery: fmt.Sprintf("login_challenge=%s", challenge), |
| 82 | }, |
| 83 | Header: map[string][]string{ |
| 84 | "Content-Type": []string{"application/json"}, |
| 85 | }, |
| 86 | Body: io.NopCloser(strings.NewReader(fmt.Sprintf(` |
| 87 | { |
| 88 | "error": "login_required %s" |
| 89 | }`, message))), |
| 90 | } |
| 91 | resp, err := c.httpClient.Do(req) |
| 92 | if err != nil { |
| 93 | return "", err |
| 94 | } |
| 95 | var r loginResp |
| 96 | if err := json.NewDecoder(resp.Body).Decode(&r); err != nil { |
| 97 | return "", err |
| 98 | } |
| 99 | if r.Error != "" { |
| 100 | return "", errors.New(r.Error) |
| 101 | } |
| 102 | return r.RedirectTo, nil |
| 103 | } |
| 104 | |
| 105 | type RequestedConsent struct { |
| 106 | Challenge string `json:"challenge"` |
| 107 | Subject string `json:"subject"` |
| 108 | RequestedScopes []string `json:"requested_scope"` |
| 109 | } |
| 110 | |
| 111 | func (c *HydraClient) GetConsentChallenge(challenge string) (RequestedConsent, error) { |
| 112 | var consent RequestedConsent |
| 113 | resp, err := c.httpClient.Get(fmt.Sprintf("https://%s/oauth2/auth/requests/consent?consent_challenge=%s", c.host, challenge)) |
| 114 | if err != nil { |
| 115 | return consent, err |
| 116 | } |
| 117 | err = json.NewDecoder(resp.Body).Decode(&consent) |
| 118 | return consent, err |
| 119 | } |
| 120 | |
| 121 | type consentAcceptReq struct { |
| 122 | GrantScope []string `json:"grant_scope"` |
| 123 | Session session `json:"session"` |
| 124 | } |
| 125 | |
| 126 | type session struct { |
| 127 | IDToken map[string]string `json:"id_token"` |
| 128 | } |
| 129 | |
| 130 | type consentAcceptResp struct { |
| 131 | RedirectTo string `json:"redirect_to"` |
| 132 | } |
| 133 | |
| 134 | func (c *HydraClient) ConsentAccept(challenge string, scopes []string, idToken map[string]string) (string, error) { |
| 135 | accept := consentAcceptReq{scopes, session{idToken}} |
| 136 | var data bytes.Buffer |
| 137 | if err := json.NewEncoder(&data).Encode(accept); err != nil { |
| 138 | return "", err |
| 139 | } |
| 140 | req := &http.Request{ |
| 141 | Method: http.MethodPut, |
| 142 | URL: &url.URL{ |
| 143 | Scheme: "https", |
| 144 | Host: c.host, |
| 145 | Path: "/oauth2/auth/requests/consent/accept", |
| 146 | RawQuery: fmt.Sprintf("challenge=%s", challenge), |
| 147 | }, |
| 148 | Header: map[string][]string{ |
| 149 | "Content-Type": []string{"application/json"}, |
| 150 | }, |
| 151 | Body: io.NopCloser(&data), |
| 152 | } |
| 153 | resp, err := c.httpClient.Do(req) |
| 154 | if err != nil { |
| 155 | return "", err |
| 156 | } |
| 157 | var r consentAcceptResp |
| 158 | if err := json.NewDecoder(resp.Body).Decode(&r); err != nil { |
| 159 | return "", err |
| 160 | } |
| 161 | return r.RedirectTo, err |
| 162 | } |