blob: 23f9e001cb09060e854275629d13b55598b136e0 [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"
gio4784f8e2024-08-01 15:20:12 +040012 "os"
Giorgi Lekveishvili12850ee2023-06-22 13:11:17 +040013
Giorgi Lekveishvili123a3672023-12-04 13:01:29 +040014 "github.com/gorilla/mux"
Giorgi Lekveishvili12850ee2023-06-22 13:11:17 +040015
16 "github.com/giolekva/pcloud/core/installer"
gio59946282024-10-07 12:55:51 +040017 "github.com/giolekva/pcloud/core/installer/server"
gioe72b54f2024-04-22 10:44:41 +040018 "github.com/giolekva/pcloud/core/installer/soft"
Giorgi Lekveishvili12850ee2023-06-22 13:11:17 +040019)
20
gio59946282024-10-07 12:55:51 +040021//go:embed templates/*
22var tmpls 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
gio59946282024-10-07 12:55:51 +040027type templates struct {
giodd213152024-09-27 11:26:59 +020028 createAccount *template.Template
29 createAccountSuccess *template.Template
30}
31
gio59946282024-10-07 12:55:51 +040032func parseTemplatesWelcome(fs embed.FS) (templates, error) {
33 base, err := template.New("base.html").ParseFS(fs, "templates/base.html")
giodd213152024-09-27 11:26:59 +020034 if err != nil {
gio59946282024-10-07 12:55:51 +040035 return templates{}, err
giodd213152024-09-27 11:26:59 +020036 }
37 parse := func(path string) (*template.Template, error) {
38 if b, err := base.Clone(); err != nil {
39 return nil, err
40 } else {
41 return b.ParseFS(fs, path)
42 }
43 }
gio59946282024-10-07 12:55:51 +040044 createAccount, err := parse("templates/create-account.html")
giodd213152024-09-27 11:26:59 +020045 if err != nil {
gio59946282024-10-07 12:55:51 +040046 return templates{}, err
giodd213152024-09-27 11:26:59 +020047 }
gio59946282024-10-07 12:55:51 +040048 createAccountSuccess, err := parse("templates/create-account-success.html")
giodd213152024-09-27 11:26:59 +020049 if err != nil {
gio59946282024-10-07 12:55:51 +040050 return templates{}, err
giodd213152024-09-27 11:26:59 +020051 }
gio59946282024-10-07 12:55:51 +040052 return templates{createAccount, createAccountSuccess}, nil
giodd213152024-09-27 11:26:59 +020053}
54
Giorgi Lekveishvili12850ee2023-06-22 13:11:17 +040055type Server struct {
gio2728e402024-08-01 18:14:21 +040056 port int
57 repo soft.RepoIO
58 nsCreator installer.NamespaceCreator
59 hf installer.HelmFetcher
60 createAccountAddr string
61 loginAddr string
62 membershipsAddr string
gio59946282024-10-07 12:55:51 +040063 tmpl templates
Giorgi Lekveishvili12850ee2023-06-22 13:11:17 +040064}
65
Giorgi Lekveishvilic89b9002023-12-21 13:09:26 +040066func NewServer(
67 port int,
gioe72b54f2024-04-22 10:44:41 +040068 repo soft.RepoIO,
Giorgi Lekveishvilic89b9002023-12-21 13:09:26 +040069 nsCreator installer.NamespaceCreator,
giof8843412024-05-22 16:38:05 +040070 hf installer.HelmFetcher,
Giorgi Lekveishvilic89b9002023-12-21 13:09:26 +040071 createAccountAddr string,
Giorgi Lekveishvili83b72192024-03-11 18:36:14 +040072 loginAddr string,
gio2728e402024-08-01 18:14:21 +040073 membershipsAddr string,
giodd213152024-09-27 11:26:59 +020074) (*Server, error) {
gio59946282024-10-07 12:55:51 +040075 tmplts, err := parseTemplatesWelcome(tmpls)
giodd213152024-09-27 11:26:59 +020076 if err != nil {
77 return nil, err
78 }
Giorgi Lekveishvili12850ee2023-06-22 13:11:17 +040079 return &Server{
80 port,
81 repo,
Giorgi Lekveishvili7fb28bf2023-06-24 19:51:16 +040082 nsCreator,
giof8843412024-05-22 16:38:05 +040083 hf,
Giorgi Lekveishvilic89b9002023-12-21 13:09:26 +040084 createAccountAddr,
Giorgi Lekveishvili83b72192024-03-11 18:36:14 +040085 loginAddr,
gio2728e402024-08-01 18:14:21 +040086 membershipsAddr,
giodd213152024-09-27 11:26:59 +020087 tmplts,
88 }, nil
Giorgi Lekveishvili12850ee2023-06-22 13:11:17 +040089}
90
91func (s *Server) Start() {
Giorgi Lekveishvili123a3672023-12-04 13:01:29 +040092 r := mux.NewRouter()
gio59946282024-10-07 12:55:51 +040093 r.PathPrefix("/static/").Handler(server.NewCachingHandler(http.FileServer(http.FS(staticAssets))))
Giorgi Lekveishvilid542b732024-03-25 18:17:39 +040094 r.Path("/").Methods("POST").HandlerFunc(s.createAccount)
95 r.Path("/").Methods("GET").HandlerFunc(s.createAccountForm)
Giorgi Lekveishvili123a3672023-12-04 13:01:29 +040096 http.Handle("/", r)
97 log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", s.port), nil))
Giorgi Lekveishvili12850ee2023-06-22 13:11:17 +040098}
99
Giorgi Lekveishvilid542b732024-03-25 18:17:39 +0400100func (s *Server) createAccountForm(w http.ResponseWriter, r *http.Request) {
giodd213152024-09-27 11:26:59 +0200101 s.renderRegistrationForm(w, formData{})
DTabidze52593392024-03-08 12:53:20 +0400102}
103
104type formData struct {
105 UsernameErrors []string
106 PasswordErrors []string
107 Data createAccountReq
Giorgi Lekveishvili12850ee2023-06-22 13:11:17 +0400108}
109
giodd213152024-09-27 11:26:59 +0200110type cpFormData struct {
111 UsernameErrors []string
112 PasswordErrors []string
113 Password string
114}
115
Giorgi Lekveishvili123a3672023-12-04 13:01:29 +0400116type createAccountReq struct {
Giorgi Lekveishvili260a97d2023-12-08 15:04:16 +0400117 Username string `json:"username,omitempty"`
Giorgi Lekveishvilic89b9002023-12-21 13:09:26 +0400118 Password string `json:"password,omitempty"`
Giorgi Lekveishvili260a97d2023-12-08 15:04:16 +0400119 SecretToken string `json:"secretToken,omitempty"`
Giorgi Lekveishvili12850ee2023-06-22 13:11:17 +0400120}
121
Giorgi Lekveishvilic89b9002023-12-21 13:09:26 +0400122type apiCreateAccountReq struct {
123 Username string `json:"username,omitempty"`
124 Password string `json:"password,omitempty"`
125}
126
DTabidze52593392024-03-08 12:53:20 +0400127type ValidationError struct {
128 Field string `json:"field"`
129 Message string `json:"message"`
130}
131
132type ErrorResponse struct {
133 Errors []ValidationError `json:"errors"`
134}
135
Giorgi Lekveishvili123a3672023-12-04 13:01:29 +0400136func extractReq(r *http.Request) (createAccountReq, error) {
137 var req createAccountReq
Giorgi Lekveishvili6b887be2023-06-22 14:38:19 +0400138 if err := func() error {
139 var err error
Giorgi Lekveishvili123a3672023-12-04 13:01:29 +0400140 if err = r.ParseForm(); err != nil {
Giorgi Lekveishvili6b887be2023-06-22 14:38:19 +0400141 return err
142 }
gio59946282024-10-07 12:55:51 +0400143 if req.Username, err = server.GetFormValue(r.PostForm, "username"); err != nil {
Giorgi Lekveishvili6b887be2023-06-22 14:38:19 +0400144 return err
145 }
gio59946282024-10-07 12:55:51 +0400146 if req.Password, err = server.GetFormValue(r.PostForm, "password"); err != nil {
Giorgi Lekveishvili6b887be2023-06-22 14:38:19 +0400147 return err
148 }
gio59946282024-10-07 12:55:51 +0400149 if req.SecretToken, err = server.GetFormValue(r.PostForm, "secret-token"); err != nil {
Giorgi Lekveishvili6b887be2023-06-22 14:38:19 +0400150 return err
151 }
152 return nil
153 }(); err != nil {
Giorgi Lekveishvili123a3672023-12-04 13:01:29 +0400154 if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
155 return createAccountReq{}, err
Giorgi Lekveishvili6b887be2023-06-22 14:38:19 +0400156 }
Giorgi Lekveishvili12850ee2023-06-22 13:11:17 +0400157 }
Giorgi Lekveishvili123a3672023-12-04 13:01:29 +0400158 return req, nil
159}
160
giodd213152024-09-27 11:26:59 +0200161func (s *Server) renderRegistrationForm(w http.ResponseWriter, data formData) {
162 if err := s.tmpl.createAccount.Execute(w, data); err != nil {
DTabidze52593392024-03-08 12:53:20 +0400163 http.Error(w, err.Error(), http.StatusInternalServerError)
164 return
165 }
166}
167
giodd213152024-09-27 11:26:59 +0200168func (s *Server) renderRegistrationSuccess(w http.ResponseWriter, loginAddr string) {
Giorgi Lekveishvili83b72192024-03-11 18:36:14 +0400169 data := struct {
170 LoginAddr string
171 }{
172 LoginAddr: loginAddr,
173 }
giodd213152024-09-27 11:26:59 +0200174 if err := s.tmpl.createAccountSuccess.Execute(w, data); err != nil {
Giorgi Lekveishvili83b72192024-03-11 18:36:14 +0400175 http.Error(w, err.Error(), http.StatusInternalServerError)
176 return
177 }
178}
179
gio134be722025-07-20 19:01:17 +0400180type identityCreateResp struct {
181 Id string `json:"id"`
182}
183
Giorgi Lekveishvilid542b732024-03-25 18:17:39 +0400184func (s *Server) createAccount(w http.ResponseWriter, r *http.Request) {
Giorgi Lekveishvili39913692023-12-05 08:58:08 +0400185 req, err := extractReq(r)
186 if err != nil {
187 http.Error(w, err.Error(), http.StatusInternalServerError)
188 return
189 }
gio134be722025-07-20 19:01:17 +0400190 var idResp identityCreateResp
Giorgi Lekveishvilic89b9002023-12-21 13:09:26 +0400191 {
192 var buf bytes.Buffer
193 cr := apiCreateAccountReq{req.Username, req.Password}
194 if err := json.NewEncoder(&buf).Encode(cr); err != nil {
195 http.Error(w, err.Error(), http.StatusInternalServerError)
196 return
197 }
198 resp, err := http.Post(s.createAccountAddr, "application/json", &buf)
199 if err != nil {
Giorgi Lekveishvili83399052024-02-14 13:27:30 +0400200 var respBody bytes.Buffer
201 if _, err := io.Copy(&respBody, resp.Body); err != nil {
202 http.Error(w, err.Error(), http.StatusInternalServerError)
203 }
204 respStr := respBody.String()
205 log.Println(respStr)
206 http.Error(w, respStr, http.StatusInternalServerError)
Giorgi Lekveishvilic89b9002023-12-21 13:09:26 +0400207 return
208 }
Giorgi Lekveishvilic89b9002023-12-21 13:09:26 +0400209 if resp.StatusCode != http.StatusOK {
DTabidze52593392024-03-08 12:53:20 +0400210 var errResponse ErrorResponse
211 if err := json.NewDecoder(resp.Body).Decode(&errResponse); err != nil {
212 http.Error(w, "Error Decoding JSON", http.StatusInternalServerError)
213 return
214 }
215 var usernameErrors, passwordErrors []string
216 for _, err := range errResponse.Errors {
217 if err.Field == "username" {
218 usernameErrors = append(usernameErrors, err.Message)
219 }
220 if err.Field == "password" {
221 passwordErrors = append(passwordErrors, err.Message)
222 }
223 }
giodd213152024-09-27 11:26:59 +0200224 s.renderRegistrationForm(w, formData{
DTabidze52593392024-03-08 12:53:20 +0400225 usernameErrors,
226 passwordErrors,
227 req,
228 })
Giorgi Lekveishvilic89b9002023-12-21 13:09:26 +0400229 return
230 }
gio134be722025-07-20 19:01:17 +0400231 if err := json.NewDecoder(resp.Body).Decode(&idResp); err != nil {
232 http.Error(w, "Error Decoding JSON", http.StatusInternalServerError)
233 return
234 }
Giorgi Lekveishvilic89b9002023-12-21 13:09:26 +0400235 }
gio134be722025-07-20 19:01:17 +0400236 if err := s.createUser(idResp.Id, req.Username); err != nil {
Giorgi Lekveishvilid542b732024-03-25 18:17:39 +0400237 http.Error(w, err.Error(), http.StatusInternalServerError)
238 return
239 }
giodd213152024-09-27 11:26:59 +0200240 s.renderRegistrationSuccess(w, s.loginAddr)
Giorgi Lekveishvili12850ee2023-06-22 13:11:17 +0400241}
Giorgi Lekveishvilid542b732024-03-25 18:17:39 +0400242
gio2728e402024-08-01 18:14:21 +0400243type firstAccount struct {
gio134be722025-07-20 19:01:17 +0400244 Created bool `json:"created"`
245 Domain string `json:"domain"`
246 Groups []group `json:"groups"`
247}
248
249type user struct {
250 Id string `json:"id"`
251 Username string `json:"username"`
252 Email string `json:"email"`
253}
254
255type group struct {
256 Id string `json:"id"`
257 Title string `json:"title"`
258 Description string `json:"description"`
Giorgi Lekveishvilid542b732024-03-25 18:17:39 +0400259}
260
261type initRequest struct {
gio134be722025-07-20 19:01:17 +0400262 User user `json:"user"`
263 Groups []group `json:"groups"`
Giorgi Lekveishvilid542b732024-03-25 18:17:39 +0400264}
265
gio134be722025-07-20 19:01:17 +0400266func (s *Server) createUser(id, username string) error {
giob4a3a192024-08-19 09:55:47 +0400267 _, err := s.repo.Do(func(r soft.RepoFS) (string, error) {
gio2728e402024-08-01 18:14:21 +0400268 var fa firstAccount
gioe72b54f2024-04-22 10:44:41 +0400269 if err := soft.ReadYaml(r, "first-account.yaml", &fa); err != nil {
gio3af43942024-04-16 08:13:50 +0400270 return "", err
271 }
gio2728e402024-08-01 18:14:21 +0400272 var resp *http.Response
273 var err error
gio134be722025-07-20 19:01:17 +0400274 u := user{
275 id,
276 username,
277 fmt.Sprintf("%s@%s", username, fa.Domain),
278 }
gio3af43942024-04-16 08:13:50 +0400279 if fa.Created {
gio2728e402024-08-01 18:14:21 +0400280 var buf bytes.Buffer
gio134be722025-07-20 19:01:17 +0400281 if err := json.NewEncoder(&buf).Encode(u); err != nil {
gio2728e402024-08-01 18:14:21 +0400282 return "", err
283 }
284 resp, err = http.Post(
285 fmt.Sprintf("%s/api/users", s.membershipsAddr),
286 "applications/json",
287 &buf,
288 )
289 } else {
gio134be722025-07-20 19:01:17 +0400290 req := initRequest{
291 u,
292 fa.Groups,
293 }
gio2728e402024-08-01 18:14:21 +0400294 var buf bytes.Buffer
295 if err := json.NewEncoder(&buf).Encode(req); err != nil {
296 return "", err
297 }
298 resp, err = http.Post(
299 fmt.Sprintf("%s/api/init", s.membershipsAddr),
300 "applications/json",
301 &buf,
302 )
303 fa.Created = true
304 if err := soft.WriteYaml(r, "first-account.yaml", fa); err != nil {
305 return "", err
306 }
gio3af43942024-04-16 08:13:50 +0400307 }
gio4784f8e2024-08-01 15:20:12 +0400308 if err != nil {
gio3af43942024-04-16 08:13:50 +0400309 return "", err
310 }
gio4784f8e2024-08-01 15:20:12 +0400311 defer resp.Body.Close()
312 fmt.Printf("Memberships resp: %d", resp.StatusCode)
313 io.Copy(os.Stdout, resp.Body)
gio2728e402024-08-01 18:14:21 +0400314 if resp.StatusCode != http.StatusOK {
315 return "", fmt.Errorf("memberships error")
gio3af43942024-04-16 08:13:50 +0400316 }
317 return "initialized groups for first account", nil
318 })
giob4a3a192024-08-19 09:55:47 +0400319 return err
Giorgi Lekveishvilid542b732024-03-25 18:17:39 +0400320}