234 lines
5.5 KiB
Go
234 lines
5.5 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"encoding/base64"
|
|
"errors"
|
|
"fmt"
|
|
"log"
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/FernandoVideira/LK_API_Temp/internal/store"
|
|
"github.com/go-chi/chi/v5"
|
|
"github.com/golang-jwt/jwt/v5"
|
|
)
|
|
|
|
func (api *api) BasicAuthMiddleware() func(http.Handler) http.Handler {
|
|
return func(next http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
//* read the auth header
|
|
authHeader := r.Header.Get("Authorization")
|
|
if authHeader == "" {
|
|
api.unauthorizedBasicError(w, r, fmt.Errorf("missing Authorization header"))
|
|
return
|
|
}
|
|
|
|
//* parse the header -> get the base64
|
|
parts := strings.Split(authHeader, " ")
|
|
if len(parts) != 2 || parts[0] != "Basic" {
|
|
api.unauthorizedBasicError(w, r, fmt.Errorf("invalid Authorization header"))
|
|
return
|
|
}
|
|
|
|
//* decode the base64
|
|
decoded, err := base64.StdEncoding.DecodeString(parts[1])
|
|
if err != nil {
|
|
api.unauthorizedBasicError(w, r, err)
|
|
return
|
|
}
|
|
|
|
username := api.config.auth.basic.username
|
|
pwd := api.config.auth.basic.password
|
|
|
|
//* check the credentials
|
|
creds := strings.SplitN(string(decoded), ":", 2)
|
|
if len(creds) != 2 || creds[0] != username || creds[1] != pwd {
|
|
api.unauthorizedBasicError(w, r, fmt.Errorf("invalid credentials"))
|
|
return
|
|
}
|
|
|
|
next.ServeHTTP(w, r)
|
|
})
|
|
}
|
|
}
|
|
|
|
func (api *api) AuthTokenMiddleware(next http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
authHeader := r.Header.Get("Authorization")
|
|
if authHeader == "" {
|
|
api.unauthorizedError(w, r, fmt.Errorf("missing Authorization header"))
|
|
return
|
|
}
|
|
|
|
parts := strings.Split(authHeader, " ")
|
|
if len(parts) != 2 || parts[0] != "Bearer" {
|
|
api.unauthorizedError(w, r, fmt.Errorf("invalid Authorization header"))
|
|
return
|
|
}
|
|
|
|
token := parts[1]
|
|
|
|
jwtToken, err := api.authenticator.ValidateToken(token)
|
|
if err != nil {
|
|
api.unauthorizedError(w, r, err)
|
|
return
|
|
}
|
|
|
|
claims := jwtToken.Claims.(jwt.MapClaims)
|
|
|
|
userID, err := strconv.ParseInt(fmt.Sprintf("%.f", claims["sub"]), 10, 64)
|
|
if err != nil {
|
|
api.unauthorizedError(w, r, fmt.Errorf("invalid token"))
|
|
return
|
|
}
|
|
|
|
ctx := r.Context()
|
|
user, err := api.getUser(ctx, userID)
|
|
if err != nil {
|
|
api.unauthorizedError(w, r, err)
|
|
return
|
|
}
|
|
|
|
ctx = context.WithValue(ctx, userContextKey, user)
|
|
next.ServeHTTP(w, r.WithContext(ctx))
|
|
})
|
|
}
|
|
|
|
func (api *api) postsContextMiddleware(next http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
idParam := chi.URLParam(r, "id")
|
|
|
|
id, err := strconv.ParseInt(idParam, 10, 64)
|
|
if err != nil {
|
|
api.badRequestError(w, r, err)
|
|
return
|
|
}
|
|
|
|
ctx := r.Context()
|
|
|
|
post, err := api.store.Posts.GetByID(ctx, id)
|
|
log.Println("post", post)
|
|
if err != nil {
|
|
switch {
|
|
case errors.Is(err, store.ErrNotFound):
|
|
api.notFoundError(w, r, err)
|
|
return
|
|
default:
|
|
api.internalServerError(w, r, err)
|
|
return
|
|
}
|
|
}
|
|
|
|
ctx = context.WithValue(ctx, postCtx, post)
|
|
next.ServeHTTP(w, r.WithContext(ctx))
|
|
})
|
|
}
|
|
|
|
func (api *api) checkPostOwnership(requiredRole string, next http.HandlerFunc) http.HandlerFunc {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
user := getUserFromContext(r)
|
|
post := getPostFromContext(r)
|
|
|
|
if user.ID == post.UserID {
|
|
next.ServeHTTP(w, r)
|
|
return
|
|
}
|
|
|
|
allowed, err := api.checkRolePrecedence(r.Context(), user, requiredRole)
|
|
if err != nil {
|
|
api.internalServerError(w, r, err)
|
|
return
|
|
}
|
|
|
|
if !allowed {
|
|
api.forbiddenError(w, r, fmt.Errorf("forbidden"))
|
|
return
|
|
}
|
|
|
|
next.ServeHTTP(w, r)
|
|
})
|
|
}
|
|
|
|
func (api *api) checkProfileOwnership(next http.HandlerFunc) http.HandlerFunc {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
user := getUserFromContext(r)
|
|
profile := getUserFromContext(r)
|
|
|
|
if user.ID == profile.ID {
|
|
next.ServeHTTP(w, r)
|
|
return
|
|
}
|
|
|
|
api.forbiddenError(w, r, fmt.Errorf("forbidden"))
|
|
})
|
|
}
|
|
|
|
func (api *api) protectAdminRoutes(requiredRole string, next http.HandlerFunc) http.HandlerFunc {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
user := getUserFromContext(r)
|
|
allowed, err := api.checkRolePrecedence(r.Context(), user, requiredRole)
|
|
if err != nil {
|
|
api.internalServerError(w, r, err)
|
|
return
|
|
}
|
|
|
|
if !allowed {
|
|
api.forbiddenError(w, r, fmt.Errorf("forbidden"))
|
|
return
|
|
}
|
|
|
|
ctx := r.Context()
|
|
|
|
ctx = context.WithValue(ctx, roleContextKey, user.Role)
|
|
next.ServeHTTP(w, r.WithContext(ctx))
|
|
})
|
|
}
|
|
|
|
func (api *api) checkRolePrecedence(ctx context.Context, user *store.User, roleName string) (bool, error) {
|
|
role, err := api.store.Roles.GetByName(ctx, roleName)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
return user.Role.Level >= role.Level, nil
|
|
}
|
|
|
|
func (api *api) getUser(ctx context.Context, id int64) (*store.User, error) {
|
|
if !api.config.redisCfg.enabled {
|
|
return api.store.Users.GetByID(ctx, id)
|
|
}
|
|
|
|
user, err := api.cacheStore.Users.Get(ctx, id)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if user == nil {
|
|
user, err = api.store.Users.GetByID(ctx, id)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err := api.cacheStore.Users.Set(ctx, user); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
return user, nil
|
|
}
|
|
|
|
func (api *api) RateLimiterMiddleware(next http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if api.config.rateLimiter.Enabled {
|
|
if allow, retryAfter := api.rateLimiter.Allow(r.RemoteAddr); !allow {
|
|
api.rateLimitExceededError(w, r, retryAfter.String())
|
|
return
|
|
}
|
|
}
|
|
|
|
next.ServeHTTP(w, r)
|
|
})
|
|
}
|