Welcome: Create new users via Memberships API

Change-Id: Iaa12b3438340a5ca4c4fdb2157d1e8f064d56139
diff --git a/charts/welcome/templates/install.yaml b/charts/welcome/templates/install.yaml
index dfbac29..81898d4 100644
--- a/charts/welcome/templates/install.yaml
+++ b/charts/welcome/templates/install.yaml
@@ -111,7 +111,7 @@
         - --port=8080
         - --create-account-addr={{ .Values.createAccountAddr }}
         - --login-addr={{ .Values.loginAddr }}
-        - --memberships-init-addr={{ .Values.membershipsInitAddr }}
+        - --memberships-addr={{ .Values.membershipsAddr }}
         volumeMounts:
         - name: ssh-key
           readOnly: true
diff --git a/charts/welcome/values.yaml b/charts/welcome/values.yaml
index 95ac8ec..a0b42b1 100644
--- a/charts/welcome/values.yaml
+++ b/charts/welcome/values.yaml
@@ -6,7 +6,7 @@
 sshPrivateKey: key
 createAccountAddr: http://api.core-auth.svc.cluster.local/identities
 loginAddr: https://accounts-ui.example.com
-membershipsInitAddr: http://memberships.example.svc.cluster.local/api/ini
+membershipsAddr: http://memberships.example.svc.cluster.local
 ingress:
   className: pcloud-ingress-public
   domain: welcome.example.com
diff --git a/core/auth/memberships/main.go b/core/auth/memberships/main.go
index 8ef0848..0e3f2da 100644
--- a/core/auth/memberships/main.go
+++ b/core/auth/memberships/main.go
@@ -33,7 +33,7 @@
 
 type Store interface {
 	// Initializes store with admin user and their groups.
-	Init(owner string, groups []string) error
+	Init(user, email string, groups []string) error
 	CreateGroup(owner string, group Group) error
 	AddChildGroup(parent, child string) error
 	AddOwnerGroup(owned_group, owner_group string) error
@@ -137,7 +137,7 @@
 	return &SQLiteStore{db: db}, nil
 }
 
-func (s *SQLiteStore) Init(owner string, groups []string) error {
+func (s *SQLiteStore) Init(user, email string, groups []string) error {
 	tx, err := s.db.Begin()
 	if err != nil {
 		return err
@@ -151,17 +151,21 @@
 	if count != 0 {
 		return fmt.Errorf("Store already initialised")
 	}
+	query := `INSERT INTO users (username, email) VALUES (?, ?)`
+	if _, err := tx.Exec(query, user, email); err != nil {
+		return err
+	}
 	for _, g := range groups {
-		query := `INSERT INTO groups (name, description) VALUES (?, '')`
+		query = `INSERT INTO groups (name, description) VALUES (?, '')`
 		if _, err := tx.Exec(query, g); err != nil {
 			return err
 		}
 		query = `INSERT INTO owners (username, group_name) VALUES (?, ?)`
-		if _, err := tx.Exec(query, owner, g); err != nil {
+		if _, err := tx.Exec(query, user, g); err != nil {
 			return err
 		}
 		query = `INSERT INTO user_to_group (username, group_name) VALUES (?, ?)`
-		if _, err := tx.Exec(query, owner, g); err != nil {
+		if _, err := tx.Exec(query, user, g); err != nil {
 			return err
 		}
 	}
@@ -1188,7 +1192,8 @@
 }
 
 type initRequest struct {
-	Owner  string   `json:"owner"`
+	User   string   `json:"user"`
+	Email  string   `json:"email"`
 	Groups []string `json:"groups"`
 }
 
@@ -1198,7 +1203,7 @@
 		http.Error(w, err.Error(), http.StatusBadRequest)
 		return
 	}
-	if err := s.store.Init(req.Owner, req.Groups); err != nil {
+	if err := s.store.Init(req.User, req.Email, req.Groups); err != nil {
 		http.Error(w, err.Error(), http.StatusInternalServerError)
 		return
 	}
@@ -1285,34 +1290,30 @@
 	}
 }
 
+type createUserRequest struct {
+	User  string `json:"user"`
+	Email string `json:"email"`
+}
+
 func (s *Server) apiCreateUser(w http.ResponseWriter, r *http.Request) {
 	defer s.pingAllSyncAddresses()
-	selfAddress := r.FormValue("selfAddress")
-	if selfAddress != "" {
-		s.addSyncAddress(selfAddress)
-	}
-	if err := r.ParseForm(); err != nil {
+	var req createUserRequest
+	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
 		http.Error(w, "Invalid request body", http.StatusBadRequest)
 		return
 	}
-	username := r.FormValue("username")
-	email := r.FormValue("email")
-	if username == "" {
+	if req.User == "" {
 		http.Error(w, "Username cannot be empty", http.StatusBadRequest)
 		return
 	}
-	if email == "" {
+	if req.Email == "" {
 		http.Error(w, "Email cannot be empty", http.StatusBadRequest)
 		return
 	}
-	username = strings.ToLower(username)
-	email = strings.ToLower(email)
-	err := s.store.CreateUser(username, email)
-	if err != nil {
+	if err := s.store.CreateUser(strings.ToLower(req.User), strings.ToLower(req.Email)); err != nil {
 		http.Error(w, err.Error(), http.StatusInternalServerError)
 		return
 	}
-	w.WriteHeader(http.StatusOK)
 }
 
 func (s *Server) pingAllSyncAddresses() {
diff --git a/core/auth/memberships/store_test.go b/core/auth/memberships/store_test.go
index 4e9ef19..bc2e9c6 100644
--- a/core/auth/memberships/store_test.go
+++ b/core/auth/memberships/store_test.go
@@ -23,7 +23,7 @@
 	if err != nil {
 		t.Fatal(err)
 	}
-	if err := store.Init("admin", []string{"admin", "all"}); err != nil {
+	if err := store.Init("admin", "admin@admin", []string{"admin", "all"}); err != nil {
 		t.Fatal(err)
 	}
 	groups, err := store.GetGroupsOwnedBy("admin")
@@ -60,7 +60,7 @@
 	if err != nil {
 		t.Fatal(err)
 	}
-	err = store.Init("admin", []string{"admin", "all"})
+	err = store.Init("admin", "admin", []string{"admin", "all"})
 	if err == nil {
 		t.Fatal("initialisation did not fail")
 	} else if err.Error() != "Store already initialised" {
diff --git a/core/installer/cmd/welcome.go b/core/installer/cmd/welcome.go
index db1ce4a..30ed48f 100644
--- a/core/installer/cmd/welcome.go
+++ b/core/installer/cmd/welcome.go
@@ -11,12 +11,12 @@
 )
 
 var welcomeFlags struct {
-	repo                string
-	sshKey              string
-	port                int
-	createAccountAddr   string
-	loginAddr           string
-	membershipsInitAddr string
+	repo              string
+	sshKey            string
+	port              int
+	createAccountAddr string
+	loginAddr         string
+	membershipsAddr   string
 }
 
 func welcomeCmd() *cobra.Command {
@@ -55,8 +55,8 @@
 		"",
 	)
 	cmd.Flags().StringVar(
-		&welcomeFlags.membershipsInitAddr,
-		"memberships-init-addr",
+		&welcomeFlags.membershipsAddr,
+		"memberships-addr",
 		"",
 		"",
 	)
@@ -95,7 +95,7 @@
 		installer.NewGitHelmFetcher(),
 		welcomeFlags.createAccountAddr,
 		welcomeFlags.loginAddr,
-		welcomeFlags.membershipsInitAddr,
+		welcomeFlags.membershipsAddr,
 	)
 	s.Start()
 	return nil
diff --git a/core/installer/tasks/infra.go b/core/installer/tasks/infra.go
index f855adf..eb4840f 100644
--- a/core/installer/tasks/infra.go
+++ b/core/installer/tasks/infra.go
@@ -78,6 +78,7 @@
 
 type firstAccount struct {
 	Created bool     `json:"created"`
+	Domain  string   `json:"domain"`
 	Groups  []string `json:"groups"`
 }
 
@@ -88,7 +89,7 @@
 			return err
 		}
 		return r.Do(func(r soft.RepoFS) (string, error) {
-			fa := firstAccount{false, initGroups}
+			fa := firstAccount{false, env.Domain, initGroups}
 			if err := soft.WriteYaml(r, "first-account.yaml", fa); err != nil {
 				return "", err
 			}
diff --git a/core/installer/values-tmpl/welcome.cue b/core/installer/values-tmpl/welcome.cue
index 55f4e14..3e385ab 100644
--- a/core/installer/values-tmpl/welcome.cue
+++ b/core/installer/values-tmpl/welcome.cue
@@ -37,7 +37,7 @@
 			sshPrivateKey: base64.Encode(null, input.sshPrivateKey)
 			createAccountAddr: "http://api.\(global.namespacePrefix)core-auth.svc.cluster.local/identities"
 			loginAddr: "https://launcher.\(networks.public.domain)"
-			membershipsInitAddr: "http://memberships-api.\(global.namespacePrefix)core-auth-memberships.svc.cluster.local/api/init"
+			membershipsAddr: "http://memberships-api.\(global.namespacePrefix)core-auth-memberships.svc.cluster.local"
 			ingress: {
 				className: input.network.ingressClass
 				domain: "welcome.\(input.network.domain)"
diff --git a/core/installer/welcome/welcome.go b/core/installer/welcome/welcome.go
index 80bbb64..85d7f61 100644
--- a/core/installer/welcome/welcome.go
+++ b/core/installer/welcome/welcome.go
@@ -28,13 +28,13 @@
 var staticAssets embed.FS
 
 type Server struct {
-	port                int
-	repo                soft.RepoIO
-	nsCreator           installer.NamespaceCreator
-	hf                  installer.HelmFetcher
-	createAccountAddr   string
-	loginAddr           string
-	membershipsInitAddr string
+	port              int
+	repo              soft.RepoIO
+	nsCreator         installer.NamespaceCreator
+	hf                installer.HelmFetcher
+	createAccountAddr string
+	loginAddr         string
+	membershipsAddr   string
 }
 
 func NewServer(
@@ -44,7 +44,7 @@
 	hf installer.HelmFetcher,
 	createAccountAddr string,
 	loginAddr string,
-	membershipsInitAddr string,
+	membershipsAddr string,
 ) *Server {
 	return &Server{
 		port,
@@ -53,7 +53,7 @@
 		hf,
 		createAccountAddr,
 		loginAddr,
-		membershipsInitAddr,
+		membershipsAddr,
 	}
 }
 
@@ -205,7 +205,7 @@
 			return
 		}
 	}
-	if err := s.initMemberships(req.Username); err != nil {
+	if err := s.createUser(req.Username); err != nil {
 		http.Error(w, err.Error(), http.StatusInternalServerError)
 		return
 	}
@@ -245,40 +245,66 @@
 	renderRegistrationSuccess(w, s.loginAddr)
 }
 
-type firstaccount struct {
+type firstAccount struct {
 	Created bool     `json:"created"`
+	Domain  string   `json:"domain"`
 	Groups  []string `json:"groups"`
 }
 
 type initRequest struct {
-	Owner  string   `json:"owner"`
+	User   string   `json:"user"`
+	Email  string   `json:"email"`
 	Groups []string `json:"groups"`
 }
 
-func (s *Server) initMemberships(username string) error {
+type createUserRequest struct {
+	User  string `json:"user"`
+	Email string `json:"email"`
+}
+
+func (s *Server) createUser(username string) error {
 	return s.repo.Do(func(r soft.RepoFS) (string, error) {
-		var fa firstaccount
+		var fa firstAccount
 		if err := soft.ReadYaml(r, "first-account.yaml", &fa); err != nil {
 			return "", err
 		}
+		var resp *http.Response
+		var err error
 		if fa.Created {
-			return "", nil
+			req := createUserRequest{username, fmt.Sprintf("%s@%s", username, fa.Domain)}
+			var buf bytes.Buffer
+			if err := json.NewEncoder(&buf).Encode(req); err != nil {
+				return "", err
+			}
+			resp, err = http.Post(
+				fmt.Sprintf("%s/api/users", s.membershipsAddr),
+				"applications/json",
+				&buf,
+			)
+		} else {
+			req := initRequest{username, fmt.Sprintf("%s@%s", username, fa.Domain), 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
+			}
 		}
-		req := initRequest{username, fa.Groups}
-		var buf bytes.Buffer
-		if err := json.NewEncoder(&buf).Encode(req); err != nil {
-			return "", err
-		}
-		resp, err := http.Post(s.membershipsInitAddr, "applications/json", &buf)
 		if err != nil {
 			return "", err
 		}
 		defer resp.Body.Close()
 		fmt.Printf("Memberships resp: %d", resp.StatusCode)
 		io.Copy(os.Stdout, resp.Body)
-		fa.Created = true
-		if err := soft.WriteYaml(r, "first-account.yaml", fa); err != nil {
-			return "", err
+		if resp.StatusCode != http.StatusOK {
+			return "", fmt.Errorf("memberships error")
 		}
 		return "initialized groups for first account", nil
 	})