266 lines
		
	
	
		
			5.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			266 lines
		
	
	
		
			5.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package api
 | |
| 
 | |
| import (
 | |
| 	"crypto/rand"
 | |
| 	"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"
 | |
| 	"golang.org/x/crypto/nacl/box"
 | |
| 	"golang.org/x/crypto/nacl/sign"
 | |
| )
 | |
| 
 | |
| //go:embed migrations
 | |
| var migrations embed.FS
 | |
| 
 | |
| type API struct {
 | |
| 	db          *sql.DB
 | |
| 	lock        sync.Mutex
 | |
| 	peerIntents map[string]PeerCreateArgs
 | |
| }
 | |
| 
 | |
| 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,
 | |
| 		peerIntents: map[string]PeerCreateArgs{},
 | |
| 	}
 | |
| 
 | |
| 	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)
 | |
| }
 | |
| 
 | |
| type PeerCreateArgs struct {
 | |
| 	Name     string
 | |
| 	PublicIP []byte
 | |
| 	Port     uint16
 | |
| 	Mediator bool
 | |
| }
 | |
| 
 | |
| // Create the intention to add a peer. The returned code is used to complete
 | |
| // the peer creation. The code is valid for 5 minutes.
 | |
| func (a *API) Peer_CreateIntent(args PeerCreateArgs) string {
 | |
| 	a.lock.Lock()
 | |
| 	defer a.lock.Unlock()
 | |
| 
 | |
| 	code := idgen.NewToken()
 | |
| 	a.peerIntents[code] = args
 | |
| 
 | |
| 	go func() {
 | |
| 		time.Sleep(5 * time.Minute)
 | |
| 		a.lock.Lock()
 | |
| 		defer a.lock.Unlock()
 | |
| 		delete(a.peerIntents, code)
 | |
| 	}()
 | |
| 
 | |
| 	return code
 | |
| }
 | |
| 
 | |
| func (a *API) Peer_Create(creationCode string) (*m.PeerConfig, error) {
 | |
| 	a.lock.Lock()
 | |
| 	defer a.lock.Unlock()
 | |
| 
 | |
| 	args, ok := a.peerIntents[creationCode]
 | |
| 	if !ok {
 | |
| 		return nil, ErrNotAuthorized
 | |
| 	}
 | |
| 
 | |
| 	delete(a.peerIntents, creationCode)
 | |
| 
 | |
| 	encPubKey, encPrivKey, err := box.GenerateKey(rand.Reader)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	signPubKey, signPrivKey, err := sign.GenerateKey(rand.Reader)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	// Get peer IP.
 | |
| 	peerIP := byte(0)
 | |
| 
 | |
| 	for i := byte(1); i < 255; i++ {
 | |
| 		exists, err := db.Peer_Exists(a.db, i)
 | |
| 		if err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 		if !exists {
 | |
| 			peerIP = i
 | |
| 			break
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if peerIP == 0 {
 | |
| 		return nil, ErrNoIPAvailable
 | |
| 	}
 | |
| 
 | |
| 	peer := &Peer{
 | |
| 		PeerIP:     peerIP,
 | |
| 		Version:    idgen.NextID(0),
 | |
| 		APIKey:     idgen.NewToken(),
 | |
| 		Name:       args.Name,
 | |
| 		PublicIP:   args.PublicIP,
 | |
| 		Port:       args.Port,
 | |
| 		Mediator:   args.Mediator,
 | |
| 		EncPubKey:  encPubKey[:],
 | |
| 		SignPubKey: signPubKey[:],
 | |
| 	}
 | |
| 
 | |
| 	if err := db.Peer_Insert(a.db, peer); err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	conf := a.Config_Get()
 | |
| 
 | |
| 	return &m.PeerConfig{
 | |
| 		PeerIP:      peer.PeerIP,
 | |
| 		HubAddress:  conf.HubAddress,
 | |
| 		APIKey:      peer.APIKey,
 | |
| 		Network:     conf.VPNNetwork,
 | |
| 		PublicIP:    peer.PublicIP,
 | |
| 		Port:        peer.Port,
 | |
| 		Mediator:    peer.Mediator,
 | |
| 		EncPubKey:   encPubKey[:],
 | |
| 		EncPrivKey:  encPrivKey[:],
 | |
| 		SignPubKey:  signPubKey[:],
 | |
| 		SignPrivKey: signPrivKey[:],
 | |
| 	}, 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)
 | |
| }
 |