Files
vppn/hub/api/api.go
2026-06-04 16:00:21 +02:00

188 lines
3.7 KiB
Go

package api
import (
"database/sql"
"embed"
"errors"
"log"
"sync"
"time"
"vppn/hub/api/db"
"vppn/m"
"git.crumpington.com/lib/go/idgen"
"git.crumpington.com/lib/go/sqliteutil"
"golang.org/x/crypto/bcrypt"
)
//go:embed migrations
var migrations embed.FS
type API struct {
db *sql.DB
lock sync.Mutex
sessionsMu sync.Mutex
sessions map[string]*Session
}
func New(dbPath string) (*API, error) {
sqlDB, err := sql.Open("sqlite3", dbPath+"?_journal=WAL")
if err != nil {
return nil, err
}
if err := sqliteutil.Migrate(sqlDB, migrations); err != nil {
return nil, err
}
a := &API{
db: sqlDB,
sessions: make(map[string]*Session),
}
return a, a.ensurePassword()
}
func (a *API) ensurePassword() error {
_, err := db.Config_Get(a.db, 1)
if err == nil {
return nil
}
if !errors.Is(err, sql.ErrNoRows) {
return err
}
pwd := idgen.NewToken()
log.Printf("Setting password: %s", pwd)
hashed, err := bcrypt.GenerateFromPassword([]byte(pwd), bcrypt.DefaultCost)
if err != nil {
return err
}
conf := &Config{ConfigID: 1, Password: hashed}
return db.Config_Insert(a.db, conf)
}
func (a *API) Config_Get() *Config {
conf, err := db.Config_Get(a.db, 1)
if err != nil {
panic(err)
}
return conf
}
func (a *API) Config_Update(conf *Config) error {
return db.Config_Update(a.db, conf)
}
func (a *API) Session_Delete(sessionID string) error {
a.sessionsMu.Lock()
defer a.sessionsMu.Unlock()
delete(a.sessions, sessionID)
return nil
}
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)
}
}
return a.session_Create(), nil
}
// caller must hold sessionsMu
func (a *API) session_Create() *Session {
s := &Session{
SessionID: idgen.NewToken(),
CreatedAt: time.Now().Unix(),
LastSeenAt: time.Now().Unix(),
}
a.sessions[s.SessionID] = s
return s
}
func (a *API) Session_SignIn(s *Session, pwd string) error {
conf := a.Config_Get()
if err := bcrypt.CompareHashAndPassword(conf.Password, []byte(pwd)); err != nil {
return ErrNotAuthorized
}
a.sessionsMu.Lock()
s.SignedIn = true
a.sessionsMu.Unlock()
return nil
}
func (a *API) Network_Create(n *Network) error {
n.NetworkID = idgen.NextID(0)
return db.Network_Insert(a.db, n)
}
func (a *API) Network_Delete(n *Network) error {
return db.Network_Delete(a.db, n.NetworkID)
}
func (a *API) Network_Get(id int64) (*Network, error) {
return db.Network_Get(a.db, id)
}
func (a *API) Network_List() ([]*Network, error) {
const query = db.Network_SelectQuery + ` ORDER BY Name ASC`
return db.Network_List(a.db, query)
}
func (a *API) Peer_CreateNew(p *Peer) error {
p.Version = idgen.NextID(0)
p.WGPubKey = []byte{}
p.APIKey = idgen.NewToken()
return db.Peer_Insert(a.db, p)
}
func (a *API) Peer_Init(peer *Peer, args m.PeerInitArgs) error {
a.lock.Lock()
defer a.lock.Unlock()
peer.Version = idgen.NextID(0)
peer.WGPubKey = args.WGPubKey
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)
}
func (a *API) Peer_List(networkID int64) ([]*Peer, error) {
return db.Peer_ListAll(a.db, networkID)
}
func (a *API) Peer_Get(networkID int64, ip byte) (*Peer, error) {
return db.Peer_Get(a.db, networkID, ip)
}
func (a *API) Peer_GetByAPIKey(key string) (*Peer, error) {
return db.Peer_GetByAPIKey(a.db, key)
}