Files

111 lines
3.1 KiB
Go

package api
import (
"linkport/internal/model"
"linkport/internal/security"
"net/http"
"time"
)
func (s *Server) logout(w http.ResponseWriter, sessionID string) {
s.mu.Lock()
delete(s.sessions, sessionID)
s.mu.Unlock()
http.SetCookie(w, &http.Cookie{Name: "linkport_session", Path: "/", MaxAge: -1, HttpOnly: true, Secure: s.secure, SameSite: http.SameSiteStrictMode})
respond(w, 200, map[string]bool{"ok": true})
}
func (s *Server) limited(key string, max int) bool {
s.mu.Lock()
defer s.mu.Unlock()
now := time.Now()
for k, a := range s.attempts {
if now.After(a.Until) {
delete(s.attempts, k)
}
}
a := s.attempts[key]
if a.Until.IsZero() {
a.Until = now.Add(5 * time.Minute)
}
a.Count++
s.attempts[key] = a
return a.Count > max
}
func (s *Server) login(w http.ResponseWriter, r *http.Request) {
if s.limited("login:"+source(r), 10) {
fail(w, 429, "too many login attempts; retry in 5 minutes")
return
}
var b struct {
Username string `json:"username"`
Password string `json:"password"`
}
if !decode(w, r, &b) {
return
}
rev, ok := s.store.Verify(b.Username, b.Password)
if !ok {
fail(w, 401, "invalid credentials")
return
}
token := security.Token()
sess := session{User: b.Username, CSRF: security.Token(), Revision: rev, Expires: time.Now().Add(12 * time.Hour)}
s.mu.Lock()
for k, v := range s.sessions {
if time.Now().After(v.Expires) {
delete(s.sessions, k)
}
}
s.sessions[token] = sess
s.mu.Unlock()
http.SetCookie(w, &http.Cookie{Name: "linkport_session", Value: token, Path: "/", HttpOnly: true, Secure: s.secure, SameSite: http.SameSiteStrictMode, MaxAge: 43200})
respond(w, 200, map[string]string{"username": sess.User, "csrfToken": sess.CSRF})
}
func (s *Server) reveal(w http.ResponseWriter, r *http.Request, p model.Proxy, sess session, token string, allowedFields ...string) {
a := s.limited("reveal:ip:"+source(r), 5)
b := s.limited("reveal:session:"+token, 5)
if a || b {
s.store.Event(p.ID, source(r), "secret_view_denied", "rate_limit")
fail(w, 429, "too many attempts; retry in 5 minutes")
return
}
var body struct {
Field string `json:"field"`
Password string `json:"password"`
}
if !decode(w, r, &body) {
return
}
if len(allowedFields) > 0 {
allowed := false
for _, field := range allowedFields {
if field == body.Field {
allowed = true
}
}
if !allowed {
fail(w, 400, "unsupported secret field")
return
}
}
secrets := map[string]string{"targetPassword": p.TargetPassword, "gatewayPassword": p.GatewayPassword, "socksPassword": p.Outbound.Password, "sshPassphrase": p.SSH.Passphrase}
for _, account := range p.GatewayAccounts {
secrets["gatewayAccount:"+account.ID+":password"] = account.Password
}
value, exists := secrets[body.Field]
if !exists {
fail(w, 400, "unsupported secret field")
return
}
if _, ok := s.store.Verify(sess.User, body.Password); s.authEnabled && !ok {
s.store.Event(p.ID, source(r), "secret_view_denied", body.Field)
fail(w, 401, "login password incorrect")
return
}
s.store.Event(p.ID, source(r), "secret_viewed", body.Field)
respond(w, 200, map[string]string{"value": value})
}