forked from atreya2011/go-kratos-test
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmain.go
321 lines (287 loc) · 9.46 KB
/
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
package main
import (
"context"
"embed"
"encoding/json"
"errors"
"fmt"
"html/template"
"log"
"net/http"
"net/http/cookiejar"
"net/http/httputil"
"strings"
kratos "github.com/ory/kratos-client-go"
)
var ctx = context.Background()
//go:embed templates
var templates embed.FS
// templateData contains data for template
type templateData struct {
Title string
UI *kratos.UiContainer
Details string
}
// server contains server information
type server struct {
KratosAPIClient *kratos.APIClient
KratosPublicEndpoint string
Port string
}
func main() {
// create server
s, err := NewServer(4433)
if err != nil {
log.Fatalln(err)
}
http.HandleFunc("/login", s.ensureCookieFlowID("login", s.handleLogin))
http.HandleFunc("/logout", s.handleLogout)
http.HandleFunc("/error", s.handleError)
http.HandleFunc("/registration", s.ensureCookieFlowID("registration", s.handleRegister))
http.HandleFunc("/verification", s.ensureCookieFlowID("verification", s.handleVerification))
http.HandleFunc("/registered", ensureCookieReferer(s.handleRegistered))
http.HandleFunc("/dashboard", s.handleDashboard)
http.HandleFunc("/recovery", s.ensureCookieFlowID("recovery", s.handleRecovery))
http.HandleFunc("/settings", s.ensureCookieFlowID("settings", s.handleSettings))
http.HandleFunc("/", s.handleIndex)
// start server
log.Println("Auth Server listening on port 4455")
log.Fatalln(http.ListenAndServe(s.Port, http.DefaultServeMux))
}
// handleLogin handles kratos login flow
func (s *server) handleLogin(w http.ResponseWriter, r *http.Request, cookie, flowID string) {
// get the login flow
flow, _, err := s.KratosAPIClient.V0alpha2Api.GetSelfServiceLoginFlow(ctx).Id(flowID).Cookie(cookie).Execute()
if err != nil {
writeError(w, http.StatusUnauthorized, err)
return
}
templateData := templateData{
Title: "Login",
UI: &flow.Ui,
}
// render template index.html
templateData.Render(w)
}
// handleLogout handles kratos logout flow
func (s *server) handleLogout(w http.ResponseWriter, r *http.Request) {
// get cookie from headers
cookie := r.Header.Get("cookie")
// create self-service logout flow for browser
flow, _, err := s.KratosAPIClient.V0alpha2Api.CreateSelfServiceLogoutFlowUrlForBrowsers(ctx).Cookie(cookie).Execute()
if err != nil {
http.Redirect(w, r, "/login", http.StatusSeeOther)
return
}
// redirect to logout url if session is valid
if flow != nil {
http.Redirect(w, r, flow.LogoutUrl, http.StatusFound)
return
}
http.Redirect(w, r, "/login", http.StatusSeeOther)
}
// handleError handles login/registration error
func (s *server) handleError(w http.ResponseWriter, r *http.Request) {
// get url query parameters
errorID := r.URL.Query().Get("id")
// get error details
errorDetails, _, err := s.KratosAPIClient.V0alpha2Api.GetSelfServiceError(ctx).Id(errorID).Execute()
if err != nil {
writeError(w, http.StatusInternalServerError, err)
return
}
// marshal errorDetails to json
errorDetailsJSON, err := json.MarshalIndent(errorDetails, "", " ")
if err != nil {
writeError(w, http.StatusInternalServerError, err)
return
}
templateData := templateData{
Title: "Error",
Details: string(errorDetailsJSON),
}
// render template index.html
templateData.Render(w)
}
// handleRegister handles kratos registration flow
func (s *server) handleRegister(w http.ResponseWriter, r *http.Request, cookie, flowID string) {
// get the registration flow
flow, _, err := s.KratosAPIClient.V0alpha2Api.GetSelfServiceRegistrationFlow(ctx).Id(flowID).Cookie(cookie).Execute()
if err != nil {
writeError(w, http.StatusUnauthorized, err)
return
}
templateData := templateData{
Title: "Registration",
UI: &flow.Ui,
}
// render template index.html
templateData.Render(w)
}
// handleVerification handles kratos verification flow
func (s *server) handleVerification(w http.ResponseWriter, r *http.Request, cookie, flowID string) {
// get self-service verification flow for browser
flow, _, err := s.KratosAPIClient.V0alpha2Api.GetSelfServiceVerificationFlow(ctx).Id(flowID).Cookie(cookie).Execute()
if err != nil {
writeError(w, http.StatusUnauthorized, err)
return
}
title := "Verify your Email address"
ui := &flow.Ui
if flow.Ui.Messages != nil {
for _, message := range flow.Ui.Messages {
if strings.ToLower(message.GetText()) == "you successfully verified your email address." {
title = "Verification Complete"
ui = nil
}
}
}
templateData := templateData{
Title: title,
UI: ui,
}
// render template index.html
templateData.Render(w)
}
// handleRegistered displays registration complete message to user
func (s *server) handleRegistered(w http.ResponseWriter, r *http.Request) {
templateData := templateData{
Title: "Registration Complete",
}
// render template index.html
templateData.Render(w)
}
// handleRecovery handles kratos recovery flow
func (s *server) handleRecovery(w http.ResponseWriter, r *http.Request, cookie, flowID string) {
// get self-service recovery flow for browser
flow, _, err := s.KratosAPIClient.V0alpha2Api.GetSelfServiceRecoveryFlow(ctx).Id(flowID).Cookie(cookie).Execute()
if err != nil {
writeError(w, http.StatusUnauthorized, err)
return
}
templateData := templateData{
Title: "Password Recovery Form",
UI: &flow.Ui,
}
// render template index.html
templateData.Render(w)
}
// handleSettings handles kratos settings flow
func (s *server) handleSettings(w http.ResponseWriter, r *http.Request, cookie, flowID string) {
// get self-service recovery flow for browser
flow, _, err := s.KratosAPIClient.V0alpha2Api.GetSelfServiceSettingsFlow(ctx).Id(flowID).Cookie(cookie).Execute()
if err != nil {
writeError(w, http.StatusUnauthorized, err)
return
}
templateData := templateData{
Title: "Settings",
UI: &flow.Ui,
}
// render template index.html
templateData.Render(w)
}
// handleDashboard shows dashboard
func (s *server) handleDashboard(w http.ResponseWriter, r *http.Request) {
// get cookie from headers
cookie := r.Header.Get("cookie")
// get session details
session, _, err := s.KratosAPIClient.V0alpha2Api.ToSession(ctx).Cookie(cookie).Execute()
if err != nil {
http.Redirect(w, r, "/login", http.StatusFound)
return
}
// marshal session to json
sessionJSON, err := json.MarshalIndent(session, "", " ")
if err != nil {
writeError(w, http.StatusInternalServerError, err)
return
}
templateData := templateData{
Title: "Session Details",
Details: string(sessionJSON),
}
// render template index.html
templateData.Render(w)
}
func NewServer(kratosPublicEndpointPort int) (*server, error) {
// create a new kratos client for self hosted server
conf := kratos.NewConfiguration()
conf.Servers = kratos.ServerConfigurations{{URL: fmt.Sprintf("http://kratos:%d", kratosPublicEndpointPort)}}
cj, err := cookiejar.New(nil)
if err != nil {
return nil, err
}
conf.HTTPClient = &http.Client{Jar: cj}
return &server{
KratosAPIClient: kratos.NewAPIClient(conf),
KratosPublicEndpoint: fmt.Sprintf("http://localhost:%d", kratosPublicEndpointPort),
Port: ":4455",
}, nil
}
// writeError writes error to the response
func writeError(w http.ResponseWriter, statusCode int, err error) {
w.WriteHeader(statusCode)
_, e := w.Write([]byte(err.Error()))
if e != nil {
log.Fatal(err)
}
}
// ensureCookieFlowID is a middleware function that ensures that a request contains
// flow ID in url query parameters and cookie in header
func (s *server) ensureCookieFlowID(flowType string, next func(w http.ResponseWriter, r *http.Request, cookie, flowID string)) http.HandlerFunc {
// create redirect url based on flow type
redirectURL := fmt.Sprintf("%s/self-service/%s/browser", s.KratosPublicEndpoint, flowType)
return func(w http.ResponseWriter, r *http.Request) {
// get flowID from url query parameters
flowID := r.URL.Query().Get("flow")
// if there is no flow id in url query parameters, create a new flow
if flowID == "" {
http.Redirect(w, r, redirectURL, http.StatusFound)
return
}
// get cookie from headers
cookie := r.Header.Get("cookie")
// if there is no cookie in header, return error
if cookie == "" {
writeError(w, http.StatusBadRequest, errors.New("missing cookie"))
return
}
// call next handler
next(w, r, cookie, flowID)
}
}
// ensureCookieReferer is a middleware function that ensures that cookie in header contains csrf_token and referer is not empty
func ensureCookieReferer(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// get cookie from headers
cookie := r.Header.Get("cookie")
// if there is no csrf_token in cookie, return error
if !strings.Contains(cookie, "csrf_token") {
writeError(w, http.StatusUnauthorized, errors.New(http.StatusText(int(http.StatusUnauthorized))))
return
}
// get referer from headers
referer := r.Header.Get("referer")
// if there is no referer in header, return error
if referer == "" {
writeError(w, http.StatusBadRequest, errors.New(http.StatusText(int(http.StatusUnauthorized))))
return
}
// call next handler
next(w, r)
}
}
// Render renders template with provided data
func (td *templateData) Render(w http.ResponseWriter) {
// render template index.html
tmpl := template.Must(template.ParseFS(templates, "templates/index.html"))
if err := tmpl.Execute(w, td); err != nil {
writeError(w, http.StatusInternalServerError, err)
}
}
func (s *server) handleIndex(w http.ResponseWriter, r *http.Request) {
b, _ := httputil.DumpRequest(r, true)
log.Println(string(b))
w.WriteHeader(200)
}