package middleware import ( "net/http" "haixun-backend/internal/library/authctx" app "haixun-backend/internal/library/errors" "haixun-backend/internal/library/errors/code" "haixun-backend/internal/model/member/domain/entity" memberdomain "haixun-backend/internal/model/member/domain/usecase" "haixun-backend/internal/response" ) var emailVerificationBypass = map[string]map[string]struct{}{ http.MethodGet: { "/api/v1/members/me": {}, }, http.MethodPost: { "/api/v1/auth/logout": {}, "/api/v1/auth/verify-email": {}, "/api/v1/auth/resend-verification-email": {}, }, } // EmailVerifiedMiddleware blocks native members who have not verified email yet. // Third-party (oauth) origins skip this gate. type EmailVerifiedMiddleware struct { members memberdomain.UseCase } func NewEmailVerifiedMiddleware(members memberdomain.UseCase) *EmailVerifiedMiddleware { return &EmailVerifiedMiddleware{members: members} } func (m *EmailVerifiedMiddleware) Handle(next http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { actor, ok := authctx.ActorFromContext(r.Context()) if !ok { next(w, r) return } if emailVerificationBypassed(r.Method, r.URL.Path) { next(w, r) return } if m.members == nil { response.Write(r.Context(), w, nil, app.For(code.Auth).SysNotImplemented("member usecase is not configured")) return } member, err := m.members.GetByUID(r.Context(), actor.TenantID, actor.UID) if err != nil { response.Write(r.Context(), w, nil, err) return } if !requiresNativeEmailVerification(member) || member.EmailVerified { next(w, r) return } response.Write(r.Context(), w, nil, app.For(code.Auth).AuthForbidden("email verification required")) } } func emailVerificationBypassed(method, path string) bool { routes, ok := emailVerificationBypass[method] if !ok { return false } _, ok = routes[path] return ok } func requiresNativeEmailVerification(member *entity.Member) bool { if member == nil { return false } return member.Origin == "" || member.Origin == entity.OriginNative }