blob: 73561e71229325e20c514ea296d70111478a808c [file] [log] [blame]
giolekva603e73a2021-10-22 14:46:45 +04001package main
2
3import (
4 "bytes"
giolekvadd750802021-11-07 13:24:21 +04005 "crypto/tls"
giolekva603e73a2021-10-22 14:46:45 +04006 "embed"
7 "encoding/json"
8 "errors"
9 "flag"
10 "fmt"
11 "html/template"
12 "io"
13 "io/ioutil"
14 "log"
15 "net/http"
16 "net/http/cookiejar"
17 "net/url"
18
19 "github.com/gorilla/mux"
20 "github.com/itaysk/regogo"
21)
22
23var port = flag.Int("port", 8080, "Port to listen on")
24var kratos = flag.String("kratos", "https://accounts.lekva.me", "Kratos URL")
giolekva788dc6e2021-10-25 20:40:53 +040025var hydra = flag.String("hydra", "hydra.pcloud", "Hydra admin server address")
giolekvadd750802021-11-07 13:24:21 +040026var emailDomain = flag.String("email-domain", "lekva.me", "Email domain")
Giorgi Lekveishvilifedd0062023-12-21 10:52:49 +040027var apiPort = flag.Int("api-port", 8081, "API Port to listen on")
28var kratosAPI = flag.String("kratos-api", "", "Kratos API address")
Giorgi Lekveishvilid76414e2023-12-21 13:30:23 +040029var enableRegistration = flag.Bool("enable-registration", false, "If true account registration will be enabled")
giodd213152024-09-27 11:26:59 +020030var defaultReturnTo = flag.String("default-return-to", "", "Default redirect address after login")
Giorgi Lekveishvilid76414e2023-12-21 13:30:23 +040031
giolekva603e73a2021-10-22 14:46:45 +040032var ErrNotLoggedIn = errors.New("Not logged in")
33
34//go:embed templates/*
35var tmpls embed.FS
36
giolekva47031752021-11-12 14:34:33 +040037//go:embed static
38var static embed.FS
39
giolekva603e73a2021-10-22 14:46:45 +040040type Templates struct {
giodd213152024-09-27 11:26:59 +020041 WhoAmI *template.Template
42 Register *template.Template
43 Login *template.Template
44 Consent *template.Template
45 ChangePassword *template.Template
46 ChangePasswordSuccess *template.Template
giolekva603e73a2021-10-22 14:46:45 +040047}
48
49func ParseTemplates(fs embed.FS) (*Templates, error) {
Giorgi Lekveishvili58cb1482023-12-04 12:33:49 +040050 base, err := template.ParseFS(fs, "templates/base.html")
giolekva788dc6e2021-10-25 20:40:53 +040051 if err != nil {
52 return nil, err
53 }
Giorgi Lekveishvili58cb1482023-12-04 12:33:49 +040054 parse := func(path string) (*template.Template, error) {
55 if b, err := base.Clone(); err != nil {
56 return nil, err
57 } else {
58 return b.ParseFS(fs, path)
59 }
60 }
61 whoami, err := parse("templates/whoami.html")
giolekva603e73a2021-10-22 14:46:45 +040062 if err != nil {
63 return nil, err
64 }
Giorgi Lekveishvili58cb1482023-12-04 12:33:49 +040065 register, err := parse("templates/register.html")
giolekva603e73a2021-10-22 14:46:45 +040066 if err != nil {
67 return nil, err
68 }
Giorgi Lekveishvili58cb1482023-12-04 12:33:49 +040069 login, err := parse("templates/login.html")
giolekva603e73a2021-10-22 14:46:45 +040070 if err != nil {
71 return nil, err
72 }
Giorgi Lekveishvili58cb1482023-12-04 12:33:49 +040073 consent, err := parse("templates/consent.html")
74 if err != nil {
75 return nil, err
76 }
giodd213152024-09-27 11:26:59 +020077 changePassword, err := parse("templates/change-password.html")
78 if err != nil {
79 return nil, err
80 }
81 changePasswordSuccess, err := parse("templates/change-password-success.html")
82 if err != nil {
83 return nil, err
84 }
85 return &Templates{whoami, register, login, consent, changePassword, changePasswordSuccess}, nil
giolekva603e73a2021-10-22 14:46:45 +040086}
87
88type Server struct {
Giorgi Lekveishvilid76414e2023-12-21 13:30:23 +040089 r *mux.Router
90 serv *http.Server
91 kratos string
92 hydra *HydraClient
93 tmpls *Templates
94 enableRegistration bool
giodd213152024-09-27 11:26:59 +020095 api *APIServer
96 defaultReturnTo string
giolekva603e73a2021-10-22 14:46:45 +040097}
98
giodd213152024-09-27 11:26:59 +020099func NewServer(
100 port int,
101 kratos string,
102 hydra *HydraClient,
103 tmpls *Templates,
104 enableRegistration bool,
105 api *APIServer,
106 defaultReturnTo string,
107) *Server {
Giorgi Lekveishvilifedd0062023-12-21 10:52:49 +0400108 r := mux.NewRouter()
109 serv := &http.Server{
110 Addr: fmt.Sprintf(":%d", port),
111 Handler: r,
112 }
giodd213152024-09-27 11:26:59 +0200113 return &Server{r, serv, kratos, hydra, tmpls, enableRegistration, api, defaultReturnTo}
Giorgi Lekveishvilifedd0062023-12-21 10:52:49 +0400114}
115
giolekva47031752021-11-12 14:34:33 +0400116func cacheControlWrapper(h http.Handler) http.Handler {
117 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
118 // TODO(giolekva): enable caching
119 // w.Header().Set("Cache-Control", "max-age=2592000") // 30 days
120 h.ServeHTTP(w, r)
121 })
122}
123
Giorgi Lekveishvilifedd0062023-12-21 10:52:49 +0400124func (s *Server) Start() error {
giolekva47031752021-11-12 14:34:33 +0400125 var staticFS = http.FS(static)
126 fs := http.FileServer(staticFS)
Giorgi Lekveishvilifedd0062023-12-21 10:52:49 +0400127 s.r.PathPrefix("/static/").Handler(cacheControlWrapper(fs))
Giorgi Lekveishvilid76414e2023-12-21 13:30:23 +0400128 if s.enableRegistration {
129 s.r.Path("/register").Methods(http.MethodGet).HandlerFunc(s.registerInitiate)
130 s.r.Path("/register").Methods(http.MethodPost).HandlerFunc(s.register)
131 }
Giorgi Lekveishvilifedd0062023-12-21 10:52:49 +0400132 s.r.Path("/login").Methods(http.MethodGet).HandlerFunc(s.loginInitiate)
133 s.r.Path("/login").Methods(http.MethodPost).HandlerFunc(s.login)
134 s.r.Path("/consent").Methods(http.MethodGet).HandlerFunc(s.consent)
135 s.r.Path("/consent").Methods(http.MethodPost).HandlerFunc(s.processConsent)
136 s.r.Path("/logout").Methods(http.MethodGet).HandlerFunc(s.logout)
giodd213152024-09-27 11:26:59 +0200137 s.r.Path("/change-password").Methods("POST").HandlerFunc(s.changePassword)
138 s.r.Path("/change-password").Methods("GET").HandlerFunc(s.changePasswordForm)
Giorgi Lekveishvilifedd0062023-12-21 10:52:49 +0400139 s.r.Path("/").HandlerFunc(s.whoami)
140 return s.serv.ListenAndServe()
giolekva603e73a2021-10-22 14:46:45 +0400141}
142
143func getCSRFToken(flowType, flow string, cookies []*http.Cookie) (string, error) {
144 jar, err := cookiejar.New(nil)
145 if err != nil {
146 return "", err
147 }
148 client := &http.Client{
149 Jar: jar,
giolekvadd750802021-11-07 13:24:21 +0400150 Transport: &http.Transport{
151 TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
152 },
giolekva603e73a2021-10-22 14:46:45 +0400153 }
giolekvadd750802021-11-07 13:24:21 +0400154 b, err := url.Parse(*kratos + "/self-service/" + flowType + "/browser")
giolekva603e73a2021-10-22 14:46:45 +0400155 if err != nil {
156 return "", err
157 }
158 client.Jar.SetCookies(b, cookies)
giolekvadd750802021-11-07 13:24:21 +0400159 resp, err := client.Get(fmt.Sprintf(*kratos+"/self-service/"+flowType+"/flows?id=%s", flow))
giolekva603e73a2021-10-22 14:46:45 +0400160 if err != nil {
161 return "", err
162 }
163 respBody, err := ioutil.ReadAll(resp.Body)
164 if err != nil {
165 return "", err
166 }
167 token, err := regogo.Get(string(respBody), "input.ui.nodes[0].attributes.value")
168 if err != nil {
169 return "", err
170 }
171 return token.String(), nil
172}
173
Giorgi Lekveishvili58cb1482023-12-04 12:33:49 +0400174func (s *Server) registerInitiate(w http.ResponseWriter, r *http.Request) {
giolekva603e73a2021-10-22 14:46:45 +0400175 if err := r.ParseForm(); err != nil {
176 http.Error(w, err.Error(), http.StatusInternalServerError)
177 return
178 }
179 flow, ok := r.Form["flow"]
180 if !ok {
181 http.Redirect(w, r, s.kratos+"/self-service/registration/browser", http.StatusSeeOther)
182 return
183 }
184 csrfToken, err := getCSRFToken("registration", flow[0], r.Cookies())
185 if err != nil {
186 http.Error(w, err.Error(), http.StatusInternalServerError)
187 return
188 }
giolekva603e73a2021-10-22 14:46:45 +0400189 w.Header().Set("Content-Type", "text/html")
Giorgi Lekveishvili58cb1482023-12-04 12:33:49 +0400190 if err := s.tmpls.Register.Execute(w, csrfToken); err != nil {
giolekva603e73a2021-10-22 14:46:45 +0400191 http.Error(w, err.Error(), http.StatusInternalServerError)
192 return
193 }
194}
195
196type regReq struct {
197 CSRFToken string `json:"csrf_token"`
198 Method string `json:"method"`
199 Password string `json:"password"`
200 Traits regReqTraits `json:"traits"`
201}
202
203type regReqTraits struct {
204 Username string `json:"username"`
205}
206
Giorgi Lekveishvili58cb1482023-12-04 12:33:49 +0400207func (s *Server) register(w http.ResponseWriter, r *http.Request) {
giolekva603e73a2021-10-22 14:46:45 +0400208 if err := r.ParseForm(); err != nil {
209 http.Error(w, err.Error(), http.StatusInternalServerError)
210 return
211 }
212 flow, ok := r.Form["flow"]
213 if !ok {
214 http.Redirect(w, r, s.kratos+"/self-service/registration/browser", http.StatusSeeOther)
215 return
216 }
217 req := regReq{
218 CSRFToken: r.FormValue("csrf_token"),
219 Method: "password",
220 Password: r.FormValue("password"),
221 Traits: regReqTraits{
222 Username: r.FormValue("username"),
223 },
224 }
225 var reqBody bytes.Buffer
226 if err := json.NewEncoder(&reqBody).Encode(req); err != nil {
227 http.Error(w, err.Error(), http.StatusInternalServerError)
228 return
229 }
230 if resp, err := postToKratos("registration", flow[0], r.Cookies(), &reqBody); err != nil {
231 http.Error(w, err.Error(), http.StatusInternalServerError)
232 return
233 } else {
234 for _, c := range resp.Cookies() {
235 http.SetCookie(w, c)
236 }
237 http.Redirect(w, r, "/", http.StatusSeeOther)
238 }
239}
240
241// Login flow
242
243func (s *Server) loginInitiate(w http.ResponseWriter, r *http.Request) {
244 if err := r.ParseForm(); err != nil {
245 http.Error(w, err.Error(), http.StatusInternalServerError)
246 return
247 }
giolekva788dc6e2021-10-25 20:40:53 +0400248 if challenge, ok := r.Form["login_challenge"]; ok {
giodd213152024-09-27 11:26:59 +0200249 _, username, err := getWhoAmIFromKratos(r.Cookies())
Giorgi Lekveishvili7016d882024-04-09 09:06:53 +0400250 if err != nil && err != ErrNotLoggedIn {
251 http.Error(w, err.Error(), http.StatusInternalServerError)
252 return
253 }
254 if err == nil {
255 redirectTo, err := s.hydra.LoginAcceptChallenge(challenge[0], username)
256 if err != nil {
257 http.Error(w, err.Error(), http.StatusInternalServerError)
258 return
259 }
260 http.Redirect(w, r, redirectTo, http.StatusSeeOther)
261 return
262 }
giolekva788dc6e2021-10-25 20:40:53 +0400263 // TODO(giolekva): encrypt
264 http.SetCookie(w, &http.Cookie{
265 Name: "login_challenge",
266 Value: challenge[0],
267 HttpOnly: true,
268 })
giolekva788dc6e2021-10-25 20:40:53 +0400269 }
giodd213152024-09-27 11:26:59 +0200270 returnTo := r.FormValue("return_to")
271 if returnTo == "" && s.defaultReturnTo != "" {
272 returnTo = s.defaultReturnTo
273 }
giolekva603e73a2021-10-22 14:46:45 +0400274 flow, ok := r.Form["flow"]
275 if !ok {
Giorgi Lekveishvili0ba5e402024-03-20 15:56:30 +0400276 addr := s.kratos + "/self-service/login/browser"
277 if returnTo != "" {
278 addr += fmt.Sprintf("?return_to=%s", returnTo)
279 }
280 http.Redirect(w, r, addr, http.StatusSeeOther)
giolekva603e73a2021-10-22 14:46:45 +0400281 return
282 }
283 csrfToken, err := getCSRFToken("login", flow[0], r.Cookies())
284 if err != nil {
285 http.Error(w, err.Error(), http.StatusInternalServerError)
286 return
287 }
giolekva603e73a2021-10-22 14:46:45 +0400288 w.Header().Set("Content-Type", "text/html")
Giorgi Lekveishvilid76414e2023-12-21 13:30:23 +0400289 if err := s.tmpls.Login.Execute(w, map[string]any{
290 "csrfToken": csrfToken,
291 "enableRegistration": s.enableRegistration,
292 }); err != nil {
giolekva603e73a2021-10-22 14:46:45 +0400293 http.Error(w, err.Error(), http.StatusInternalServerError)
294 return
295 }
296}
297
298type loginReq struct {
299 CSRFToken string `json:"csrf_token"`
300 Method string `json:"method"`
301 Password string `json:"password"`
302 Username string `json:"password_identifier"`
303}
304
305func postToKratos(flowType, flow string, cookies []*http.Cookie, req io.Reader) (*http.Response, error) {
306 jar, err := cookiejar.New(nil)
307 if err != nil {
308 return nil, err
309 }
310 client := &http.Client{
311 Jar: jar,
giolekvadd750802021-11-07 13:24:21 +0400312 Transport: &http.Transport{
313 TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
314 },
giolekva603e73a2021-10-22 14:46:45 +0400315 }
giolekvadd750802021-11-07 13:24:21 +0400316 b, err := url.Parse(*kratos + "/self-service/" + flowType + "/browser")
giolekva603e73a2021-10-22 14:46:45 +0400317 if err != nil {
318 return nil, err
319 }
320 client.Jar.SetCookies(b, cookies)
giolekvadd750802021-11-07 13:24:21 +0400321 resp, err := client.Post(fmt.Sprintf(*kratos+"/self-service/"+flowType+"?flow=%s", flow), "application/json", req)
giolekva603e73a2021-10-22 14:46:45 +0400322 if err != nil {
323 return nil, err
324 }
325 return resp, nil
326}
327
Giorgi Lekveishvili0ba5e402024-03-20 15:56:30 +0400328func postFormToKratos(flowType, flow string, cookies []*http.Cookie, data url.Values) (*http.Response, error) {
329 jar, err := cookiejar.New(nil)
330 if err != nil {
331 return nil, err
332 }
333 client := &http.Client{
334 Jar: jar,
335 Transport: &http.Transport{
336 TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
337 },
338 CheckRedirect: func(req *http.Request, via []*http.Request) error {
339 return http.ErrUseLastResponse
340 },
341 }
342 b, err := url.Parse(*kratos + "/self-service/" + flowType + "/browser")
343 if err != nil {
344 return nil, err
345 }
346 client.Jar.SetCookies(b, cookies)
347 resp, err := client.PostForm(fmt.Sprintf(*kratos+"/self-service/"+flowType+"?flow=%s", flow), data)
348 if err != nil {
349 return nil, err
350 }
351 return resp, nil
352}
353
giolekva603e73a2021-10-22 14:46:45 +0400354type logoutResp struct {
355 LogoutURL string `json:"logout_url"`
356}
357
358func getLogoutURLFromKratos(cookies []*http.Cookie) (string, error) {
359 jar, err := cookiejar.New(nil)
360 if err != nil {
361 return "", err
362 }
363 client := &http.Client{
364 Jar: jar,
giolekvadd750802021-11-07 13:24:21 +0400365 Transport: &http.Transport{
366 TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
367 },
giolekva603e73a2021-10-22 14:46:45 +0400368 }
giolekvadd750802021-11-07 13:24:21 +0400369 b, err := url.Parse(*kratos + "/self-service/logout/browser")
giolekva603e73a2021-10-22 14:46:45 +0400370 if err != nil {
371 return "", err
372 }
373 client.Jar.SetCookies(b, cookies)
giolekvadd750802021-11-07 13:24:21 +0400374 resp, err := client.Get(*kratos + "/self-service/logout/browser")
giolekva603e73a2021-10-22 14:46:45 +0400375 if err != nil {
376 return "", err
377 }
378 var lr logoutResp
379 if err := json.NewDecoder(resp.Body).Decode(&lr); err != nil {
380 return "", err
381 }
382 return lr.LogoutURL, nil
383}
384
giodd213152024-09-27 11:26:59 +0200385func getWhoAmIFromKratos(cookies []*http.Cookie) (string, string, error) {
giolekva603e73a2021-10-22 14:46:45 +0400386 jar, err := cookiejar.New(nil)
387 if err != nil {
giodd213152024-09-27 11:26:59 +0200388 return "", "", err
giolekva603e73a2021-10-22 14:46:45 +0400389 }
390 client := &http.Client{
391 Jar: jar,
giolekvadd750802021-11-07 13:24:21 +0400392 Transport: &http.Transport{
393 TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
394 },
giolekva603e73a2021-10-22 14:46:45 +0400395 }
giolekvadd750802021-11-07 13:24:21 +0400396 b, err := url.Parse(*kratos + "/sessions/whoami")
giolekva603e73a2021-10-22 14:46:45 +0400397 if err != nil {
giodd213152024-09-27 11:26:59 +0200398 return "", "", err
giolekva603e73a2021-10-22 14:46:45 +0400399 }
400 client.Jar.SetCookies(b, cookies)
giolekvadd750802021-11-07 13:24:21 +0400401 resp, err := client.Get(*kratos + "/sessions/whoami")
giolekva603e73a2021-10-22 14:46:45 +0400402 if err != nil {
giodd213152024-09-27 11:26:59 +0200403 return "", "", err
giolekva603e73a2021-10-22 14:46:45 +0400404 }
405 respBody, err := ioutil.ReadAll(resp.Body)
406 if err != nil {
giodd213152024-09-27 11:26:59 +0200407 return "", "", err
giolekva603e73a2021-10-22 14:46:45 +0400408 }
409 username, err := regogo.Get(string(respBody), "input.identity.traits.username")
410 if err != nil {
giodd213152024-09-27 11:26:59 +0200411 return "", "", err
giolekva603e73a2021-10-22 14:46:45 +0400412 }
413 if username.String() == "" {
giodd213152024-09-27 11:26:59 +0200414 return "", "", ErrNotLoggedIn
giolekva603e73a2021-10-22 14:46:45 +0400415 }
giodd213152024-09-27 11:26:59 +0200416 id, err := regogo.Get(string(respBody), "input.identity.id")
417 if err != nil {
418 return "", "", err
419 }
420 if id.String() == "" {
421 return "", "", ErrNotLoggedIn
422 }
423 return id.String(), username.String(), nil
giolekva603e73a2021-10-22 14:46:45 +0400424
425}
426
giolekva788dc6e2021-10-25 20:40:53 +0400427func extractError(r io.Reader) error {
428 respBody, err := ioutil.ReadAll(r)
429 if err != nil {
430 return err
431 }
Giorgi Lekveishvili0ba5e402024-03-20 15:56:30 +0400432 fmt.Printf("++ %s\n", respBody)
giolekva788dc6e2021-10-25 20:40:53 +0400433 t, err := regogo.Get(string(respBody), "input.ui.messages[0].type")
434 if err != nil {
435 return err
436 }
437 if t.String() == "error" {
438 message, err := regogo.Get(string(respBody), "input.ui.messages[0].text")
439 if err != nil {
440 return err
441 }
442 return errors.New(message.String())
443 }
444 return nil
445}
446
giolekva603e73a2021-10-22 14:46:45 +0400447func (s *Server) login(w http.ResponseWriter, r *http.Request) {
448 if err := r.ParseForm(); err != nil {
449 http.Error(w, err.Error(), http.StatusInternalServerError)
450 return
451 }
452 flow, ok := r.Form["flow"]
453 if !ok {
454 http.Redirect(w, r, s.kratos+"/self-service/login/browser", http.StatusSeeOther)
455 return
456 }
Giorgi Lekveishvili0ba5e402024-03-20 15:56:30 +0400457 req := url.Values{
458 "csrf_token": []string{r.FormValue("csrf_token")},
459 "method": []string{"password"},
460 "password": []string{r.FormValue("password")},
461 "identifier": []string{r.FormValue("username")},
giolekva603e73a2021-10-22 14:46:45 +0400462 }
Giorgi Lekveishvili0ba5e402024-03-20 15:56:30 +0400463 resp, err := postFormToKratos("login", flow[0], r.Cookies(), req)
Giorgi Lekveishvili0ba5e402024-03-20 15:56:30 +0400464 var vv bytes.Buffer
465 io.Copy(&vv, resp.Body)
466 fmt.Println(vv.String())
giolekva788dc6e2021-10-25 20:40:53 +0400467 if err != nil {
giolekvaeb590282021-10-22 17:31:40 +0400468 if challenge, _ := r.Cookie("login_challenge"); challenge != nil {
giolekva788dc6e2021-10-25 20:40:53 +0400469 redirectTo, err := s.hydra.LoginRejectChallenge(challenge.Value, err.Error())
giolekvaeb590282021-10-22 17:31:40 +0400470 if err != nil {
471 http.Error(w, err.Error(), http.StatusInternalServerError)
472 return
473 }
giolekva788dc6e2021-10-25 20:40:53 +0400474 http.Redirect(w, r, redirectTo, http.StatusSeeOther)
475 return
giolekvaeb590282021-10-22 17:31:40 +0400476 }
giolekva788dc6e2021-10-25 20:40:53 +0400477 http.Error(w, err.Error(), http.StatusInternalServerError)
478 return
giolekva603e73a2021-10-22 14:46:45 +0400479 }
giolekva788dc6e2021-10-25 20:40:53 +0400480 for _, c := range resp.Cookies() {
481 http.SetCookie(w, c)
482 }
483 if challenge, _ := r.Cookie("login_challenge"); challenge != nil {
giodd213152024-09-27 11:26:59 +0200484 _, username, err := getWhoAmIFromKratos(resp.Cookies())
giolekva788dc6e2021-10-25 20:40:53 +0400485 if err != nil {
486 http.Error(w, err.Error(), http.StatusInternalServerError)
487 return
488 }
489 redirectTo, err := s.hydra.LoginAcceptChallenge(challenge.Value, username)
490 if err != nil {
491 http.Error(w, err.Error(), http.StatusInternalServerError)
492 return
493 }
494 http.Redirect(w, r, redirectTo, http.StatusSeeOther)
495 return
496 }
Giorgi Lekveishvili0ba5e402024-03-20 15:56:30 +0400497 if resp.StatusCode == http.StatusSeeOther {
498 http.Redirect(w, r, resp.Header.Get("Location"), http.StatusSeeOther)
499 } else {
500 http.Redirect(w, r, "/", http.StatusSeeOther)
501 }
giolekva603e73a2021-10-22 14:46:45 +0400502}
503
504func (s *Server) logout(w http.ResponseWriter, r *http.Request) {
505 if logoutURL, err := getLogoutURLFromKratos(r.Cookies()); err != nil {
506 http.Error(w, err.Error(), http.StatusInternalServerError)
507 return
508 } else {
509 http.Redirect(w, r, logoutURL, http.StatusSeeOther)
510 }
511}
512
513func (s *Server) whoami(w http.ResponseWriter, r *http.Request) {
giodd213152024-09-27 11:26:59 +0200514 if _, username, err := getWhoAmIFromKratos(r.Cookies()); err != nil {
giolekva603e73a2021-10-22 14:46:45 +0400515 if errors.Is(err, ErrNotLoggedIn) {
516 http.Redirect(w, r, "/login", http.StatusSeeOther)
517 return
518 }
519 http.Error(w, err.Error(), http.StatusInternalServerError)
520 } else {
521 if err := s.tmpls.WhoAmI.Execute(w, username); err != nil {
522 http.Error(w, err.Error(), http.StatusInternalServerError)
523 }
524 }
525}
526
giolekva788dc6e2021-10-25 20:40:53 +0400527// TODO(giolekva): verify if logged in
528func (s *Server) consent(w http.ResponseWriter, r *http.Request) {
529 if err := r.ParseForm(); err != nil {
530 http.Error(w, err.Error(), http.StatusBadRequest)
531 return
532 }
533 challenge, ok := r.Form["consent_challenge"]
534 if !ok {
535 http.Error(w, "Consent challenge not provided", http.StatusBadRequest)
536 return
537 }
538 consent, err := s.hydra.GetConsentChallenge(challenge[0])
539 if err != nil {
540 http.Error(w, err.Error(), http.StatusInternalServerError)
541 return
542 }
giodd213152024-09-27 11:26:59 +0200543 _, username, err := getWhoAmIFromKratos(r.Cookies())
Giorgi Lekveishvili1f2c1c52024-04-12 07:17:58 +0400544 if err != nil {
giolekva788dc6e2021-10-25 20:40:53 +0400545 http.Error(w, err.Error(), http.StatusInternalServerError)
546 return
547 }
Giorgi Lekveishvili1f2c1c52024-04-12 07:17:58 +0400548 acceptedScopes := consent.RequestedScopes
549 idToken := map[string]string{
550 "username": username,
551 "email": username + "@" + *emailDomain,
552 }
553 // TODO(gio): is auto consent safe? should such behaviour be configurable?
554 if redirectTo, err := s.hydra.ConsentAccept(r.FormValue("consent_challenge"), acceptedScopes, idToken); err != nil {
555 http.Error(w, err.Error(), http.StatusInternalServerError)
556 } else {
557 http.Redirect(w, r, redirectTo, http.StatusSeeOther)
558 }
559 // w.Header().Set("Content-Type", "text/html")
560 // if err := s.tmpls.Consent.Execute(w, consent.RequestedScopes); err != nil {
561 // http.Error(w, err.Error(), http.StatusInternalServerError)
562 // return
563 // }
giolekva788dc6e2021-10-25 20:40:53 +0400564}
565
566func (s *Server) processConsent(w http.ResponseWriter, r *http.Request) {
567 if err := r.ParseForm(); err != nil {
568 http.Error(w, err.Error(), http.StatusBadRequest)
569 return
570 }
giodd213152024-09-27 11:26:59 +0200571 _, username, err := getWhoAmIFromKratos(r.Cookies())
giolekva788dc6e2021-10-25 20:40:53 +0400572 if err != nil {
573 http.Error(w, err.Error(), http.StatusInternalServerError)
574 return
575 }
576 if _, accepted := r.Form["allow"]; accepted {
577 acceptedScopes, _ := r.Form["scope"]
578 idToken := map[string]string{
579 "username": username,
giolekvadd750802021-11-07 13:24:21 +0400580 "email": username + "@" + *emailDomain,
giolekva788dc6e2021-10-25 20:40:53 +0400581 }
582 if redirectTo, err := s.hydra.ConsentAccept(r.FormValue("consent_challenge"), acceptedScopes, idToken); err != nil {
583 http.Error(w, err.Error(), http.StatusInternalServerError)
584 } else {
585 http.Redirect(w, r, redirectTo, http.StatusSeeOther)
586 }
587 return
588 } else {
589 // TODO(giolekva): implement rejection logic
590 }
591}
592
giodd213152024-09-27 11:26:59 +0200593type changePasswordData struct {
594 Username string
595 Password string
596 PasswordErrors []ValidationError
597}
598
599func (s *Server) changePasswordForm(w http.ResponseWriter, r *http.Request) {
600 _, username, err := getWhoAmIFromKratos(r.Cookies())
601 if err != nil {
602 if errors.Is(err, ErrNotLoggedIn) {
603 http.Redirect(w, r, "/", http.StatusSeeOther)
604 } else {
605 http.Error(w, err.Error(), http.StatusInternalServerError)
606 }
607 return
608 }
609 if err := s.tmpls.ChangePassword.Execute(w, changePasswordData{Username: username}); err != nil {
610 http.Error(w, err.Error(), http.StatusInternalServerError)
611 return
612 }
613}
614
615func (s *Server) changePassword(w http.ResponseWriter, r *http.Request) {
616 if err := r.ParseForm(); err != nil {
617 http.Error(w, err.Error(), http.StatusBadRequest)
618 return
619 }
620 password := r.FormValue("password")
621 id, username, err := getWhoAmIFromKratos(r.Cookies())
622 if err != nil {
623 if errors.Is(err, ErrNotLoggedIn) {
624 http.Redirect(w, r, "/", http.StatusSeeOther)
625 } else {
626 http.Error(w, err.Error(), http.StatusInternalServerError)
627 }
628 return
629 }
630 if verr, err := s.api.apiPasswordChange(id, username, password); err != nil {
631 http.Error(w, err.Error(), http.StatusInternalServerError)
632 } else if len(verr) > 0 {
633 if err := s.tmpls.ChangePassword.Execute(w, changePasswordData{username, password, verr}); err != nil {
634 http.Error(w, err.Error(), http.StatusInternalServerError)
635 return
636 }
637 } else {
638 if err := s.tmpls.ChangePasswordSuccess.Execute(w, nil); err != nil {
639 http.Error(w, err.Error(), http.StatusInternalServerError)
640 return
641 }
642 }
643}
644
giolekva603e73a2021-10-22 14:46:45 +0400645func main() {
646 flag.Parse()
647 t, err := ParseTemplates(tmpls)
648 if err != nil {
649 log.Fatal(err)
650 }
giodd213152024-09-27 11:26:59 +0200651 api := NewAPIServer(*apiPort, *kratosAPI)
Giorgi Lekveishvilifedd0062023-12-21 10:52:49 +0400652 go func() {
giodd213152024-09-27 11:26:59 +0200653 log.Fatal(api.Start())
Giorgi Lekveishvilifedd0062023-12-21 10:52:49 +0400654 }()
655 func() {
656 s := NewServer(
657 *port,
658 *kratos,
659 NewHydraClient(*hydra),
660 t,
Giorgi Lekveishvilid76414e2023-12-21 13:30:23 +0400661 *enableRegistration,
giodd213152024-09-27 11:26:59 +0200662 api,
663 *defaultReturnTo,
Giorgi Lekveishvilifedd0062023-12-21 10:52:49 +0400664 )
665 log.Fatal(s.Start())
666 }()
giolekva603e73a2021-10-22 14:46:45 +0400667}