thread-master/apps/backend/internal/middleware/auth.go

92 lines
2.8 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package middleware
import (
"context"
"net/http"
"strings"
"apps/backend/internal/domain"
"apps/backend/internal/module/ai"
memberDomain "apps/backend/internal/module/member/domain"
"apps/backend/internal/module/permission"
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)
// LLM 系統 prompt 語言:會員 preferred_language > Accept-Language > 繁中
ctx = ai.WithResponseLanguage(ctx, ai.PickLanguage(m.PreferredLanguage, r.Header.Get("Accept-Language")))
next(w, r.WithContext(ctx))
}
}
}
// AdminMiddleware requires permission.AdminAccessadmin 角色預設授予).
func AdminMiddleware(next http.HandlerFunc) http.HandlerFunc {
return RequirePermission(permission.AdminAccess)(next)
}
// RequirePermission 檢查 JWT roles 是否具備指定能力點(須在 AuthJWT 之後).
func RequirePermission(code string) func(http.HandlerFunc) http.HandlerFunc {
return func(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
roles := RolesFrom(r.Context())
if !permission.Has(roles, code) {
response.Fail(w, http.StatusForbidden, 403002, "permission denied: "+code, nil)
return
}
next(w, r)
}
}
}
func MemberFrom(ctx context.Context) (*memberDomain.Member, bool) {
m, ok := ctx.Value(ctxMember).(*memberDomain.Member)
return m, ok
}