Memebrships: Refactor Store interface

Use unified memberships table.
Add few internal API endpoints.

Change-Id: I80ac5a0f5c262e04d7898cca571b938a35d68d39
diff --git a/core/auth/proxy/main.go b/core/auth/proxy/main.go
index f8ab620..a4a2efc 100644
--- a/core/auth/proxy/main.go
+++ b/core/auth/proxy/main.go
@@ -35,6 +35,7 @@
 var f embed.FS
 
 var noAuthPathRegexps []*regexp.Regexp
+var allowedGroups []Group
 
 func initPathPatterns() error {
 	for _, p := range strings.Split(*noAuthPathPatterns, ",") {
@@ -51,6 +52,38 @@
 	return nil
 }
 
+type getGroupReq struct {
+	GroupId string `json:"groupId"`
+}
+
+type getGroupResp struct {
+	Self Group `json:"self"`
+}
+
+func initAllowedGroups() error {
+	for _, groupId := range strings.Split(*groups, ",") {
+		gid := strings.TrimSpace(groupId)
+		if len(gid) == 0 {
+			continue
+		}
+		var buf bytes.Buffer
+		if err := json.NewEncoder(&buf).Encode(getGroupReq{gid}); err != nil {
+			return err
+		}
+		resp, err := http.Post(fmt.Sprintf("%s/api/get-group", *membershipAddr), "application/json", &buf)
+		if err != nil {
+			return err
+		}
+		var info getGroupResp
+		if err := json.NewDecoder(resp.Body).Decode(&info); err != nil {
+			return err
+		}
+		fmt.Println(info.Self)
+		allowedGroups = append(allowedGroups, info.Self)
+	}
+	return nil
+}
+
 type cachingHandler struct {
 	h http.Handler
 }
@@ -91,7 +124,7 @@
 
 type UnauthorizedPageData struct {
 	MembershipPublicAddr string
-	Groups               []string
+	Groups               []Group
 }
 
 func renderUnauthorizedPage(w http.ResponseWriter, groups []string) {
@@ -102,7 +135,7 @@
 	}
 	data := UnauthorizedPageData{
 		MembershipPublicAddr: *membershipPublicAddr,
-		Groups:               groups,
+		Groups:               allowedGroups,
 	}
 	w.Header().Set("Content-Type", "text/html")
 	w.WriteHeader(http.StatusUnauthorized)
@@ -141,13 +174,13 @@
 		}
 		if *groups != "" {
 			hasPermission := false
-			tg, err := getTransitiveGroups(user.Identity.Traits.Username)
+			tg, err := getGroupsUserCanActAs(user.Identity.Id)
 			if err != nil {
 				http.Error(w, err.Error(), http.StatusInternalServerError)
 				return
 			}
-			for _, i := range strings.Split(*groups, ",") {
-				if slices.Contains(tg, strings.TrimSpace(i)) {
+			for _, i := range allowedGroups {
+				if slices.Contains(tg, i) {
 					hasPermission = true
 					break
 				}
@@ -219,44 +252,35 @@
 	if err != nil {
 		return nil, err
 	}
-	data := make(map[string]any)
-	if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
-		return nil, err
-	}
-	// TODO(gio): remove debugging
-	b, err := json.MarshalIndent(data, "", "  ")
-	if err != nil {
-		return nil, err
-	}
-	fmt.Println(string(b))
-	var buf bytes.Buffer
-	if err := json.NewEncoder(&buf).Encode(data); err != nil {
-		return nil, err
-	}
-	tmp := buf.String()
 	if resp.StatusCode == http.StatusOK {
 		u := &user{}
-		if err := json.NewDecoder(strings.NewReader(tmp)).Decode(u); err != nil {
+		if err := json.NewDecoder(resp.Body).Decode(u); err != nil {
 			return nil, err
 		}
 		return u, nil
 	}
 	e := &authError{}
-	if err := json.NewDecoder(strings.NewReader(tmp)).Decode(e); err != nil {
+	if err := json.NewDecoder(resp.Body).Decode(e); err != nil {
 		return nil, err
 	}
 	if e.Error.Status == "Unauthorized" {
 		return nil, nil
 	}
-	return nil, fmt.Errorf("Unknown error: %s", tmp)
+	return nil, fmt.Errorf("Unknown error")
+}
+
+type Group struct {
+	Id          string `json:"id"`
+	Title       string `json:"title"`
+	Description string `json:"description"`
 }
 
 type MembershipInfo struct {
-	MemberOf []string `json:"memberOf"`
+	CanActAs []Group `json:"canActAs"`
 }
 
-func getTransitiveGroups(user string) ([]string, error) {
-	resp, err := http.Get(fmt.Sprintf("%s/%s", *membershipAddr, user))
+func getGroupsUserCanActAs(user string) ([]Group, error) {
+	resp, err := http.Get(fmt.Sprintf("%s/api/user/%s", *membershipAddr, user))
 	if err != nil {
 		return nil, err
 	}
@@ -264,7 +288,7 @@
 	if err := json.NewDecoder(resp.Body).Decode(&info); err != nil {
 		return nil, err
 	}
-	return info.MemberOf, nil
+	return info.CanActAs, nil
 }
 
 func main() {
@@ -275,6 +299,9 @@
 	if err := initPathPatterns(); err != nil {
 		log.Fatal(err)
 	}
+	if err := initAllowedGroups(); err != nil {
+		log.Fatal(err)
+	}
 	http.Handle("/.auth/static/", http.StripPrefix("/.auth", cachingHandler{http.FileServer(http.FS(f))}))
 	http.HandleFunc("/", handle)
 	fmt.Printf("Starting HTTP server on port: %d\n", *port)
diff --git a/core/auth/proxy/unauthorized.html b/core/auth/proxy/unauthorized.html
index 130cf81..28c8913 100644
--- a/core/auth/proxy/unauthorized.html
+++ b/core/auth/proxy/unauthorized.html
@@ -15,7 +15,7 @@
             <p>
                 Only members of
                 {{ range $index, $group := .Groups }}
-                <a href="{{ $.MembershipPublicAddr }}/group/{{ $group }}">{{ $group }}</a>{{ if not (IsLast $index $.Groups) }},{{ end }}
+                <a href="{{ $.MembershipPublicAddr }}/group/{{ $group.Id }}">{{ $group.Title }}</a>{{ if not (IsLast $index $.Groups) }},{{ end }}
                 {{ end }}
                 are authorized to access this page.
             </p>