blob: 7aa6d3a1fd9e8c230223bfce0ee245275b6e6331 [file] [log] [blame]
Giorgi Lekveishvili12850ee2023-06-22 13:11:17 +04001package welcome
2
3import (
Giorgi Lekveishvilic89b9002023-12-21 13:09:26 +04004 "bytes"
Giorgi Lekveishvili12850ee2023-06-22 13:11:17 +04005 "embed"
6 "encoding/json"
7 "fmt"
DTabidze52593392024-03-08 12:53:20 +04008 "html/template"
Giorgi Lekveishvilic89b9002023-12-21 13:09:26 +04009 "io"
Giorgi Lekveishvili12850ee2023-06-22 13:11:17 +040010 "log"
11 "net/http"
Giorgi Lekveishvili6b887be2023-06-22 14:38:19 +040012 "net/url"
gio4784f8e2024-08-01 15:20:12 +040013 "os"
Giorgi Lekveishvili12850ee2023-06-22 13:11:17 +040014
Giorgi Lekveishvili123a3672023-12-04 13:01:29 +040015 "github.com/gorilla/mux"
Giorgi Lekveishvili12850ee2023-06-22 13:11:17 +040016
17 "github.com/giolekva/pcloud/core/installer"
gioe72b54f2024-04-22 10:44:41 +040018 "github.com/giolekva/pcloud/core/installer/soft"
Giorgi Lekveishvili12850ee2023-06-22 13:11:17 +040019)
20
giodd213152024-09-27 11:26:59 +020021//go:embed welcome-tmpl/*
22var welcomeTmpls embed.FS
Giorgi Lekveishvili83b72192024-03-11 18:36:14 +040023
Giorgi Lekveishvili12850ee2023-06-22 13:11:17 +040024//go:embed static/*
25var staticAssets embed.FS
26
gio1bf00802024-08-17 12:31:41 +040027//go:embed stat/*
28var statAssets embed.FS
29
giodd213152024-09-27 11:26:59 +020030type welcomeTmplts struct {
31 createAccount *template.Template
32 createAccountSuccess *template.Template
33}
34
35func parseTemplatesWelcome(fs embed.FS) (welcomeTmplts, error) {
36 base, err := template.New("base.html").ParseFS(fs, "welcome-tmpl/base.html")
37 if err != nil {
38 return welcomeTmplts{}, err
39 }
40 parse := func(path string) (*template.Template, error) {
41 if b, err := base.Clone(); err != nil {
42 return nil, err
43 } else {
44 return b.ParseFS(fs, path)
45 }
46 }
47 createAccount, err := parse("welcome-tmpl/create-account.html")
48 if err != nil {
49 return welcomeTmplts{}, err
50 }
51 createAccountSuccess, err := parse("welcome-tmpl/create-account-success.html")
52 if err != nil {
53 return welcomeTmplts{}, err
54 }
55 return welcomeTmplts{createAccount, createAccountSuccess}, nil
56}
57
Giorgi Lekveishvili12850ee2023-06-22 13:11:17 +040058type Server struct {
gio2728e402024-08-01 18:14:21 +040059 port int
60 repo soft.RepoIO
61 nsCreator installer.NamespaceCreator
62 hf installer.HelmFetcher
63 createAccountAddr string
64 loginAddr string
65 membershipsAddr string
giodd213152024-09-27 11:26:59 +020066 tmpl welcomeTmplts
Giorgi Lekveishvili12850ee2023-06-22 13:11:17 +040067}
68
Giorgi Lekveishvilic89b9002023-12-21 13:09:26 +040069func NewServer(
70 port int,
gioe72b54f2024-04-22 10:44:41 +040071 repo soft.RepoIO,
Giorgi Lekveishvilic89b9002023-12-21 13:09:26 +040072 nsCreator installer.NamespaceCreator,
giof8843412024-05-22 16:38:05 +040073 hf installer.HelmFetcher,
Giorgi Lekveishvilic89b9002023-12-21 13:09:26 +040074 createAccountAddr string,
Giorgi Lekveishvili83b72192024-03-11 18:36:14 +040075 loginAddr string,
gio2728e402024-08-01 18:14:21 +040076 membershipsAddr string,
giodd213152024-09-27 11:26:59 +020077) (*Server, error) {
78 tmplts, err := parseTemplatesWelcome(welcomeTmpls)
79 if err != nil {
80 return nil, err
81 }
Giorgi Lekveishvili12850ee2023-06-22 13:11:17 +040082 return &Server{
83 port,
84 repo,
Giorgi Lekveishvili7fb28bf2023-06-24 19:51:16 +040085 nsCreator,
giof8843412024-05-22 16:38:05 +040086 hf,
Giorgi Lekveishvilic89b9002023-12-21 13:09:26 +040087 createAccountAddr,
Giorgi Lekveishvili83b72192024-03-11 18:36:14 +040088 loginAddr,
gio2728e402024-08-01 18:14:21 +040089 membershipsAddr,
giodd213152024-09-27 11:26:59 +020090 tmplts,
91 }, nil
Giorgi Lekveishvili12850ee2023-06-22 13:11:17 +040092}
93
94func (s *Server) Start() {
Giorgi Lekveishvili123a3672023-12-04 13:01:29 +040095 r := mux.NewRouter()
gio1bf00802024-08-17 12:31:41 +040096 r.PathPrefix("/stat/").Handler(cachingHandler{http.FileServer(http.FS(statAssets))})
Giorgi Lekveishvilid542b732024-03-25 18:17:39 +040097 r.Path("/").Methods("POST").HandlerFunc(s.createAccount)
98 r.Path("/").Methods("GET").HandlerFunc(s.createAccountForm)
Giorgi Lekveishvili123a3672023-12-04 13:01:29 +040099 http.Handle("/", r)
100 log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", s.port), nil))
Giorgi Lekveishvili12850ee2023-06-22 13:11:17 +0400101}
102
Giorgi Lekveishvilid542b732024-03-25 18:17:39 +0400103func (s *Server) createAccountForm(w http.ResponseWriter, r *http.Request) {
giodd213152024-09-27 11:26:59 +0200104 s.renderRegistrationForm(w, formData{})
DTabidze52593392024-03-08 12:53:20 +0400105}
106
107type formData struct {
108 UsernameErrors []string
109 PasswordErrors []string
110 Data createAccountReq
Giorgi Lekveishvili12850ee2023-06-22 13:11:17 +0400111}
112
giodd213152024-09-27 11:26:59 +0200113type cpFormData struct {
114 UsernameErrors []string
115 PasswordErrors []string
116 Password string
117}
118
Giorgi Lekveishvili123a3672023-12-04 13:01:29 +0400119type createAccountReq struct {
Giorgi Lekveishvili260a97d2023-12-08 15:04:16 +0400120 Username string `json:"username,omitempty"`
Giorgi Lekveishvilic89b9002023-12-21 13:09:26 +0400121 Password string `json:"password,omitempty"`
Giorgi Lekveishvili260a97d2023-12-08 15:04:16 +0400122 SecretToken string `json:"secretToken,omitempty"`
Giorgi Lekveishvili12850ee2023-06-22 13:11:17 +0400123}
124
Giorgi Lekveishvilic89b9002023-12-21 13:09:26 +0400125type apiCreateAccountReq struct {
126 Username string `json:"username,omitempty"`
127 Password string `json:"password,omitempty"`
128}
129
DTabidze52593392024-03-08 12:53:20 +0400130type ValidationError struct {
131 Field string `json:"field"`
132 Message string `json:"message"`
133}
134
135type ErrorResponse struct {
136 Errors []ValidationError `json:"errors"`
137}
138
Giorgi Lekveishvili6b887be2023-06-22 14:38:19 +0400139func getFormValue(v url.Values, name string) (string, error) {
140 items, ok := v[name]
141 if !ok || len(items) != 1 {
142 return "", fmt.Errorf("%s not found", name)
143 }
144 return items[0], nil
145}
146
Giorgi Lekveishvili123a3672023-12-04 13:01:29 +0400147func extractReq(r *http.Request) (createAccountReq, error) {
148 var req createAccountReq
Giorgi Lekveishvili6b887be2023-06-22 14:38:19 +0400149 if err := func() error {
150 var err error
Giorgi Lekveishvili123a3672023-12-04 13:01:29 +0400151 if err = r.ParseForm(); err != nil {
Giorgi Lekveishvili6b887be2023-06-22 14:38:19 +0400152 return err
153 }
Giorgi Lekveishvili123a3672023-12-04 13:01:29 +0400154 if req.Username, err = getFormValue(r.PostForm, "username"); err != nil {
Giorgi Lekveishvili6b887be2023-06-22 14:38:19 +0400155 return err
156 }
Giorgi Lekveishvili123a3672023-12-04 13:01:29 +0400157 if req.Password, err = getFormValue(r.PostForm, "password"); err != nil {
Giorgi Lekveishvili6b887be2023-06-22 14:38:19 +0400158 return err
159 }
Giorgi Lekveishvili123a3672023-12-04 13:01:29 +0400160 if req.SecretToken, err = getFormValue(r.PostForm, "secret-token"); err != nil {
Giorgi Lekveishvili6b887be2023-06-22 14:38:19 +0400161 return err
162 }
163 return nil
164 }(); err != nil {
Giorgi Lekveishvili123a3672023-12-04 13:01:29 +0400165 if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
166 return createAccountReq{}, err
Giorgi Lekveishvili6b887be2023-06-22 14:38:19 +0400167 }
Giorgi Lekveishvili12850ee2023-06-22 13:11:17 +0400168 }
Giorgi Lekveishvili123a3672023-12-04 13:01:29 +0400169 return req, nil
170}
171
giodd213152024-09-27 11:26:59 +0200172func (s *Server) renderRegistrationForm(w http.ResponseWriter, data formData) {
173 if err := s.tmpl.createAccount.Execute(w, data); err != nil {
DTabidze52593392024-03-08 12:53:20 +0400174 http.Error(w, err.Error(), http.StatusInternalServerError)
175 return
176 }
177}
178
giodd213152024-09-27 11:26:59 +0200179func (s *Server) renderRegistrationSuccess(w http.ResponseWriter, loginAddr string) {
Giorgi Lekveishvili83b72192024-03-11 18:36:14 +0400180 data := struct {
181 LoginAddr string
182 }{
183 LoginAddr: loginAddr,
184 }
giodd213152024-09-27 11:26:59 +0200185 if err := s.tmpl.createAccountSuccess.Execute(w, data); err != nil {
Giorgi Lekveishvili83b72192024-03-11 18:36:14 +0400186 http.Error(w, err.Error(), http.StatusInternalServerError)
187 return
188 }
189}
190
Giorgi Lekveishvilid542b732024-03-25 18:17:39 +0400191func (s *Server) createAccount(w http.ResponseWriter, r *http.Request) {
Giorgi Lekveishvili39913692023-12-05 08:58:08 +0400192 req, err := extractReq(r)
193 if err != nil {
194 http.Error(w, err.Error(), http.StatusInternalServerError)
195 return
196 }
Giorgi Lekveishvilic89b9002023-12-21 13:09:26 +0400197 {
198 var buf bytes.Buffer
199 cr := apiCreateAccountReq{req.Username, req.Password}
200 if err := json.NewEncoder(&buf).Encode(cr); err != nil {
201 http.Error(w, err.Error(), http.StatusInternalServerError)
202 return
203 }
204 resp, err := http.Post(s.createAccountAddr, "application/json", &buf)
205 if err != nil {
Giorgi Lekveishvili83399052024-02-14 13:27:30 +0400206 var respBody bytes.Buffer
207 if _, err := io.Copy(&respBody, resp.Body); err != nil {
208 http.Error(w, err.Error(), http.StatusInternalServerError)
209 }
210 respStr := respBody.String()
211 log.Println(respStr)
212 http.Error(w, respStr, http.StatusInternalServerError)
Giorgi Lekveishvilic89b9002023-12-21 13:09:26 +0400213 return
214 }
Giorgi Lekveishvilic89b9002023-12-21 13:09:26 +0400215 if resp.StatusCode != http.StatusOK {
DTabidze52593392024-03-08 12:53:20 +0400216 var errResponse ErrorResponse
217 if err := json.NewDecoder(resp.Body).Decode(&errResponse); err != nil {
218 http.Error(w, "Error Decoding JSON", http.StatusInternalServerError)
219 return
220 }
221 var usernameErrors, passwordErrors []string
222 for _, err := range errResponse.Errors {
223 if err.Field == "username" {
224 usernameErrors = append(usernameErrors, err.Message)
225 }
226 if err.Field == "password" {
227 passwordErrors = append(passwordErrors, err.Message)
228 }
229 }
giodd213152024-09-27 11:26:59 +0200230 s.renderRegistrationForm(w, formData{
DTabidze52593392024-03-08 12:53:20 +0400231 usernameErrors,
232 passwordErrors,
233 req,
234 })
Giorgi Lekveishvilic89b9002023-12-21 13:09:26 +0400235 return
236 }
237 }
gio2728e402024-08-01 18:14:21 +0400238 if err := s.createUser(req.Username); err != nil {
Giorgi Lekveishvilid542b732024-03-25 18:17:39 +0400239 http.Error(w, err.Error(), http.StatusInternalServerError)
240 return
241 }
giodd213152024-09-27 11:26:59 +0200242 s.renderRegistrationSuccess(w, s.loginAddr)
Giorgi Lekveishvili12850ee2023-06-22 13:11:17 +0400243}
Giorgi Lekveishvilid542b732024-03-25 18:17:39 +0400244
gio2728e402024-08-01 18:14:21 +0400245type firstAccount struct {
Giorgi Lekveishvilid542b732024-03-25 18:17:39 +0400246 Created bool `json:"created"`
gio2728e402024-08-01 18:14:21 +0400247 Domain string `json:"domain"`
Giorgi Lekveishvilid542b732024-03-25 18:17:39 +0400248 Groups []string `json:"groups"`
249}
250
251type initRequest struct {
gio2728e402024-08-01 18:14:21 +0400252 User string `json:"user"`
253 Email string `json:"email"`
Giorgi Lekveishvilid542b732024-03-25 18:17:39 +0400254 Groups []string `json:"groups"`
255}
256
gio2728e402024-08-01 18:14:21 +0400257type createUserRequest struct {
258 User string `json:"user"`
259 Email string `json:"email"`
260}
261
262func (s *Server) createUser(username string) error {
giob4a3a192024-08-19 09:55:47 +0400263 _, err := s.repo.Do(func(r soft.RepoFS) (string, error) {
gio2728e402024-08-01 18:14:21 +0400264 var fa firstAccount
gioe72b54f2024-04-22 10:44:41 +0400265 if err := soft.ReadYaml(r, "first-account.yaml", &fa); err != nil {
gio3af43942024-04-16 08:13:50 +0400266 return "", err
267 }
gio2728e402024-08-01 18:14:21 +0400268 var resp *http.Response
269 var err error
gio3af43942024-04-16 08:13:50 +0400270 if fa.Created {
gio2728e402024-08-01 18:14:21 +0400271 req := createUserRequest{username, fmt.Sprintf("%s@%s", username, fa.Domain)}
272 var buf bytes.Buffer
273 if err := json.NewEncoder(&buf).Encode(req); err != nil {
274 return "", err
275 }
276 resp, err = http.Post(
277 fmt.Sprintf("%s/api/users", s.membershipsAddr),
278 "applications/json",
279 &buf,
280 )
281 } else {
282 req := initRequest{username, fmt.Sprintf("%s@%s", username, fa.Domain), fa.Groups}
283 var buf bytes.Buffer
284 if err := json.NewEncoder(&buf).Encode(req); err != nil {
285 return "", err
286 }
287 resp, err = http.Post(
288 fmt.Sprintf("%s/api/init", s.membershipsAddr),
289 "applications/json",
290 &buf,
291 )
292 fa.Created = true
293 if err := soft.WriteYaml(r, "first-account.yaml", fa); err != nil {
294 return "", err
295 }
gio3af43942024-04-16 08:13:50 +0400296 }
gio4784f8e2024-08-01 15:20:12 +0400297 if err != nil {
gio3af43942024-04-16 08:13:50 +0400298 return "", err
299 }
gio4784f8e2024-08-01 15:20:12 +0400300 defer resp.Body.Close()
301 fmt.Printf("Memberships resp: %d", resp.StatusCode)
302 io.Copy(os.Stdout, resp.Body)
gio2728e402024-08-01 18:14:21 +0400303 if resp.StatusCode != http.StatusOK {
304 return "", fmt.Errorf("memberships error")
gio3af43942024-04-16 08:13:50 +0400305 }
306 return "initialized groups for first account", nil
307 })
giob4a3a192024-08-19 09:55:47 +0400308 return err
Giorgi Lekveishvilid542b732024-03-25 18:17:39 +0400309}