DodoApp: Use JSON file for configuration.

Specify json schema so code editors can validate user input.
Update auth proxy to disable auth on specified paths.

Change-Id: Ic6667d802a9553444d3630c4ff73f4b33304ccfd
diff --git a/core/auth/proxy/main.go b/core/auth/proxy/main.go
index 2114127..2c10258 100644
--- a/core/auth/proxy/main.go
+++ b/core/auth/proxy/main.go
@@ -25,6 +25,7 @@
 var membershipPublicAddr = flag.String("membership-public-addr", "", "Public address of membership service")
 var groups = flag.String("groups", "", "Comma separated list of groups. User must be part of at least one of them. If empty group membership will not be checked.")
 var upstream = flag.String("upstream", "", "Upstream service address")
+var noAuthPathPrefixes = flag.String("no-auth-path-prefixes", "", "Path prefixes to disable authentication for")
 
 //go:embed unauthorized.html
 var unauthorizedHTML embed.FS
@@ -92,46 +93,60 @@
 }
 
 func handle(w http.ResponseWriter, r *http.Request) {
-	user, err := queryWhoAmI(r.Cookies())
-	if err != nil {
-		http.Error(w, err.Error(), http.StatusInternalServerError)
-		return
-	}
-	if user == nil {
-		if r.Method != http.MethodGet {
-			http.Error(w, "Unauthorized", http.StatusUnauthorized)
-			return
+	reqAuth := true
+	for _, p := range strings.Split(*noAuthPathPrefixes, ",") {
+		if strings.HasPrefix(r.URL.Path, p) {
+			reqAuth = false
+			break
 		}
-		curr, err := getAddr(r)
+	}
+	var user *user
+	if reqAuth {
+		var err error
+		user, err = queryWhoAmI(r.Cookies())
 		if err != nil {
 			http.Error(w, err.Error(), http.StatusInternalServerError)
 			return
 		}
-		addr := fmt.Sprintf("%s?return_to=%s", *loginAddr, curr.String())
-		http.Redirect(w, r, addr, http.StatusSeeOther)
-		return
-	}
-	if *groups != "" {
-		hasPermission := false
-		tg, err := getTransitiveGroups(user.Identity.Traits.Username)
-		if err != nil {
-			http.Error(w, err.Error(), http.StatusInternalServerError)
-			return
-		}
-		for _, i := range strings.Split(*groups, ",") {
-			if slices.Contains(tg, strings.TrimSpace(i)) {
-				hasPermission = true
-				break
+		if user == nil {
+			if r.Method != http.MethodGet {
+				http.Error(w, "Unauthorized", http.StatusUnauthorized)
+				return
 			}
-		}
-		if !hasPermission {
-			groupList := strings.Split(*groups, ",")
-			renderUnauthorizedPage(w, groupList)
+			curr, err := getAddr(r)
+			if err != nil {
+				http.Error(w, err.Error(), http.StatusInternalServerError)
+				return
+			}
+			addr := fmt.Sprintf("%s?return_to=%s", *loginAddr, curr.String())
+			http.Redirect(w, r, addr, http.StatusSeeOther)
 			return
 		}
+		if *groups != "" {
+			hasPermission := false
+			tg, err := getTransitiveGroups(user.Identity.Traits.Username)
+			if err != nil {
+				http.Error(w, err.Error(), http.StatusInternalServerError)
+				return
+			}
+			for _, i := range strings.Split(*groups, ",") {
+				if slices.Contains(tg, strings.TrimSpace(i)) {
+					hasPermission = true
+					break
+				}
+			}
+			if !hasPermission {
+				groupList := strings.Split(*groups, ",")
+				renderUnauthorizedPage(w, groupList)
+				return
+			}
+		}
 	}
 	rc := r.Clone(context.Background())
-	rc.Header.Add("X-User", user.Identity.Traits.Username)
+	if user != nil {
+		// TODO(gio): Rename to X-Forwarded-User
+		rc.Header.Add("X-User", user.Identity.Traits.Username)
+	}
 	ru, err := url.Parse(fmt.Sprintf("http://%s%s", *upstream, r.URL.RequestURI()))
 	if err != nil {
 		http.Error(w, err.Error(), http.StatusInternalServerError)
@@ -237,7 +252,7 @@
 	if *groups != "" && (*membershipAddr == "" || *membershipPublicAddr == "") {
 		log.Fatal("membership-addr and membership-public-addr flags are required when groups are provided")
 	}
-	http.Handle("/static/", cachingHandler{http.FileServer(http.FS(f))})
+	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)
 	log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", *port), nil))
diff --git a/core/auth/proxy/unauthorized.html b/core/auth/proxy/unauthorized.html
index 35fafab..130cf81 100644
--- a/core/auth/proxy/unauthorized.html
+++ b/core/auth/proxy/unauthorized.html
@@ -4,8 +4,8 @@
     <meta charset="UTF-8">
     <meta name="viewport" content="width=device-width, initial-scale=1.0">
     <title>Auth Proxy</title>
-    <link rel="stylesheet" href="/static/pico.2.0.6.min.css">
-    <link rel="stylesheet" href="/static/main.css?v=0.0.1">
+    <link rel="stylesheet" href="/.auth/static/pico.2.0.6.min.css">
+    <link rel="stylesheet" href="/.auth/static/main.css?v=0.0.1">
     <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/hack-font/3.3.0/web/hack.min.css">
 </head>
 <body>