124 lines
3.9 KiB
Go
124 lines
3.9 KiB
Go
package broker
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"git.asio.asia/nixevol/NixMsg/internal/auth"
|
|
"git.asio.asia/nixevol/NixMsg/internal/store"
|
|
)
|
|
|
|
func TestSessionIdleOKUsesOnlineOffline(t *testing.T) {
|
|
now := time.UnixMilli(1_700_000_000_000)
|
|
idleDays := 1
|
|
old := now.Add(-48 * time.Hour).UnixMilli()
|
|
recentOffline := now.Add(-2 * time.Hour).UnixMilli()
|
|
if sessionIdleOK(now, old, 0, 0, idleDays) {
|
|
t.Fatal("stale used_at should expire")
|
|
}
|
|
if !sessionIdleOK(now, old, now.UnixMilli(), 0, idleDays) {
|
|
t.Fatal("currently online should not expire")
|
|
}
|
|
if !sessionIdleOK(now, old, 0, recentOffline, idleDays) {
|
|
t.Fatal("recent offline_since should keep token")
|
|
}
|
|
}
|
|
|
|
func TestPasswordLoginConditionalUpdate(t *testing.T) {
|
|
dir := t.TempDir()
|
|
db, err := store.Open(dir, "FULL")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Cleanup(func() { _ = db.Close() })
|
|
pool := auth.NewStubHashPool()
|
|
login := NewLogin(LoginOptions{DB: db, Pool: pool, Tokens: auth.NewSessionTokens(), Locks: auth.NewLoginLocks(), IdleDays: 30})
|
|
phc, _ := pool.Hash(context.Background(), auth.PasswordLogin, "password1")
|
|
_ = db.Queue.Do(context.Background(), func(tx *sql.Tx) error {
|
|
_, e := tx.Exec(`INSERT INTO endpoints(id, name, login_hash, talk_hash, talk_version, default_delay_ms, enabled, created_at)
|
|
VALUES ('ep-cond', '', ?, NULL, 0, 0, 1, ?)`, phc, time.Now().UnixMilli())
|
|
return e
|
|
})
|
|
res, err := login.Authenticate(context.Background(), "ep-cond", []byte("password1"), "1.1.1.1")
|
|
if err != nil || !res.OK || res.SessionToken == "" {
|
|
t.Fatalf("first login %+v err=%v", res, err)
|
|
}
|
|
ok, err := login.TokenMatchesDB(context.Background(), "ep-cond", res.SessionToken)
|
|
if err != nil || !ok {
|
|
t.Fatalf("match=%v err=%v", ok, err)
|
|
}
|
|
res2, err := login.Authenticate(context.Background(), "ep-cond", []byte("password1"), "1.1.1.1")
|
|
if err != nil || !res2.OK {
|
|
t.Fatalf("second login %+v err=%v", res2, err)
|
|
}
|
|
ok, _ = login.TokenMatchesDB(context.Background(), "ep-cond", res.SessionToken)
|
|
if ok {
|
|
t.Fatal("old token should not match after second login")
|
|
}
|
|
ok, _ = login.TokenMatchesDB(context.Background(), "ep-cond", res2.SessionToken)
|
|
if !ok {
|
|
t.Fatal("new token should match")
|
|
}
|
|
}
|
|
|
|
func TestAuthenticateRespectsCanceledContext(t *testing.T) {
|
|
dir := t.TempDir()
|
|
db, err := store.Open(dir, "FULL")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Cleanup(func() { _ = db.Close() })
|
|
pool := &blockingPool{ready: make(chan struct{}), release: make(chan struct{})}
|
|
login := NewLogin(LoginOptions{DB: db, Pool: pool, Tokens: auth.NewSessionTokens(), Locks: auth.NewLoginLocks()})
|
|
phc, _ := auth.NewStubHashPool().Hash(context.Background(), auth.PasswordLogin, "password1")
|
|
_ = db.Queue.Do(context.Background(), func(tx *sql.Tx) error {
|
|
_, e := tx.Exec(`INSERT INTO endpoints(id, name, login_hash, talk_hash, talk_version, default_delay_ms, enabled, created_at)
|
|
VALUES ('ep-to', '', ?, NULL, 0, 0, 1, ?)`, phc, time.Now().UnixMilli())
|
|
return e
|
|
})
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
var wg sync.WaitGroup
|
|
wg.Add(1)
|
|
var gotErr error
|
|
go func() {
|
|
defer wg.Done()
|
|
_, gotErr = login.Authenticate(ctx, "ep-to", []byte("password1"), "9.9.9.9")
|
|
}()
|
|
select {
|
|
case <-pool.ready:
|
|
case <-time.After(2 * time.Second):
|
|
t.Fatal("verify did not start")
|
|
}
|
|
cancel()
|
|
wg.Wait()
|
|
close(pool.release)
|
|
if gotErr == nil {
|
|
t.Fatal("expected canceled auth")
|
|
}
|
|
}
|
|
|
|
type blockingPool struct {
|
|
ready chan struct{}
|
|
release chan struct{}
|
|
once sync.Once
|
|
}
|
|
|
|
func (p *blockingPool) Hash(ctx context.Context, kind auth.PasswordKind, password string) (string, error) {
|
|
return auth.NewStubHashPool().Hash(ctx, kind, password)
|
|
}
|
|
|
|
func (p *blockingPool) Verify(ctx context.Context, _ auth.PasswordKind, _, _ string) (bool, error) {
|
|
p.once.Do(func() { close(p.ready) })
|
|
select {
|
|
case <-ctx.Done():
|
|
return false, ctx.Err()
|
|
case <-p.release:
|
|
return true, nil
|
|
}
|
|
}
|
|
|
|
func (p *blockingPool) QueueLen() int { return 0 }
|