package api import ( "fmt" "net/http" "net/url" "strings" "code.nonshy.com/nonshy/website/pkg/config" "code.nonshy.com/nonshy/website/pkg/encryption" "code.nonshy.com/nonshy/website/pkg/session" "github.com/golang-jwt/jwt/v4" ) // StaticAuth API protects paths like /static/photos/ to authenticated user requests only. func StaticAuth() http.HandlerFunc { type Response struct { Success bool `json:"success"` Error string `json:",omitempty"` Username string `json:"username"` } return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // We only protect the /static/photos subpath. // And check if the SignedPhoto feature is enabled and enforcing. var originalURI = r.Header.Get("X-Original-URI") if !config.Current.SignedPhoto.Enabled || !strings.HasPrefix(originalURI, config.PhotoWebPath) { SendJSON(w, http.StatusOK, Response{ Success: true, }) return } // Parse the JWT token parameter from the original URL. var token string if path, err := url.Parse(originalURI); err == nil { query := path.Query() token = query.Get("jwt") } // The token is required from here. if token == "" { SendJSON(w, http.StatusForbidden, Response{ Error: "JWT token is required", }) return } // Check if we're logged in. currentUser, err := session.CurrentUser(r) if err != nil { SendJSON(w, http.StatusForbidden, Response{ Error: "Login Required", }) return } // Validate the JWT token. claims, ok, err := encryption.ValidateClaims( token, []byte(config.Current.SignedPhoto.JWTSecret), &jwt.RegisteredClaims{}, ) if !ok || err != nil { SendJSON(w, http.StatusForbidden, Response{ Error: fmt.Sprintf("JWT claims: %v", err), }) return } // Sanity check that the username in these claims is the same as the viewer. if c, ok := claims.(*jwt.RegisteredClaims); !ok || c.Subject != currentUser.Username { SendJSON(w, http.StatusForbidden, Response{ Error: "That photo was not for you", }) return } SendJSON(w, http.StatusOK, Response{ Success: true, Username: currentUser.Username, }) }) }