195 lines
		
	
	
		
			3.8 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			195 lines
		
	
	
		
			3.8 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
 | |
| }
 | |
| 
 | |
| 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,
 | |
| 	}
 | |
| 
 | |
| 	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,
 | |
| 		VPNNetwork: []byte{10, 1, 1, 0},
 | |
| 		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) Config_UpdatePassword(pwdHash []byte) error {
 | |
| 	return db.Config_UpdatePassword(a.db, pwdHash)
 | |
| }
 | |
| 
 | |
| func (a *API) Session_Delete(sessionID string) error {
 | |
| 	return db.Session_Delete(a.db, sessionID)
 | |
| }
 | |
| 
 | |
| func (a *API) Session_Get(sessionID string) (*Session, error) {
 | |
| 	if sessionID == "" {
 | |
| 		return a.session_CreatePub()
 | |
| 	}
 | |
| 
 | |
| 	session, err := db.Session_Get(a.db, sessionID)
 | |
| 	if err != nil {
 | |
| 		return a.session_CreatePub()
 | |
| 	}
 | |
| 
 | |
| 	if timeSince(session.LastSeenAt) > 86400*21 {
 | |
| 		return a.session_CreatePub()
 | |
| 	}
 | |
| 
 | |
| 	if timeSince(session.LastSeenAt) > 86400*7 {
 | |
| 		session.LastSeenAt = time.Now().Unix()
 | |
| 		if err := db.Session_UpdateLastSeenAt(a.db, session.SessionID); err != nil {
 | |
| 			log.Printf("Failed to update session: %v", err)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return session, nil
 | |
| }
 | |
| 
 | |
| func (a *API) session_CreatePub() (*Session, error) {
 | |
| 	s := &Session{
 | |
| 		SessionID:  idgen.NewToken(),
 | |
| 		CSRF:       idgen.NewToken(),
 | |
| 		SignedIn:   false,
 | |
| 		CreatedAt:  time.Now().Unix(),
 | |
| 		LastSeenAt: time.Now().Unix(),
 | |
| 	}
 | |
| 	err := db.Session_Insert(a.db, s)
 | |
| 	return s, err
 | |
| }
 | |
| 
 | |
| func (a *API) Session_DeleteBefore(timestamp int64) error {
 | |
| 	return db.Session_DeleteBefore(a.db, timestamp)
 | |
| }
 | |
| 
 | |
| 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
 | |
| 	}
 | |
| 
 | |
| 	return db.Session_SetSignedIn(a.db, s.SessionID)
 | |
| }
 | |
| 
 | |
| func (a *API) Peer_CreateNew(p *Peer) error {
 | |
| 	p.Version = idgen.NextID(0)
 | |
| 	p.PubKey = []byte{}
 | |
| 	p.PubSignKey = []byte{}
 | |
| 	p.APIKey = idgen.NewToken()
 | |
| 
 | |
| 	return db.Peer_Insert(a.db, p)
 | |
| }
 | |
| 
 | |
| func (a *API) Peer_Init(peer *Peer, args m.PeerInitArgs) (*m.PeerConfig, error) {
 | |
| 	a.lock.Lock()
 | |
| 	defer a.lock.Unlock()
 | |
| 
 | |
| 	peer.Version = idgen.NextID(0)
 | |
| 	peer.PubKey = args.EncPubKey
 | |
| 	peer.PubSignKey = args.PubSignKey
 | |
| 
 | |
| 	if err := db.Peer_UpdateFull(a.db, peer); err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	conf := a.Config_Get()
 | |
| 
 | |
| 	return &m.PeerConfig{
 | |
| 		PeerIP:   peer.PeerIP,
 | |
| 		Network:  conf.VPNNetwork,
 | |
| 		PublicIP: peer.PublicIP,
 | |
| 		Port:     peer.Port,
 | |
| 		Relay:    peer.Relay,
 | |
| 	}, nil
 | |
| }
 | |
| 
 | |
| 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(ip byte) error {
 | |
| 	return db.Peer_Delete(a.db, ip)
 | |
| }
 | |
| 
 | |
| func (a *API) Peer_List() ([]*Peer, error) {
 | |
| 	return db.Peer_ListAll(a.db)
 | |
| }
 | |
| 
 | |
| func (a *API) Peer_Get(ip byte) (*Peer, error) {
 | |
| 	return db.Peer_Get(a.db, ip)
 | |
| }
 | |
| 
 | |
| func (a *API) Peer_GetByAPIKey(key string) (*Peer, error) {
 | |
| 	return db.Peer_GetByAPIKey(a.db, key)
 | |
| }
 |