Removed peer editing, cleanup

This commit is contained in:
jdl
2026-06-12 14:49:40 +02:00
parent 4d2aa5c8c7
commit 3e7d34a92a
17 changed files with 99 additions and 203 deletions

View File

@@ -40,7 +40,13 @@ func New(dbPath string) (*API, error) {
sessions: make(map[string]*Session),
}
return a, a.ensurePassword()
if err := a.ensurePassword(); err != nil {
return nil, err
}
go a.sweepSessions()
return a, nil
}
func (a *API) ensurePassword() error {
@@ -80,58 +86,72 @@ func (a *API) Session_Delete(sessionID string) error {
return nil
}
// Session_Get returns a snapshot copy of the session for sessionID (creating a
// fresh one if absent/expired). Returning a value rather than the stored
// pointer prevents callers from racing on the shared struct; mutations go
// through Session_SignIn / Session_Delete under the lock.
const (
sessionTTLSecs = 86400 * 21 // sessions expire 21 days after last use
sessionSweepEvery = time.Hour // cadence of expired-session eviction
)
// Session_Get returns a snapshot copy of the signed-in session for sessionID,
// or the zero Session if the cookie is missing/unknown/expired. It never
// creates a session, so anonymous requests cost no memory — a session is minted
// only by Session_SignIn. Returning a value (not the stored pointer) keeps
// callers from racing on the shared struct.
func (a *API) Session_Get(sessionID string) (Session, error) {
a.sessionsMu.Lock()
defer a.sessionsMu.Unlock()
if sessionID != "" {
s, ok := a.sessions[sessionID]
if ok {
if timeSince(s.LastSeenAt) <= 86400*21 {
if timeSince(s.LastSeenAt) > 86400*7 {
s.LastSeenAt = time.Now().Unix()
}
return *s, nil
}
delete(a.sessions, sessionID)
}
s, ok := a.sessions[sessionID]
if sessionID == "" || !ok {
return Session{}, nil
}
return *a.session_Create(), nil
if timeSince(s.LastSeenAt) > sessionTTLSecs {
delete(a.sessions, sessionID)
return Session{}, nil
}
s.LastSeenAt = time.Now().Unix()
return *s, nil
}
// caller must hold sessionsMu
func (a *API) session_Create() *Session {
// Session_SignIn verifies pwd and, on success, mints a fresh signed-in session,
// returning it so the caller can set the cookie. A new ID per sign-in rotates
// the session at the privilege boundary (session-fixation resistance).
func (a *API) Session_SignIn(pwd string) (Session, error) {
conf, err := a.Config_Get()
if err != nil {
return Session{}, err
}
if err := bcrypt.CompareHashAndPassword(conf.Password, []byte(pwd)); err != nil {
return Session{}, ErrNotAuthorized
}
a.sessionsMu.Lock()
defer a.sessionsMu.Unlock()
s := &Session{
SessionID: idgen.NewToken(),
SignedIn: true,
CreatedAt: time.Now().Unix(),
LastSeenAt: time.Now().Unix(),
}
a.sessions[s.SessionID] = s
return s
return *s, nil
}
func (a *API) Session_SignIn(sessionID, pwd string) error {
conf, err := a.Config_Get()
if err != nil {
return err
// sweepSessions periodically evicts sessions past their TTL. Without it, a
// signed-in session whose ID is never presented again would linger forever
// (Session_Get only evicts on a lookup of that same ID).
func (a *API) sweepSessions() {
for range time.Tick(sessionSweepEvery) {
a.sessionsMu.Lock()
for id, s := range a.sessions {
if timeSince(s.LastSeenAt) > sessionTTLSecs {
delete(a.sessions, id)
}
}
a.sessionsMu.Unlock()
}
if err := bcrypt.CompareHashAndPassword(conf.Password, []byte(pwd)); err != nil {
return ErrNotAuthorized
}
a.sessionsMu.Lock()
defer a.sessionsMu.Unlock()
s, ok := a.sessions[sessionID]
if !ok {
// Session expired or was evicted between fetch and sign-in.
return ErrNotAuthorized
}
s.SignedIn = true
return nil
}
func (a *API) Network_Create(n *Network) error {
@@ -153,7 +173,6 @@ func (a *API) Network_List() ([]*Network, error) {
}
func (a *API) Peer_CreateNew(p *Peer) error {
p.Version = idgen.NextID(0)
p.WGPubKey = []byte{}
p.SignPubKey = []byte{}
p.APIKey = idgen.NewToken()
@@ -175,21 +194,12 @@ func (a *API) Peer_Init(peer *Peer, args m.PeerInitArgs) error {
return errors.New("peer already initialized")
}
peer.Version = idgen.NextID(0)
peer.WGPubKey = args.WGPubKey
peer.SignPubKey = args.SignPubKey
return db.Peer_UpdateFull(a.db, peer)
}
func (a *API) Peer_Update(p *Peer) error {
a.lock.Lock()
defer a.lock.Unlock()
p.Version = idgen.NextID(0)
return db.Peer_Update(a.db, p)
}
func (a *API) Peer_Delete(networkID int64, peerIP byte) error {
return db.Peer_Delete(a.db, networkID, peerIP)
}

View File

@@ -341,7 +341,6 @@ func Network_List(
type Peer struct {
NetworkID int64
PeerIP byte
Version int64
APIKey string
Name string
Addr4 []byte
@@ -352,7 +351,7 @@ type Peer struct {
SignPubKey []byte
}
const Peer_SelectQuery = "SELECT NetworkID,PeerIP,Version,APIKey,Name,Addr4,Addr6,Port,Relay,WGPubKey,SignPubKey FROM peers"
const Peer_SelectQuery = "SELECT NetworkID,PeerIP,APIKey,Name,Addr4,Addr6,Port,Relay,WGPubKey,SignPubKey FROM peers"
func Peer_Insert(
tx TX,
@@ -363,7 +362,7 @@ func Peer_Insert(
return err
}
_, err = tx.Exec("INSERT INTO peers(NetworkID,PeerIP,Version,APIKey,Name,Addr4,Addr6,Port,Relay,WGPubKey,SignPubKey) VALUES(?,?,?,?,?,?,?,?,?,?,?)", row.NetworkID, row.PeerIP, row.Version, row.APIKey, row.Name, row.Addr4, row.Addr6, row.Port, row.Relay, row.WGPubKey, row.SignPubKey)
_, err = tx.Exec("INSERT INTO peers(NetworkID,PeerIP,APIKey,Name,Addr4,Addr6,Port,Relay,WGPubKey,SignPubKey) VALUES(?,?,?,?,?,?,?,?,?,?)", row.NetworkID, row.PeerIP, row.APIKey, row.Name, row.Addr4, row.Addr6, row.Port, row.Relay, row.WGPubKey, row.SignPubKey)
return err
}
@@ -376,7 +375,7 @@ func Peer_Update(
return err
}
result, err := tx.Exec("UPDATE peers SET Version=?,Name=?,Addr4=?,Addr6=?,Port=?,Relay=? WHERE NetworkID=? AND PeerIP=?", row.Version, row.Name, row.Addr4, row.Addr6, row.Port, row.Relay, row.NetworkID, row.PeerIP)
result, err := tx.Exec("UPDATE peers SET Name=?,Addr4=?,Addr6=?,Port=?,Relay=? WHERE NetworkID=? AND PeerIP=?", row.Name, row.Addr4, row.Addr6, row.Port, row.Relay, row.NetworkID, row.PeerIP)
if err != nil {
return err
}
@@ -404,7 +403,7 @@ func Peer_UpdateFull(
return err
}
result, err := tx.Exec("UPDATE peers SET Version=?,APIKey=?,Name=?,Addr4=?,Addr6=?,Port=?,Relay=?,WGPubKey=?,SignPubKey=? WHERE NetworkID=? AND PeerIP=?", row.Version, row.APIKey, row.Name, row.Addr4, row.Addr6, row.Port, row.Relay, row.WGPubKey, row.SignPubKey, row.NetworkID, row.PeerIP)
result, err := tx.Exec("UPDATE peers SET APIKey=?,Name=?,Addr4=?,Addr6=?,Port=?,Relay=?,WGPubKey=?,SignPubKey=? WHERE NetworkID=? AND PeerIP=?", row.APIKey, row.Name, row.Addr4, row.Addr6, row.Port, row.Relay, row.WGPubKey, row.SignPubKey, row.NetworkID, row.PeerIP)
if err != nil {
return err
}
@@ -456,8 +455,8 @@ func Peer_Get(
err error,
) {
row = &Peer{}
r := tx.QueryRow("SELECT NetworkID,PeerIP,Version,APIKey,Name,Addr4,Addr6,Port,Relay,WGPubKey,SignPubKey FROM peers WHERE NetworkID=? AND PeerIP=?", NetworkID, PeerIP)
if err = r.Scan(&row.NetworkID, &row.PeerIP, &row.Version, &row.APIKey, &row.Name, &row.Addr4, &row.Addr6, &row.Port, &row.Relay, &row.WGPubKey, &row.SignPubKey); err != nil {
r := tx.QueryRow("SELECT NetworkID,PeerIP,APIKey,Name,Addr4,Addr6,Port,Relay,WGPubKey,SignPubKey FROM peers WHERE NetworkID=? AND PeerIP=?", NetworkID, PeerIP)
if err = r.Scan(&row.NetworkID, &row.PeerIP, &row.APIKey, &row.Name, &row.Addr4, &row.Addr6, &row.Port, &row.Relay, &row.WGPubKey, &row.SignPubKey); err != nil {
row = nil
}
return
@@ -473,7 +472,7 @@ func Peer_GetWhere(
) {
row = &Peer{}
r := tx.QueryRow(query, args...)
if err = r.Scan(&row.NetworkID, &row.PeerIP, &row.Version, &row.APIKey, &row.Name, &row.Addr4, &row.Addr6, &row.Port, &row.Relay, &row.WGPubKey, &row.SignPubKey); err != nil {
if err = r.Scan(&row.NetworkID, &row.PeerIP, &row.APIKey, &row.Name, &row.Addr4, &row.Addr6, &row.Port, &row.Relay, &row.WGPubKey, &row.SignPubKey); err != nil {
row = nil
}
return
@@ -495,7 +494,7 @@ func Peer_Iterate(
defer rows.Close()
for rows.Next() {
row := &Peer{}
err := rows.Scan(&row.NetworkID, &row.PeerIP, &row.Version, &row.APIKey, &row.Name, &row.Addr4, &row.Addr6, &row.Port, &row.Relay, &row.WGPubKey, &row.SignPubKey)
err := rows.Scan(&row.NetworkID, &row.PeerIP, &row.APIKey, &row.Name, &row.Addr4, &row.Addr6, &row.Port, &row.Relay, &row.WGPubKey, &row.SignPubKey)
if !yield(row, err) {
return
}

View File

@@ -12,13 +12,12 @@ TABLE networks OF Network (
TABLE peers OF Peer (
NetworkID int64 PK,
PeerIP byte PK,
Version int64,
APIKey string NoUpdate,
Name string,
Addr4 []byte,
Addr6 []byte,
Port uint16,
Relay bool,
Name string NoUpdate,
Addr4 []byte NoUpdate,
Addr6 []byte NoUpdate,
Port uint16 NoUpdate,
Relay bool NoUpdate,
WGPubKey []byte NoUpdate,
SignPubKey []byte NoUpdate
);

View File

@@ -12,7 +12,6 @@ CREATE TABLE networks (
CREATE TABLE peers (
NetworkID INTEGER NOT NULL,
PeerIP INTEGER NOT NULL, -- Final byte of IP.
Version INTEGER NOT NULL, -- Changes when updated.
APIKey TEXT NOT NULL UNIQUE, -- Peer's secret API key.
Name TEXT NOT NULL, -- For humans.
Addr4 BLOB NOT NULL,

View File

@@ -20,10 +20,6 @@ func (app *App) handlePub(pattern string, fn handlerFunc) {
return
}
if s.SessionID != sessionID {
app.setCookie(w, SESSION_ID_COOKIE_NAME, s.SessionID)
}
if r.Method == http.MethodPost {
r.ParseMultipartForm(64 * 1024)
} else {

View File

@@ -34,9 +34,11 @@ func (a *App) _signinSubmit(s *api.Session, w http.ResponseWriter, r *http.Reque
return err
}
if err := a.api.Session_SignIn(s.SessionID, pwd); err != nil {
sess, err := a.api.Session_SignIn(pwd)
if err != nil {
return err
}
a.setCookie(w, SESSION_ID_COOKIE_NAME, sess.SessionID)
return a.redirect(w, r, "/")
}
@@ -185,52 +187,6 @@ func (a *App) _adminPeerView(s *api.Session, w http.ResponseWriter, r *http.Requ
}{s, net, peer})
}
func (a *App) _adminPeerEdit(s *api.Session, w http.ResponseWriter, r *http.Request) error {
net, peer, err := a.formGetPeer(r.Form)
if err != nil {
return err
}
return a.render("/network/peer-edit.html", w, struct {
Session *api.Session
Network *api.Network
Peer *api.Peer
}{s, net, peer})
}
func (a *App) _adminPeerEditSubmit(s *api.Session, w http.ResponseWriter, r *http.Request) error {
_, peer, err := a.formGetPeer(r.Form)
if err != nil {
return err
}
var addr4Str, addr6Str string
err = webutil.NewFormScanner(r.Form).
Scan("Name", &peer.Name).
Scan("Addr4", &addr4Str).
Scan("Addr6", &addr6Str).
Scan("Port", &peer.Port).
Scan("Relay", &peer.Relay).
Error()
if err != nil {
return err
}
if peer.Addr4, err = stringToIP(addr4Str); err != nil {
return err
}
if peer.Addr6, err = stringToIP(addr6Str); err != nil {
return err
}
if err = a.api.Peer_Update(peer); err != nil {
return err
}
return a.redirect(w, r, "/admin/peer/view/?NetworkID=%d&PeerIP=%d", peer.NetworkID, peer.PeerIP)
}
func (a *App) _adminPeerDelete(s *api.Session, w http.ResponseWriter, r *http.Request) error {
n, peer, err := a.formGetPeer(r.Form)
if err != nil {
@@ -378,7 +334,6 @@ func (a *App) peersList(networkID int64) (peers []m.Peer, err error) {
copy(signKey[:], p.SignPubKey)
peers = append(peers, m.Peer{
PeerIP: p.PeerIP,
Version: p.Version,
Name: p.Name,
Addr4: addrFromBytes(p.Addr4),
Addr6: addrFromBytes(p.Addr6),

View File

@@ -22,8 +22,6 @@ func (a *App) registerRoutes() {
a.handleSignedIn("GET /admin/peer/create/", a._adminPeerCreate)
a.handleSignedIn("POST /admin/peer/create/", a._adminPeerCreateSubmit)
a.handleSignedIn("GET /admin/peer/view/", a._adminPeerView)
a.handleSignedIn("GET /admin/peer/edit/", a._adminPeerEdit)
a.handleSignedIn("POST /admin/peer/edit/", a._adminPeerEditSubmit)
a.handleSignedIn("GET /admin/peer/delete/", a._adminPeerDelete)
a.handleSignedIn("POST /admin/peer/delete/", a._adminPeerDeleteSubmit)

View File

@@ -4,7 +4,7 @@
{{with .Peer -}}
<form method="POST">
<input type="hidden" name="NetworkID" value="{{.NetworkID}}">
<input type="hidden" name="NetworkID" value="{{.PeerIP}}">
<input type="hidden" name="PeerIP" value="{{.PeerIP}}">
<p>
<button type="submit">Delete</button>
<a href="/admin/peer/view/?PeerIP={{.PeerIP}}&NetworkID={{.NetworkID}}">Cancel</a>

View File

@@ -1,38 +0,0 @@
{{define "body" -}}
<h2>Edit Peer</h2>
{{with .Peer -}}
<form method="POST">
<p>
<label>Peer IP</label><br>
<input type="text" value="{{.PeerIP}}" disabled>
</p>
<p>
<label>Name</label><br>
<input type="text" name="Name" value="{{.Name}}">
</p>
<p>
<label>IPv4 Address (optional)</label><br>
<input type="text" name="Addr4" value="{{ipToString .Addr4}}">
</p>
<p>
<label>IPv6 Address (optional)</label><br>
<input type="text" name="Addr6" value="{{ipToString .Addr6}}">
</p>
<p>
<label>WireGuard Port</label><br>
<input type="number" name="Port" value="{{.Port}}">
</p>
<p>
<label>
<input type="checkbox" name="Relay" {{if .Relay}}checked{{end}}>
Relay
</label>
</p>
<p>
<button type="submit">Save</button>
<a href="/admin/peer/view/?NetworkID={{$.Network.NetworkID}}&PeerIP={{.PeerIP}}">Cancel</a>
</p>
</form>
{{- end}}
{{- end}}

View File

@@ -1,7 +1,6 @@
{{define "body" -}}
<h3>{{.Peer.Name}}</h3>
<p>
<a href="/admin/peer/edit/?NetworkID={{.Network.NetworkID}}&PeerIP={{.Peer.PeerIP}}">Edit</a> /
<a href="/admin/peer/delete/?NetworkID={{.Network.NetworkID}}&PeerIP={{.Peer.PeerIP}}">Delete</a>
</p>