82 lines
2.2 KiB
Go
82 lines
2.2 KiB
Go
|
|
package middleware
|
||
|
|
|
||
|
|
import (
|
||
|
|
"context"
|
||
|
|
"net/http"
|
||
|
|
"strings"
|
||
|
|
|
||
|
|
"apps/backend/internal/domain"
|
||
|
|
memberDomain "apps/backend/internal/module/member/domain"
|
||
|
|
tokenDomain "apps/backend/internal/module/token/domain"
|
||
|
|
"apps/backend/internal/response"
|
||
|
|
)
|
||
|
|
|
||
|
|
type ctxKey int
|
||
|
|
|
||
|
|
const (
|
||
|
|
ctxUID ctxKey = iota + 1
|
||
|
|
ctxRoles
|
||
|
|
ctxMember
|
||
|
|
)
|
||
|
|
|
||
|
|
func UIDFrom(ctx context.Context) (int64, bool) {
|
||
|
|
v, ok := ctx.Value(ctxUID).(int64)
|
||
|
|
return v, ok
|
||
|
|
}
|
||
|
|
|
||
|
|
func RolesFrom(ctx context.Context) []string {
|
||
|
|
v, _ := ctx.Value(ctxRoles).([]string)
|
||
|
|
return v
|
||
|
|
}
|
||
|
|
|
||
|
|
// AuthMiddleware validates Bearer access JWT.
|
||
|
|
func AuthMiddleware(issuer tokenDomain.Issuer, store memberDomain.Repository) func(http.HandlerFunc) http.HandlerFunc {
|
||
|
|
return func(next http.HandlerFunc) http.HandlerFunc {
|
||
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
||
|
|
h := r.Header.Get("Authorization")
|
||
|
|
if h == "" || !strings.HasPrefix(strings.ToLower(h), "bearer ") {
|
||
|
|
response.Fail(w, http.StatusUnauthorized, 401001, "missing authorization", nil)
|
||
|
|
return
|
||
|
|
}
|
||
|
|
token := strings.TrimSpace(h[7:])
|
||
|
|
claims, err := issuer.ParseAccess(token)
|
||
|
|
if err != nil {
|
||
|
|
response.Fail(w, http.StatusUnauthorized, 401002, "invalid or expired token", nil)
|
||
|
|
return
|
||
|
|
}
|
||
|
|
m, err := store.FindByUID(r.Context(), claims.UID)
|
||
|
|
if err != nil {
|
||
|
|
response.Fail(w, http.StatusUnauthorized, 401003, "member not found", nil)
|
||
|
|
return
|
||
|
|
}
|
||
|
|
if m.IsSuspended() {
|
||
|
|
response.Fail(w, http.StatusForbidden, 403001, "account suspended", nil)
|
||
|
|
return
|
||
|
|
}
|
||
|
|
ctx := context.WithValue(r.Context(), ctxUID, m.UID)
|
||
|
|
ctx = context.WithValue(ctx, ctxRoles, m.Roles)
|
||
|
|
ctx = context.WithValue(ctx, ctxMember, m)
|
||
|
|
ctx = context.WithValue(ctx, domain.UIDCode, m.UID)
|
||
|
|
next(w, r.WithContext(ctx))
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// AdminMiddleware requires admin role (after AuthMiddleware).
|
||
|
|
func AdminMiddleware(next http.HandlerFunc) http.HandlerFunc {
|
||
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
||
|
|
for _, role := range RolesFrom(r.Context()) {
|
||
|
|
if role == memberDomain.RoleAdmin {
|
||
|
|
next(w, r)
|
||
|
|
return
|
||
|
|
}
|
||
|
|
}
|
||
|
|
response.Fail(w, http.StatusForbidden, 403002, "admin required", nil)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func MemberFrom(ctx context.Context) (*memberDomain.Member, bool) {
|
||
|
|
m, ok := ctx.Value(ctxMember).(*memberDomain.Member)
|
||
|
|
return m, ok
|
||
|
|
}
|