package main import ( "errors" "log" "net/http" "net/url" "time" jwt "github.com/dgrijalva/jwt-go" "riedstra.dev/mitch/go-website/page" "riedstra.dev/mitch/go-website/users" ) func (a *App) Err5xx(w http.ResponseWriter, r *http.Request, statusCode int, title, desc string) { page.Render5xx(w, r, map[string]interface{}{ "Error": title, "Description": desc, }, statusCode) } func (a *App) Err500Default(w http.ResponseWriter, r *http.Request) { a.Err5xx(w, r, http.StatusInternalServerError, "Internal server error", "Internal server error.") } func (a *App) LogoutHandler(w http.ResponseWriter, r *http.Request) { http.SetCookie(w, &http.Cookie{ Name: "Auth", HttpOnly: a.auth.HTTPOnly, SameSite: a.auth.SameSiteStrict, Secure: a.auth.Secure, Value: "logout", Expires: time.Now().Add(time.Second), //nolint }) http.Redirect(w, r, "/", http.StatusFound) } func (a *App) LoginHandler(w http.ResponseWriter, r *http.Request) { //nolint loggedIn := a.IsLoggedIn(r) next, _ := url.Parse(r.URL.Query().Get("next")) if r.Method == "GET" && !loggedIn { page.RenderForPath(w, r, "login") return } if r.Method == "GET" && loggedIn { if next.Path != "" { http.Redirect(w, r, next.Path, http.StatusFound) return } http.Redirect(w, r, "/dashboard", http.StatusFound) return } if r.Method != "POST" { a.Err500Default(w, r) return } username := r.FormValue("username") password := r.FormValue("password") var ( err error = nil u *users.SiteUser found = false ) for _, u = range a.auth.Users { if u.Username == username { err = u.CheckPassword(password) found = true } } if err != nil || !found { page.Render(w, r, "login", map[string]interface{}{ "Error": "Invalid username or password", "Username": username, }, http.StatusUnauthorized) return } token := jwt.NewWithClaims(jwt.SigningMethodHS512, &jwt.StandardClaims{ ExpiresAt: time.Now().Add( time.Hour * time.Duration(a.auth.LoginHours)).Unix(), Id: u.Username, }) ss, err := token.SignedString([]byte(a.auth.TokenKey)) if err != nil { log.Println("login: encountered while setting up JWT: ", err) a.Err500Default(w, r) return } http.SetCookie(w, &http.Cookie{ Name: "Auth", HttpOnly: a.auth.HTTPOnly, SameSite: a.auth.SameSiteStrict, Secure: a.auth.Secure, Value: ss, }) http.Redirect(w, r, "/login", http.StatusFound) } func (a *App) IsLoggedIn(r *http.Request) bool { _, err := a.GetAuthToken(r) if err != nil { log.Printf("%s IsLoggedIn: false", r.URL.Path) return false } log.Printf("%s IsLoggedIn: true", r.URL.Path) return true } func (a *App) GetAuthToken(r *http.Request) (*jwt.Token, error) { c, err := r.Cookie("Auth") if err != nil { return nil, err } token, err := jwt.Parse(c.Value, func(token *jwt.Token) (interface{}, error) { return []byte(a.auth.TokenKey), nil }, ) if err != nil { return nil, err } if !token.Valid { return token, errors.New("IsLoggedIn: token not valid") } return token, nil } func (a *App) RequiresLogin(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if !a.IsLoggedIn(r) { log.Printf("Unauthorized request %s %s", r.Method, r.URL.Path) page.Render(w, r, "login", map[string]interface{}{ "Error": "You must login to view this page", }, http.StatusUnauthorized) return } next.ServeHTTP(w, r) }) } /* // ConditionalMiddleware is used to select one handler or another based on // a test function. If the test function returns true, use handler A, otherwise B. // This allows the test condition to only be run when the handler is selected // rather than trying to do this as a top level and ending up with a condition // that is tested on every single request, regardless of whether or not // the specific handler is selected type ConditionalMiddleware struct { A, B http.Handler Test func(r *http.Request) bool } func NewConditionalMiddleware(test func(r *http.Request) bool, A, B http.Handler) http.Handler { return &ConditionalMiddleware{ Test: test, A: A, B: B, } } func (cm *ConditionalMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request) { if cm.Test(r) { cm.A.ServeHTTP(w, r) } else { cm.B.ServeHTTP(w, r) } } */