201 lines
4.2 KiB
Go
201 lines
4.2 KiB
Go
![]() |
package main
|
||
|
|
||
|
import (
|
||
|
// "fmt"
|
||
|
"net/http"
|
||
|
"net/url"
|
||
|
|
||
|
"github.com/gorilla/mux"
|
||
|
"github.com/sirupsen/logrus"
|
||
|
)
|
||
|
|
||
|
type Server struct {
|
||
|
mux *mux.Router
|
||
|
}
|
||
|
|
||
|
func NewServer() *Server {
|
||
|
s := &Server{}
|
||
|
s.buildRoutes()
|
||
|
return s
|
||
|
}
|
||
|
|
||
|
func (s *Server) buildRoutes() {
|
||
|
s.mux = mux.NewRouter()
|
||
|
|
||
|
// Let's build a server
|
||
|
for _, rules := range config.Rules {
|
||
|
// fmt.Printf("Rule: %s\n", name)
|
||
|
for _, match := range rules.Match {
|
||
|
s.attachHandler(&match, rules.Action)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Add callback handler
|
||
|
s.mux.Handle(config.Path, s.AuthCallbackHandler())
|
||
|
|
||
|
// Add a default handler
|
||
|
s.mux.NewRoute().Handler(s.AuthHandler())
|
||
|
}
|
||
|
|
||
|
func (s *Server) RootHandler(w http.ResponseWriter, r *http.Request) {
|
||
|
// Modify request
|
||
|
r.URL, _ = url.Parse(r.Header.Get("X-Forwarded-Uri"))
|
||
|
|
||
|
// Pass to mux
|
||
|
s.mux.ServeHTTP(w, r)
|
||
|
}
|
||
|
|
||
|
// Handler that allows requests
|
||
|
func (s *Server) AllowHandler() http.HandlerFunc {
|
||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||
|
s.logger(r, "Allowing request")
|
||
|
w.WriteHeader(200)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Authenticate requests
|
||
|
func (s *Server) AuthHandler() http.HandlerFunc {
|
||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||
|
// Logging setup
|
||
|
logger := s.logger(r, "Authenticating request")
|
||
|
|
||
|
// Get auth cookie
|
||
|
c, err := r.Cookie(config.CookieName)
|
||
|
if err != nil {
|
||
|
// Error indicates no cookie, generate nonce
|
||
|
err, nonce := fw.Nonce()
|
||
|
if err != nil {
|
||
|
logger.Errorf("Error generating nonce, %v", err)
|
||
|
http.Error(w, "Service unavailable", 503)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// Set the CSRF cookie
|
||
|
http.SetCookie(w, fw.MakeCSRFCookie(r, nonce))
|
||
|
logger.Debug("Set CSRF cookie and redirecting to google login")
|
||
|
|
||
|
// Forward them on
|
||
|
http.Redirect(w, r, fw.GetLoginURL(r, nonce), http.StatusTemporaryRedirect)
|
||
|
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// Validate cookie
|
||
|
valid, email, err := fw.ValidateCookie(r, c)
|
||
|
if !valid {
|
||
|
logger.Errorf("Invalid cookie: %v", err)
|
||
|
http.Error(w, "Not authorized", 401)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// Validate user
|
||
|
valid = fw.ValidateEmail(email)
|
||
|
if !valid {
|
||
|
logger.WithFields(logrus.Fields{
|
||
|
"email": email,
|
||
|
}).Errorf("Invalid email")
|
||
|
http.Error(w, "Not authorized", 401)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// Valid request
|
||
|
logger.Debugf("Allowing valid request ")
|
||
|
w.Header().Set("X-Forwarded-User", email)
|
||
|
w.WriteHeader(200)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Handle auth callback
|
||
|
func (s *Server) AuthCallbackHandler() http.HandlerFunc {
|
||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||
|
// Logging setup
|
||
|
logger := s.logger(r, "Handling callback")
|
||
|
|
||
|
// Check for CSRF cookie
|
||
|
c, err := r.Cookie(config.CSRFCookieName)
|
||
|
if err != nil {
|
||
|
logger.Warn("Missing csrf cookie")
|
||
|
http.Error(w, "Not authorized", 401)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// Validate state
|
||
|
valid, redirect, err := fw.ValidateCSRFCookie(r, c)
|
||
|
if !valid {
|
||
|
logger.Warnf("Error validating csrf cookie: %v", err)
|
||
|
http.Error(w, "Not authorized", 401)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// Clear CSRF cookie
|
||
|
http.SetCookie(w, fw.ClearCSRFCookie(r))
|
||
|
|
||
|
// Exchange code for token
|
||
|
token, err := fw.ExchangeCode(r)
|
||
|
if err != nil {
|
||
|
logger.Errorf("Code exchange failed with: %v", err)
|
||
|
http.Error(w, "Service unavailable", 503)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// Get user
|
||
|
user, err := fw.GetUser(token)
|
||
|
if err != nil {
|
||
|
logger.Errorf("Error getting user: %s", err)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// Generate cookie
|
||
|
http.SetCookie(w, fw.MakeCookie(r, user.Email))
|
||
|
logger.WithFields(logrus.Fields{
|
||
|
"user": user.Email,
|
||
|
}).Infof("Generated auth cookie")
|
||
|
|
||
|
// Redirect
|
||
|
http.Redirect(w, r, redirect, http.StatusTemporaryRedirect)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Build a handler for a given matcher
|
||
|
func (s *Server) attachHandler(m *Match, action string) {
|
||
|
// Build a new route matcher
|
||
|
route := s.mux.NewRoute()
|
||
|
|
||
|
for _, host := range m.Host {
|
||
|
route.Host(host)
|
||
|
}
|
||
|
|
||
|
for _, pathPrefix := range m.PathPrefix {
|
||
|
route.PathPrefix(pathPrefix)
|
||
|
}
|
||
|
|
||
|
for _, header := range m.Header {
|
||
|
if len(header) != 2 {
|
||
|
panic("todo")
|
||
|
}
|
||
|
|
||
|
route.Headers(header[0], header[1])
|
||
|
}
|
||
|
|
||
|
// Add handler to new route
|
||
|
if action == "allow" {
|
||
|
route.Handler(s.AllowHandler())
|
||
|
} else {
|
||
|
route.Handler(s.AuthHandler())
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (s *Server) logger(r *http.Request, msg string) *logrus.Entry {
|
||
|
// Create logger
|
||
|
logger := log.WithFields(logrus.Fields{
|
||
|
"RemoteAddr": r.RemoteAddr,
|
||
|
})
|
||
|
|
||
|
// Log request
|
||
|
logger.WithFields(logrus.Fields{
|
||
|
"Headers": r.Header,
|
||
|
}).Debugf(msg)
|
||
|
|
||
|
return logger
|
||
|
}
|