85 lines
2.1 KiB
Go
85 lines
2.1 KiB
Go
|
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,
|
||
|
})
|
||
|
})
|
||
|
}
|