package main import ( "os" "fmt" "flag" "time" "errors" "strings" "path/filepath" "net/http" "encoding/json" "sanine.net/git/phlox/config" "sanine.net/git/phlox/auth" "sanine.net/git/phlox/page" log "github.com/sirupsen/logrus" ) func main() { configFile := parseFlags() conf := loadConfig(configFile) sessions := auth.NewSessionContainer() pages := loadPages(conf) users := getUsers(conf) // add phlox endpoints http.HandleFunc("/phlox/login", func(w http.ResponseWriter, r *http.Request) { Login(w, r, users, sessions, pages) }) http.HandleFunc("/phlox/logout", func(w http.ResponseWriter, r *http.Request) { Logout(w, r, sessions) }) http.HandleFunc("/phlox/asset/", func(w http.ResponseWriter, r *http.Request) { filename := strings.TrimPrefix(r.URL.Path, "/phlox/asset/") path := filepath.Join(conf.AssetDirectory, filename) http.ServeFile(w, r, path) }) http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { if r.URL.Path == "/" { w.Header().Add("Location", "/phlox/login") w.WriteHeader(http.StatusTemporaryRedirect) } else { pages.ServeError404(w) } }) // add reverse proxy endpoints for _, e := range conf.Endpoints { addEndpoint(sessions, pages, e) } // timer for inactivity log out c := time.Tick(time.Millisecond) go (func() { for ;; { _ = <-c sessions.CleanSessions(time.Duration(conf.LoginTimeout)*time.Second) } })() log.Infof("listening on %v", conf.ListenAddress) log.Fatal(http.ListenAndServe(conf.ListenAddress, nil)) } func parseFlags() string { var configFile string var username string var passwd string flag.StringVar(&configFile, "c", "./config.json", "the configuration file to use") flag.StringVar(&passwd, "passwd", "", "hash a password") flag.StringVar(&username, "user", "", "optional username for the JSON output of --passwd") flag.Parse() if passwd != "" { // generate a user JSON block with the password hash // and random salt, then exit without launching the // phlox daemon showHash(username, passwd) os.Exit(0) } return configFile } func showHash(username, passwd string) { salt, err := auth.GenerateSalt() if err != nil { fmt.Fprintf(os.Stderr, "failed to generate salt: %v\n", err.Error()) } user := config.User{ Name: username, PasswordHash: auth.HashPassword(passwd, salt), Salt: salt, } blob, err := json.MarshalIndent(user, "", "\t") if err != nil { fmt.Fprintf(os.Stderr, "failed to generate JSON: %v\n", err.Error()) } fmt.Println(string(blob)) } func loadConfig(filename string) config.Config { conf, err := config.Load(filename) if err != nil { fmt.Fprintf(os.Stderr, "failed to load configuration file: %v\n", err.Error()) os.Exit(1) } if conf.LoginTimeout == 0 { conf.LoginTimeout = 3600 } return conf } func loadPages(c config.Config) page.Pages { pages, err := page.LoadPages(c) if err != nil { fmt.Fprintf(os.Stderr, "failed to load html pages: %v\n", err.Error()) os.Exit(1) } return pages } func getUsers(c config.Config) map[string]config.User { users := make(map[string]config.User) for _, user := range c.Users { users[user.Name] = user } return users } func authenticateRequest(r *http.Request, s *auth.Sessions) bool { cookie, err := r.Cookie("phlox-session") if errors.Is(err, http.ErrNoCookie) { return false } id := cookie.Value valid := s.IsSessionValid(id) if !valid { return false } s.TouchSession(id) return true }