feat(backend): implement HTTP API, auth, chat, and admin surfaces
Expose learning, registration controls, mentor chat, realtime WebSocket, and staff administration.
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
const wsBearerProtocol = "bearer"
|
||||
|
||||
// extractWSToken resolves the access token without putting it in the query string.
|
||||
// Preference order: Authorization Bearer → Sec-WebSocket-Protocol → httpOnly cookie.
|
||||
func extractWSToken(r *http.Request) (token string, selectedProtocol string) {
|
||||
if auth := r.Header.Get("Authorization"); strings.HasPrefix(auth, "Bearer ") {
|
||||
return strings.TrimSpace(strings.TrimPrefix(auth, "Bearer ")), ""
|
||||
}
|
||||
|
||||
protocols := websocket.Subprotocols(r)
|
||||
for i, p := range protocols {
|
||||
p = strings.TrimSpace(p)
|
||||
if p == wsBearerProtocol && i+1 < len(protocols) {
|
||||
next := strings.TrimSpace(protocols[i+1])
|
||||
if next != "" {
|
||||
return next, wsBearerProtocol
|
||||
}
|
||||
}
|
||||
if strings.HasPrefix(p, wsBearerProtocol+".") {
|
||||
tok := strings.TrimPrefix(p, wsBearerProtocol+".")
|
||||
if tok != "" {
|
||||
// Negotiate the bare "bearer" name so the JWT is not echoed in the response.
|
||||
return tok, wsBearerProtocol
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if c, err := r.Cookie(cookieAccess); err == nil && c.Value != "" {
|
||||
return c.Value, ""
|
||||
}
|
||||
|
||||
// Query-string tokens are rejected — JWTs in URLs end up in proxy/access logs.
|
||||
return "", ""
|
||||
}
|
||||
|
||||
// handleWS upgrades to a websocket authenticated via Bearer header or
|
||||
// Sec-WebSocket-Protocol (bearer + token).
|
||||
func (s *Server) handleWS(w http.ResponseWriter, r *http.Request) {
|
||||
if !s.allowWSHandshake(r) {
|
||||
w.Header().Set("Retry-After", "60")
|
||||
writeErr(w, http.StatusTooManyRequests, authRateMsg)
|
||||
return
|
||||
}
|
||||
|
||||
token, selectedProtocol := extractWSToken(r)
|
||||
if token == "" {
|
||||
writeErr(w, http.StatusUnauthorized, "توکن ارسال نشده است")
|
||||
return
|
||||
}
|
||||
user, err := s.auth.parseAndLoad(r.Context(), token)
|
||||
if err != nil {
|
||||
writeErr(w, http.StatusUnauthorized, "توکن نامعتبر است")
|
||||
return
|
||||
}
|
||||
if meta, ok := r.Context().Value(ctxMeta).(*requestMeta); ok && meta != nil {
|
||||
meta.UserID = user.ID
|
||||
}
|
||||
|
||||
upgrader := s.newUpgrader()
|
||||
if selectedProtocol != "" {
|
||||
upgrader.Subprotocols = []string{selectedProtocol}
|
||||
}
|
||||
conn, err := upgrader.Upgrade(w, r, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
c := s.hub.Register(user.ID, conn)
|
||||
|
||||
// send a welcome frame so clients know the socket is live
|
||||
c.send <- []byte(`{"type":"connected"}`)
|
||||
}
|
||||
Reference in New Issue
Block a user