blob: 8a0dea007dfc45c1a86fd5941da0e9d1c43e27e1 [file] [log] [blame]
giolekva788dc6e2021-10-25 20:40:53 +04001package main
2
3import (
4 "bytes"
5 "crypto/tls"
6 "encoding/json"
7 "errors"
8 "fmt"
9 "io"
10 "net/http"
11 "net/url"
12 "strings"
13)
14
15type HydraClient struct {
16 httpClient *http.Client
17 host string
18}
19
20func 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
32type 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
40func (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
74func (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
105type RequestedConsent struct {
106 Challenge string `json:"challenge"`
107 Subject string `json:"subject"`
108 RequestedScopes []string `json:"requested_scope"`
109}
110
111func (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
121type consentAcceptReq struct {
122 GrantScope []string `json:"grant_scope"`
123 Session session `json:"session"`
124}
125
126type session struct {
127 IDToken map[string]string `json:"id_token"`
128}
129
130type consentAcceptResp struct {
131 RedirectTo string `json:"redirect_to"`
132}
133
134func (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}