blob: 23f9e001cb09060e854275629d13b55598b136e0 [file] [log] [blame]
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
}