76 lines
2.3 KiB
Go
76 lines
2.3 KiB
Go
package api
|
|
|
|
import (
|
|
"net"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// requestIdentity belongs to one authorized request, including its confirmation binding.
|
|
type requestIdentity struct {
|
|
session session
|
|
sessionID string
|
|
}
|
|
|
|
// authorize adapts bearer, local-development and cookie authentication to one
|
|
// request identity. CSRF validation remains after authentication and scope checks.
|
|
func (s *Server) authorize(w http.ResponseWriter, r *http.Request, path string) (http.ResponseWriter, requestIdentity, bool) {
|
|
var sess session
|
|
cookie := &http.Cookie{Value: "development"}
|
|
bearer := r.Header.Get("Authorization") != ""
|
|
if bearer {
|
|
value := strings.TrimPrefix(r.Header.Get("Authorization"), "Bearer ")
|
|
credential, err := s.store.VerifyToken(value)
|
|
if err != nil {
|
|
fail(w, 401, "invalid or expired API token")
|
|
return w, requestIdentity{}, false
|
|
}
|
|
if !tokenPermits(credential, path, r.Method) {
|
|
fail(w, 403, "token scope does not permit this operation")
|
|
return w, requestIdentity{}, false
|
|
}
|
|
sess = session{User: credential.Owner, Revision: credential.Revision}
|
|
cookie.Value = "token:" + credential.ID
|
|
w = tokenResponseWriter{w}
|
|
} else if !s.authEnabled {
|
|
ip := net.ParseIP(source(r))
|
|
host := r.Host
|
|
if h, _, e := net.SplitHostPort(host); e == nil {
|
|
host = h
|
|
}
|
|
hostIP := net.ParseIP(host)
|
|
if ip == nil || !ip.IsLoopback() || (host != "localhost" && (hostIP == nil || !hostIP.IsLoopback())) {
|
|
fail(w, 403, "development mode is local only")
|
|
return w, requestIdentity{}, false
|
|
}
|
|
sess = session{User: "开发者", CSRF: s.devCSRF}
|
|
} else {
|
|
var err error
|
|
cookie, err = r.Cookie("linkport_session")
|
|
if err != nil {
|
|
fail(w, 401, "login required")
|
|
return w, requestIdentity{}, false
|
|
}
|
|
s.mu.Lock()
|
|
var ok bool
|
|
sess, ok = s.sessions[cookie.Value]
|
|
if time.Now().After(sess.Expires) {
|
|
delete(s.sessions, cookie.Value)
|
|
ok = false
|
|
}
|
|
s.mu.Unlock()
|
|
rev, err := s.store.PrimaryRevision(sess.User)
|
|
if !ok || err != nil || rev != sess.Revision {
|
|
fail(w, 401, "session expired")
|
|
return w, requestIdentity{}, false
|
|
}
|
|
}
|
|
if !bearer && r.Method != "GET" && r.Header.Get("X-CSRF-Token") != sess.CSRF {
|
|
fail(w, 403, "CSRF token required")
|
|
return w, requestIdentity{}, false
|
|
}
|
|
|
|
return w, requestIdentity{session: sess, sessionID: cookie.Value}, true
|
|
}
|