welcome: init group memberships for first create (#115)

* rename createAdminAccount to createAccount

* welcome: call memberships init on first user

* auth: add http endpoints to allowed return addresses

* memberships: make init user member of groups as well

---------

Co-authored-by: Giorgi Lekveishvili <lekva@gl-mbp-m1-max.local>
diff --git a/core/installer/welcome/welcome.go b/core/installer/welcome/welcome.go
index 5184a8e..eb7a2dc 100644
--- a/core/installer/welcome/welcome.go
+++ b/core/installer/welcome/welcome.go
@@ -26,11 +26,12 @@
 var staticAssets embed.FS
 
 type Server struct {
-	port              int
-	repo              installer.RepoIO
-	nsCreator         installer.NamespaceCreator
-	createAccountAddr string
-	loginAddr         string
+	port                int
+	repo                installer.RepoIO
+	nsCreator           installer.NamespaceCreator
+	createAccountAddr   string
+	loginAddr           string
+	membershipsInitAddr string
 }
 
 func NewServer(
@@ -39,6 +40,7 @@
 	nsCreator installer.NamespaceCreator,
 	createAccountAddr string,
 	loginAddr string,
+	membershipsInitAddr string,
 ) *Server {
 	return &Server{
 		port,
@@ -46,19 +48,20 @@
 		nsCreator,
 		createAccountAddr,
 		loginAddr,
+		membershipsInitAddr,
 	}
 }
 
 func (s *Server) Start() {
 	r := mux.NewRouter()
 	r.PathPrefix("/static/").Handler(http.FileServer(http.FS(staticAssets)))
-	r.Path("/").Methods("POST").HandlerFunc(s.createAdminAccount)
-	r.Path("/").Methods("GET").HandlerFunc(s.createAdminAccountForm)
+	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) createAdminAccountForm(w http.ResponseWriter, r *http.Request) {
+func (s *Server) createAccountForm(w http.ResponseWriter, r *http.Request) {
 	renderRegistrationForm(w, formData{})
 }
 
@@ -150,7 +153,7 @@
 	}
 }
 
-func (s *Server) createAdminAccount(w http.ResponseWriter, r *http.Request) {
+func (s *Server) createAccount(w http.ResponseWriter, r *http.Request) {
 	req, err := extractReq(r)
 	if err != nil {
 		http.Error(w, err.Error(), http.StatusInternalServerError)
@@ -197,6 +200,10 @@
 			return
 		}
 	}
+	if err := s.initMemberships(req.Username); err != nil {
+		http.Error(w, err.Error(), http.StatusInternalServerError)
+		return
+	}
 	{
 		config, err := s.repo.ReadConfig()
 		if err != nil {
@@ -234,3 +241,40 @@
 	}
 	renderRegistrationSuccess(w, s.loginAddr)
 }
+
+type firstaccount struct {
+	Created bool     `json:"created"`
+	Groups  []string `json:"groups"`
+}
+
+type initRequest struct {
+	Owner  string   `json:"owner"`
+	Groups []string `json:"groups"`
+}
+
+func (s *Server) initMemberships(username string) error {
+	inp, err := s.repo.Reader("first-account.yaml")
+	if err != nil {
+		return err
+	}
+	var fa firstaccount
+	if err := installer.ReadYaml(inp, &fa); err != nil {
+		return err
+	}
+	if fa.Created {
+		return nil
+	}
+	req := initRequest{username, fa.Groups}
+	var buf bytes.Buffer
+	if err := json.NewEncoder(&buf).Encode(req); err != nil {
+		return err
+	}
+	if _, err = http.Post(s.membershipsInitAddr, "applications/json", &buf); err != nil {
+		return err
+	}
+	fa.Created = true
+	if err := s.repo.WriteYaml("first-account.yaml", fa); err != nil {
+		return err
+	}
+	return s.repo.CommitAndPush("initialized groups for first account")
+}