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

92 lines
2.8 KiB
Go
Raw Permalink Normal View History

2026-07-10 12:54:45 +00:00
package middleware
import (
"context"
"net/http"
"strings"
"apps/backend/internal/domain"
2026-07-13 01:15:30 +00:00
"apps/backend/internal/module/ai"
2026-07-10 12:54:45 +00:00
memberDomain "apps/backend/internal/module/member/domain"
2026-07-13 01:15:30 +00:00
"apps/backend/internal/module/permission"
2026-07-10 12:54:45 +00:00
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)
2026-07-13 01:15:30 +00:00
// LLM 系統 prompt 語言:會員 preferred_language > Accept-Language > 繁中
ctx = ai.WithResponseLanguage(ctx, ai.PickLanguage(m.PreferredLanguage, r.Header.Get("Accept-Language")))
2026-07-10 12:54:45 +00:00
next(w, r.WithContext(ctx))
}
}
}
2026-07-13 01:15:30 +00:00
// AdminMiddleware requires permission.AdminAccessadmin 角色預設授予).
2026-07-10 12:54:45 +00:00
func AdminMiddleware(next http.HandlerFunc) http.HandlerFunc {
2026-07-13 01:15:30 +00:00
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)
2026-07-10 12:54:45 +00:00
return
}
2026-07-13 01:15:30 +00:00
next(w, r)
2026-07-10 12:54:45 +00:00
}
}
}
func MemberFrom(ctx context.Context) (*memberDomain.Member, bool) {
m, ok := ctx.Value(ctxMember).(*memberDomain.Member)
return m, ok
}