main.go (183 lines of code) (raw):

package main import ( "fmt" "html/template" "net/http" "os" "strings" "github.com/gobuffalo/flect" "github.com/gorilla/mux" "github.com/gorilla/sessions" "github.com/markbates/goth" "github.com/markbates/goth/gothic" "github.com/markbates/goth/providers/auth0" "github.com/mozilla/protodash/pkce" "github.com/rs/zerolog/log" "gopkg.in/yaml.v3" ) func main() { // load config file cfg, err := LoadConfig() if err != nil { log.Panic().Err(err).Send() } s := &Server{config: cfg} // configure logging configureLogging(cfg.LogLevel) // load dashboard configs dashboards, err := loadDashboards(cfg.ConfigFile, cfg) if err != nil { log.Fatal().Err(err).Send() } // parse index template tmpl, err := template.ParseFiles("index.gohtml") if err != nil { log.Fatal().Err(err).Send() } // create chain with http loggin public := newLoggingChain() private := public r := mux.NewRouter() r.StrictSlash(true) bd := cfg.BaseDomain bdr := r.Host(bd).Subrouter() // configure authentication if enabled if cfg.OAuthEnabled { cookieStore := sessions.NewCookieStore([]byte(cfg.SessionSecret)) cookieStore.Options.HttpOnly = true parts := strings.Split(cfg.BaseDomain, ":") cookieStore.Options.Domain = parts[0] gothic.Store = cookieStore s.sessionStore = cookieStore pkceProvider := pkce.New( cfg.OAuthClientID, cfg.OAuthRedirectURI, cfg.OAuthDomain, ) auth0Provider := auth0.New( cfg.OAuthClientID, cfg.OAuthClientSecret, cfg.OAuthRedirectURI, cfg.OAuthDomain, "openid", "profile", "email", ) goth.UseProviders( pkceProvider, auth0Provider, ) providerName := auth0Provider.Name() if cfg.OAuthClientSecret == "" { providerName = pkceProvider.Name() } gothic.GetProviderName = func(req *http.Request) (string, error) { return providerName, nil } log.Info().Msgf("enabling authentication with %s provider", providerName) bdr.Handle("/auth/login", public.Then(s.authLogin())).Methods("GET") bdr.Handle("/auth/callback", public.Then(s.authCallback())).Methods("GET") bdr.Handle("/auth/logout", public.Then(s.authLogout())).Methods("GET") private = public.Append(s.requireAuth) } // iterate over the dashboards and mount them for _, dashboard := range dashboards { log.Info().Msgf("mounting %s at /%s/", dashboard.Name, dashboard.Slug) chain := private if dashboard.Public { chain = public } sd := dashboard.Slug + "." + cfg.BaseDomain bdp := "/" + dashboard.Slug + "/" sdp := "/" sdr := r.Host(sd).Subrouter() bdghr := bdr.Methods("GET", "HEAD").Subrouter() sdghr := sdr.Methods("GET", "HEAD").Subrouter() var bdh http.Handler var sdh http.Handler if dashboard.Subdomain { bdh = chain.Then(redirectToDomain(sd, stripPrefix(bdp))) sdh = chain.Then(dashboard.Handler(sdp)) } else { bdh = chain.Then(dashboard.Handler(bdp)) sdh = chain.Then(redirectToDomain(bd, addPrefix(bdp))) } bdghr.Handle(bdp, bdh) bdghr.PathPrefix(bdp).Handler(bdh) if dashboard.Subdomain { sdghr.Handle(bdp, bdh) sdghr.PathPrefix(bdp).Handler(bdh) } sdghr.Handle(sdp, sdh) sdghr.PathPrefix(sdp).Handler(sdh) } // mount the index function to "/" bdr.Handle("/", public.Then(s.index(dashboards, tmpl))).Methods("GET") if err = http.ListenAndServe(cfg.Listen, r); err != nil { log.Fatal().Err(err).Send() } } type modifyPathFn func(path string) string func redirectToDomain(domain string, fn modifyPathFn) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { newURL := fmt.Sprintf("//%s%s", domain, fn(r.URL.Path)) http.Redirect(w, r, newURL, http.StatusPermanentRedirect) } } func addPrefix(prefix string) modifyPathFn { return func(path string) string { return prefix + strings.TrimPrefix(path, "/") } } func stripPrefix(prefix string) modifyPathFn { return func(path string) string { return "/" + strings.TrimPrefix(path, prefix) } } type indexData struct { Dashboards []*Dash User *goth.User Config *Config } func (s *Server) index(dashboards []*Dash, tmpl *template.Template) http.HandlerFunc { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // return 404 if not the root if r.URL.Path != "/" { http.NotFound(w, r) return } data := &indexData{ Dashboards: dashboards, Config: s.config, } if s.config.OAuthEnabled { session, _ := gothic.Store.Get(r, sessionName) if email, ok := session.Values["current_user_email"]; ok { data.User = &goth.User{ Email: email.(string), } } } if err := tmpl.Execute(w, data); err != nil { log.Error().Err(err).Send() http.Error(w, "500 Internal Server Error", http.StatusInternalServerError) } }) } func loadDashboards(name string, config *Config) ([]*Dash, error) { cfgFile, err := os.Open(name) if err != nil { return nil, err } var dashboardMap map[string]*Dash d := yaml.NewDecoder(cfgFile) if err := d.Decode(&dashboardMap); err != nil { return nil, err } var dashboards []*Dash for slug, dashboard := range dashboardMap { dashboard.Slug = slug dashboard.Name = flect.Titleize(slug) if dashboard.Bucket == "" { dashboard.Bucket = config.DefaultBucket } dashboard.Config = config dashboard.Client, err = config.HTTPClient() if err != nil { return nil, err } dashboards = append(dashboards, dashboard) } return dashboards, nil }