| package welcome |
| |
| import ( |
| "bytes" |
| "embed" |
| "encoding/json" |
| "fmt" |
| "html/template" |
| "io" |
| "log" |
| "net/http" |
| "os" |
| |
| "github.com/gorilla/mux" |
| |
| "github.com/giolekva/pcloud/core/installer" |
| "github.com/giolekva/pcloud/core/installer/server" |
| "github.com/giolekva/pcloud/core/installer/soft" |
| ) |
| |
| //go:embed templates/* |
| var tmpls embed.FS |
| |
| //go:embed static/* |
| var staticAssets embed.FS |
| |
| type templates struct { |
| createAccount *template.Template |
| createAccountSuccess *template.Template |
| } |
| |
| func parseTemplatesWelcome(fs embed.FS) (templates, error) { |
| base, err := template.New("base.html").ParseFS(fs, "templates/base.html") |
| if err != nil { |
| return templates{}, err |
| } |
| parse := func(path string) (*template.Template, error) { |
| if b, err := base.Clone(); err != nil { |
| return nil, err |
| } else { |
| return b.ParseFS(fs, path) |
| } |
| } |
| createAccount, err := parse("templates/create-account.html") |
| if err != nil { |
| return templates{}, err |
| } |
| createAccountSuccess, err := parse("templates/create-account-success.html") |
| if err != nil { |
| return templates{}, err |
| } |
| return templates{createAccount, createAccountSuccess}, nil |
| } |
| |
| type Server struct { |
| port int |
| repo soft.RepoIO |
| nsCreator installer.NamespaceCreator |
| hf installer.HelmFetcher |
| createAccountAddr string |
| loginAddr string |
| membershipsAddr string |
| tmpl templates |
| } |
| |
| func NewServer( |
| port int, |
| repo soft.RepoIO, |
| nsCreator installer.NamespaceCreator, |
| hf installer.HelmFetcher, |
| createAccountAddr string, |
| loginAddr string, |
| membershipsAddr string, |
| ) (*Server, error) { |
| tmplts, err := parseTemplatesWelcome(tmpls) |
| if err != nil { |
| return nil, err |
| } |
| return &Server{ |
| port, |
| repo, |
| nsCreator, |
| hf, |
| createAccountAddr, |
| loginAddr, |
| membershipsAddr, |
| tmplts, |
| }, nil |
| } |
| |
| func (s *Server) Start() { |
| r := mux.NewRouter() |
| r.PathPrefix("/static/").Handler(server.NewCachingHandler(http.FileServer(http.FS(staticAssets)))) |
| r.Path("/").Methods("POST").HandlerFunc(s.createAccount) |
| r.Path("/").Methods("GET").HandlerFunc(s.createAccountForm) |
| http.Handle("/", r) |
| log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", s.port), nil)) |
| } |
| |
| func (s *Server) createAccountForm(w http.ResponseWriter, r *http.Request) { |
| s.renderRegistrationForm(w, formData{}) |
| } |
| |
| type formData struct { |
| UsernameErrors []string |
| PasswordErrors []string |
| Data createAccountReq |
| } |
| |
| type cpFormData struct { |
| UsernameErrors []string |
| PasswordErrors []string |
| Password string |
| } |
| |
| type createAccountReq struct { |
| Username string `json:"username,omitempty"` |
| Password string `json:"password,omitempty"` |
| SecretToken string `json:"secretToken,omitempty"` |
| } |
| |
| type apiCreateAccountReq struct { |
| Username string `json:"username,omitempty"` |
| Password string `json:"password,omitempty"` |
| } |
| |
| type ValidationError struct { |
| Field string `json:"field"` |
| Message string `json:"message"` |
| } |
| |
| type ErrorResponse struct { |
| Errors []ValidationError `json:"errors"` |
| } |
| |
| func extractReq(r *http.Request) (createAccountReq, error) { |
| var req createAccountReq |
| if err := func() error { |
| var err error |
| if err = r.ParseForm(); err != nil { |
| return err |
| } |
| if req.Username, err = server.GetFormValue(r.PostForm, "username"); err != nil { |
| return err |
| } |
| if req.Password, err = server.GetFormValue(r.PostForm, "password"); err != nil { |
| return err |
| } |
| if req.SecretToken, err = server.GetFormValue(r.PostForm, "secret-token"); err != nil { |
| return err |
| } |
| return nil |
| }(); err != nil { |
| if err := json.NewDecoder(r.Body).Decode(&req); err != nil { |
| return createAccountReq{}, err |
| } |
| } |
| return req, nil |
| } |
| |
| func (s *Server) renderRegistrationForm(w http.ResponseWriter, data formData) { |
| if err := s.tmpl.createAccount.Execute(w, data); err != nil { |
| http.Error(w, err.Error(), http.StatusInternalServerError) |
| return |
| } |
| } |
| |
| func (s *Server) renderRegistrationSuccess(w http.ResponseWriter, loginAddr string) { |
| data := struct { |
| LoginAddr string |
| }{ |
| LoginAddr: loginAddr, |
| } |
| if err := s.tmpl.createAccountSuccess.Execute(w, data); err != nil { |
| http.Error(w, err.Error(), http.StatusInternalServerError) |
| return |
| } |
| } |
| |
| type identityCreateResp struct { |
| Id string `json:"id"` |
| } |
| |
| func (s *Server) createAccount(w http.ResponseWriter, r *http.Request) { |
| req, err := extractReq(r) |
| if err != nil { |
| http.Error(w, err.Error(), http.StatusInternalServerError) |
| return |
| } |
| var idResp identityCreateResp |
| { |
| var buf bytes.Buffer |
| cr := apiCreateAccountReq{req.Username, req.Password} |
| if err := json.NewEncoder(&buf).Encode(cr); err != nil { |
| http.Error(w, err.Error(), http.StatusInternalServerError) |
| return |
| } |
| resp, err := http.Post(s.createAccountAddr, "application/json", &buf) |
| if err != nil { |
| var respBody bytes.Buffer |
| if _, err := io.Copy(&respBody, resp.Body); err != nil { |
| http.Error(w, err.Error(), http.StatusInternalServerError) |
| } |
| respStr := respBody.String() |
| log.Println(respStr) |
| http.Error(w, respStr, http.StatusInternalServerError) |
| return |
| } |
| if resp.StatusCode != http.StatusOK { |
| var errResponse ErrorResponse |
| if err := json.NewDecoder(resp.Body).Decode(&errResponse); err != nil { |
| http.Error(w, "Error Decoding JSON", http.StatusInternalServerError) |
| return |
| } |
| var usernameErrors, passwordErrors []string |
| for _, err := range errResponse.Errors { |
| if err.Field == "username" { |
| usernameErrors = append(usernameErrors, err.Message) |
| } |
| if err.Field == "password" { |
| passwordErrors = append(passwordErrors, err.Message) |
| } |
| } |
| s.renderRegistrationForm(w, formData{ |
| usernameErrors, |
| passwordErrors, |
| req, |
| }) |
| return |
| } |
| if err := json.NewDecoder(resp.Body).Decode(&idResp); err != nil { |
| http.Error(w, "Error Decoding JSON", http.StatusInternalServerError) |
| return |
| } |
| } |
| if err := s.createUser(idResp.Id, req.Username); err != nil { |
| http.Error(w, err.Error(), http.StatusInternalServerError) |
| return |
| } |
| s.renderRegistrationSuccess(w, s.loginAddr) |
| } |
| |
| type firstAccount struct { |
| Created bool `json:"created"` |
| Domain string `json:"domain"` |
| Groups []group `json:"groups"` |
| } |
| |
| type user struct { |
| Id string `json:"id"` |
| Username string `json:"username"` |
| Email string `json:"email"` |
| } |
| |
| type group struct { |
| Id string `json:"id"` |
| Title string `json:"title"` |
| Description string `json:"description"` |
| } |
| |
| type initRequest struct { |
| User user `json:"user"` |
| Groups []group `json:"groups"` |
| } |
| |
| func (s *Server) createUser(id, username string) error { |
| _, err := s.repo.Do(func(r soft.RepoFS) (string, error) { |
| var fa firstAccount |
| if err := soft.ReadYaml(r, "first-account.yaml", &fa); err != nil { |
| return "", err |
| } |
| var resp *http.Response |
| var err error |
| u := user{ |
| id, |
| username, |
| fmt.Sprintf("%s@%s", username, fa.Domain), |
| } |
| if fa.Created { |
| var buf bytes.Buffer |
| if err := json.NewEncoder(&buf).Encode(u); err != nil { |
| return "", err |
| } |
| resp, err = http.Post( |
| fmt.Sprintf("%s/api/users", s.membershipsAddr), |
| "applications/json", |
| &buf, |
| ) |
| } else { |
| req := initRequest{ |
| u, |
| fa.Groups, |
| } |
| var buf bytes.Buffer |
| if err := json.NewEncoder(&buf).Encode(req); err != nil { |
| return "", err |
| } |
| resp, err = http.Post( |
| fmt.Sprintf("%s/api/init", s.membershipsAddr), |
| "applications/json", |
| &buf, |
| ) |
| fa.Created = true |
| if err := soft.WriteYaml(r, "first-account.yaml", fa); err != nil { |
| return "", err |
| } |
| } |
| if err != nil { |
| return "", err |
| } |
| defer resp.Body.Close() |
| fmt.Printf("Memberships resp: %d", resp.StatusCode) |
| io.Copy(os.Stdout, resp.Body) |
| if resp.StatusCode != http.StatusOK { |
| return "", fmt.Errorf("memberships error") |
| } |
| return "initialized groups for first account", nil |
| }) |
| return err |
| } |