blob: b6436c71ebe54b2b87ed1f1535876f56bac0768f [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
Giorgi Lekveishvili260a97d2023-12-08 15:04:16 +040021//go:embed create-account.html
Giorgi Lekveishvili123a3672023-12-04 13:01:29 +040022var indexHtml []byte
Giorgi Lekveishvili12850ee2023-06-22 13:11:17 +040023
Giorgi Lekveishvili83b72192024-03-11 18:36:14 +040024//go:embed create-account-success.html
25var successHtml []byte
26
Giorgi Lekveishvili12850ee2023-06-22 13:11:17 +040027//go:embed static/*
28var staticAssets embed.FS
29
gio1bf00802024-08-17 12:31:41 +040030//go:embed stat/*
31var statAssets embed.FS
32
Giorgi Lekveishvili12850ee2023-06-22 13:11:17 +040033type Server struct {
gio2728e402024-08-01 18:14:21 +040034 port int
35 repo soft.RepoIO
36 nsCreator installer.NamespaceCreator
37 hf installer.HelmFetcher
38 createAccountAddr string
39 loginAddr string
40 membershipsAddr string
Giorgi Lekveishvili12850ee2023-06-22 13:11:17 +040041}
42
Giorgi Lekveishvilic89b9002023-12-21 13:09:26 +040043func NewServer(
44 port int,
gioe72b54f2024-04-22 10:44:41 +040045 repo soft.RepoIO,
Giorgi Lekveishvilic89b9002023-12-21 13:09:26 +040046 nsCreator installer.NamespaceCreator,
giof8843412024-05-22 16:38:05 +040047 hf installer.HelmFetcher,
Giorgi Lekveishvilic89b9002023-12-21 13:09:26 +040048 createAccountAddr string,
Giorgi Lekveishvili83b72192024-03-11 18:36:14 +040049 loginAddr string,
gio2728e402024-08-01 18:14:21 +040050 membershipsAddr string,
Giorgi Lekveishvilic89b9002023-12-21 13:09:26 +040051) *Server {
Giorgi Lekveishvili12850ee2023-06-22 13:11:17 +040052 return &Server{
53 port,
54 repo,
Giorgi Lekveishvili7fb28bf2023-06-24 19:51:16 +040055 nsCreator,
giof8843412024-05-22 16:38:05 +040056 hf,
Giorgi Lekveishvilic89b9002023-12-21 13:09:26 +040057 createAccountAddr,
Giorgi Lekveishvili83b72192024-03-11 18:36:14 +040058 loginAddr,
gio2728e402024-08-01 18:14:21 +040059 membershipsAddr,
Giorgi Lekveishvili12850ee2023-06-22 13:11:17 +040060 }
61}
62
63func (s *Server) Start() {
Giorgi Lekveishvili123a3672023-12-04 13:01:29 +040064 r := mux.NewRouter()
gio1bf00802024-08-17 12:31:41 +040065 r.PathPrefix("/stat/").Handler(cachingHandler{http.FileServer(http.FS(statAssets))})
Giorgi Lekveishvilid542b732024-03-25 18:17:39 +040066 r.Path("/").Methods("POST").HandlerFunc(s.createAccount)
67 r.Path("/").Methods("GET").HandlerFunc(s.createAccountForm)
Giorgi Lekveishvili123a3672023-12-04 13:01:29 +040068 http.Handle("/", r)
69 log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", s.port), nil))
Giorgi Lekveishvili12850ee2023-06-22 13:11:17 +040070}
71
Giorgi Lekveishvilid542b732024-03-25 18:17:39 +040072func (s *Server) createAccountForm(w http.ResponseWriter, r *http.Request) {
DTabidze52593392024-03-08 12:53:20 +040073 renderRegistrationForm(w, formData{})
74}
75
76type formData struct {
77 UsernameErrors []string
78 PasswordErrors []string
79 Data createAccountReq
Giorgi Lekveishvili12850ee2023-06-22 13:11:17 +040080}
81
Giorgi Lekveishvili123a3672023-12-04 13:01:29 +040082type createAccountReq struct {
Giorgi Lekveishvili260a97d2023-12-08 15:04:16 +040083 Username string `json:"username,omitempty"`
Giorgi Lekveishvilic89b9002023-12-21 13:09:26 +040084 Password string `json:"password,omitempty"`
Giorgi Lekveishvili260a97d2023-12-08 15:04:16 +040085 SecretToken string `json:"secretToken,omitempty"`
Giorgi Lekveishvili12850ee2023-06-22 13:11:17 +040086}
87
Giorgi Lekveishvilic89b9002023-12-21 13:09:26 +040088type apiCreateAccountReq struct {
89 Username string `json:"username,omitempty"`
90 Password string `json:"password,omitempty"`
91}
92
DTabidze52593392024-03-08 12:53:20 +040093type ValidationError struct {
94 Field string `json:"field"`
95 Message string `json:"message"`
96}
97
98type ErrorResponse struct {
99 Errors []ValidationError `json:"errors"`
100}
101
Giorgi Lekveishvili6b887be2023-06-22 14:38:19 +0400102func getFormValue(v url.Values, name string) (string, error) {
103 items, ok := v[name]
104 if !ok || len(items) != 1 {
105 return "", fmt.Errorf("%s not found", name)
106 }
107 return items[0], nil
108}
109
Giorgi Lekveishvili123a3672023-12-04 13:01:29 +0400110func extractReq(r *http.Request) (createAccountReq, error) {
111 var req createAccountReq
Giorgi Lekveishvili6b887be2023-06-22 14:38:19 +0400112 if err := func() error {
113 var err error
Giorgi Lekveishvili123a3672023-12-04 13:01:29 +0400114 if err = r.ParseForm(); err != nil {
Giorgi Lekveishvili6b887be2023-06-22 14:38:19 +0400115 return err
116 }
Giorgi Lekveishvili123a3672023-12-04 13:01:29 +0400117 if req.Username, err = getFormValue(r.PostForm, "username"); err != nil {
Giorgi Lekveishvili6b887be2023-06-22 14:38:19 +0400118 return err
119 }
Giorgi Lekveishvili123a3672023-12-04 13:01:29 +0400120 if req.Password, err = getFormValue(r.PostForm, "password"); err != nil {
Giorgi Lekveishvili6b887be2023-06-22 14:38:19 +0400121 return err
122 }
Giorgi Lekveishvili123a3672023-12-04 13:01:29 +0400123 if req.SecretToken, err = getFormValue(r.PostForm, "secret-token"); err != nil {
Giorgi Lekveishvili6b887be2023-06-22 14:38:19 +0400124 return err
125 }
126 return nil
127 }(); err != nil {
Giorgi Lekveishvili123a3672023-12-04 13:01:29 +0400128 if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
129 return createAccountReq{}, err
Giorgi Lekveishvili6b887be2023-06-22 14:38:19 +0400130 }
Giorgi Lekveishvili12850ee2023-06-22 13:11:17 +0400131 }
Giorgi Lekveishvili123a3672023-12-04 13:01:29 +0400132 return req, nil
133}
134
DTabidze52593392024-03-08 12:53:20 +0400135func renderRegistrationForm(w http.ResponseWriter, data formData) {
136 tmpl, err := template.New("create-account").Parse(string(indexHtml))
137 if err != nil {
138 http.Error(w, err.Error(), http.StatusInternalServerError)
139 return
140 }
141 if err := tmpl.Execute(w, data); err != nil {
142 http.Error(w, err.Error(), http.StatusInternalServerError)
143 return
144 }
145}
146
Giorgi Lekveishvili83b72192024-03-11 18:36:14 +0400147func renderRegistrationSuccess(w http.ResponseWriter, loginAddr string) {
148 data := struct {
149 LoginAddr string
150 }{
151 LoginAddr: loginAddr,
152 }
153 tmpl, err := template.New("create-account-success").Parse(string(successHtml))
154 if err != nil {
155 http.Error(w, err.Error(), http.StatusInternalServerError)
156 return
157 }
158 if err := tmpl.Execute(w, data); err != nil {
159 http.Error(w, err.Error(), http.StatusInternalServerError)
160 return
161 }
162}
163
Giorgi Lekveishvilid542b732024-03-25 18:17:39 +0400164func (s *Server) createAccount(w http.ResponseWriter, r *http.Request) {
Giorgi Lekveishvili39913692023-12-05 08:58:08 +0400165 req, err := extractReq(r)
166 if err != nil {
167 http.Error(w, err.Error(), http.StatusInternalServerError)
168 return
169 }
Giorgi Lekveishvilic89b9002023-12-21 13:09:26 +0400170 {
171 var buf bytes.Buffer
172 cr := apiCreateAccountReq{req.Username, req.Password}
173 if err := json.NewEncoder(&buf).Encode(cr); err != nil {
174 http.Error(w, err.Error(), http.StatusInternalServerError)
175 return
176 }
177 resp, err := http.Post(s.createAccountAddr, "application/json", &buf)
178 if err != nil {
Giorgi Lekveishvili83399052024-02-14 13:27:30 +0400179 var respBody bytes.Buffer
180 if _, err := io.Copy(&respBody, resp.Body); err != nil {
181 http.Error(w, err.Error(), http.StatusInternalServerError)
182 }
183 respStr := respBody.String()
184 log.Println(respStr)
185 http.Error(w, respStr, http.StatusInternalServerError)
Giorgi Lekveishvilic89b9002023-12-21 13:09:26 +0400186 return
187 }
Giorgi Lekveishvilic89b9002023-12-21 13:09:26 +0400188 if resp.StatusCode != http.StatusOK {
DTabidze52593392024-03-08 12:53:20 +0400189 var errResponse ErrorResponse
190 if err := json.NewDecoder(resp.Body).Decode(&errResponse); err != nil {
191 http.Error(w, "Error Decoding JSON", http.StatusInternalServerError)
192 return
193 }
194 var usernameErrors, passwordErrors []string
195 for _, err := range errResponse.Errors {
196 if err.Field == "username" {
197 usernameErrors = append(usernameErrors, err.Message)
198 }
199 if err.Field == "password" {
200 passwordErrors = append(passwordErrors, err.Message)
201 }
202 }
203 renderRegistrationForm(w, formData{
204 usernameErrors,
205 passwordErrors,
206 req,
207 })
Giorgi Lekveishvilic89b9002023-12-21 13:09:26 +0400208 return
209 }
210 }
gio2728e402024-08-01 18:14:21 +0400211 if err := s.createUser(req.Username); err != nil {
Giorgi Lekveishvilid542b732024-03-25 18:17:39 +0400212 http.Error(w, err.Error(), http.StatusInternalServerError)
213 return
214 }
Giorgi Lekveishvili83b72192024-03-11 18:36:14 +0400215 renderRegistrationSuccess(w, s.loginAddr)
Giorgi Lekveishvili12850ee2023-06-22 13:11:17 +0400216}
Giorgi Lekveishvilid542b732024-03-25 18:17:39 +0400217
gio2728e402024-08-01 18:14:21 +0400218type firstAccount struct {
Giorgi Lekveishvilid542b732024-03-25 18:17:39 +0400219 Created bool `json:"created"`
gio2728e402024-08-01 18:14:21 +0400220 Domain string `json:"domain"`
Giorgi Lekveishvilid542b732024-03-25 18:17:39 +0400221 Groups []string `json:"groups"`
222}
223
224type initRequest struct {
gio2728e402024-08-01 18:14:21 +0400225 User string `json:"user"`
226 Email string `json:"email"`
Giorgi Lekveishvilid542b732024-03-25 18:17:39 +0400227 Groups []string `json:"groups"`
228}
229
gio2728e402024-08-01 18:14:21 +0400230type createUserRequest struct {
231 User string `json:"user"`
232 Email string `json:"email"`
233}
234
235func (s *Server) createUser(username string) error {
giob4a3a192024-08-19 09:55:47 +0400236 _, err := s.repo.Do(func(r soft.RepoFS) (string, error) {
gio2728e402024-08-01 18:14:21 +0400237 var fa firstAccount
gioe72b54f2024-04-22 10:44:41 +0400238 if err := soft.ReadYaml(r, "first-account.yaml", &fa); err != nil {
gio3af43942024-04-16 08:13:50 +0400239 return "", err
240 }
gio2728e402024-08-01 18:14:21 +0400241 var resp *http.Response
242 var err error
gio3af43942024-04-16 08:13:50 +0400243 if fa.Created {
gio2728e402024-08-01 18:14:21 +0400244 req := createUserRequest{username, fmt.Sprintf("%s@%s", username, fa.Domain)}
245 var buf bytes.Buffer
246 if err := json.NewEncoder(&buf).Encode(req); err != nil {
247 return "", err
248 }
249 resp, err = http.Post(
250 fmt.Sprintf("%s/api/users", s.membershipsAddr),
251 "applications/json",
252 &buf,
253 )
254 } else {
255 req := initRequest{username, fmt.Sprintf("%s@%s", username, fa.Domain), fa.Groups}
256 var buf bytes.Buffer
257 if err := json.NewEncoder(&buf).Encode(req); err != nil {
258 return "", err
259 }
260 resp, err = http.Post(
261 fmt.Sprintf("%s/api/init", s.membershipsAddr),
262 "applications/json",
263 &buf,
264 )
265 fa.Created = true
266 if err := soft.WriteYaml(r, "first-account.yaml", fa); err != nil {
267 return "", err
268 }
gio3af43942024-04-16 08:13:50 +0400269 }
gio4784f8e2024-08-01 15:20:12 +0400270 if err != nil {
gio3af43942024-04-16 08:13:50 +0400271 return "", err
272 }
gio4784f8e2024-08-01 15:20:12 +0400273 defer resp.Body.Close()
274 fmt.Printf("Memberships resp: %d", resp.StatusCode)
275 io.Copy(os.Stdout, resp.Body)
gio2728e402024-08-01 18:14:21 +0400276 if resp.StatusCode != http.StatusOK {
277 return "", fmt.Errorf("memberships error")
gio3af43942024-04-16 08:13:50 +0400278 }
279 return "initialized groups for first account", nil
280 })
giob4a3a192024-08-19 09:55:47 +0400281 return err
Giorgi Lekveishvilid542b732024-03-25 18:17:39 +0400282}