Test login + callback

This commit is contained in:
ekzyis 2023-10-04 15:37:04 +02:00
parent c66e96bf3f
commit fc6fb9914e
10 changed files with 243 additions and 8 deletions

View File

@ -10,3 +10,5 @@ delphi.market: $(SOURCE)
run:
go run .
test:
go test ./server/... ./db/... ./lnd/...

View File

@ -2,6 +2,8 @@ package db
import (
"database/sql"
"fmt"
"io/ioutil"
_ "github.com/lib/pq"
)
@ -10,6 +12,10 @@ type DB struct {
*sql.DB
}
var (
initSqlPath = "./db/init.sql"
)
func New(dbUrl string) (*DB, error) {
var (
db_ *sql.DB
@ -27,3 +33,39 @@ func New(dbUrl string) (*DB, error) {
db = &DB{DB: db_}
return db, nil
}
func (db *DB) Reset(dbName string) error {
var (
f []byte
err error
)
if err = db.Clear(dbName); err != nil {
return err
}
if f, err = ioutil.ReadFile(initSqlPath); err != nil {
return err
}
if _, err = db.Exec(string(f)); err != nil {
return err
}
return nil
}
func (db *DB) Clear(dbName string) error {
var (
tables = []string{"lnauth", "users", "sessions", "markets", "shares", "invoices", "order_side", "orders", "matches"}
sql []string
err error
)
for _, t := range tables {
sql = append(sql, fmt.Sprintf("DROP TABLE IF EXISTS %s CASCADE", t))
}
sql = append(sql, "DROP EXTENSION IF EXISTS \"uuid-ossp\"")
sql = append(sql, "DROP TYPE IF EXISTS order_side")
for _, s := range sql {
if _, err = db.Exec(s); err != nil {
return err
}
}
return nil
}

View File

@ -12,7 +12,7 @@ type (
LNAuth struct {
K1 string
LNURL string
CreatdAt time.Time
CreatedAt time.Time
SessionId string
}
User struct {

View File

@ -7,6 +7,10 @@ func (db *DB) CreateUser(u *User) error {
return err
}
func (db *DB) FetchUser(pubkey string, u *User) error {
return db.QueryRow("SELECT pubkey, last_seen FROM users WHERE pubkey = $1", pubkey).Scan(&u.Pubkey, &u.LastSeen)
}
func (db *DB) UpdateUser(u *User) error {
_, err := db.Exec("UPDATE users SET last_seen = $1 WHERE pubkey = $2", u.LastSeen, u.Pubkey)
return err

3
go.mod
View File

@ -14,6 +14,7 @@ require (
github.com/lightningnetwork/lnd v0.10.0-beta.rc6.0.20200615174244-103c59a4889f
github.com/namsral/flag v1.7.4-pre
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
github.com/stretchr/testify v1.8.1
golang.org/x/exp v0.0.0-20230905200255-921286631fa9
gopkg.in/guregu/null.v4 v4.0.0
)
@ -72,6 +73,7 @@ require (
github.com/miekg/dns v0.0.0-20171125082028-79bfde677fa8 // indirect
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
github.com/modern-go/reflect2 v1.0.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_golang v0.9.3 // indirect
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 // indirect
github.com/prometheus/common v0.4.0 // indirect
@ -98,5 +100,6 @@ require (
gopkg.in/macaroon-bakery.v2 v2.0.1 // indirect
gopkg.in/macaroon.v2 v2.1.0 // indirect
gopkg.in/yaml.v2 v2.2.3 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
sigs.k8s.io/yaml v1.1.0 // indirect
)

6
go.sum
View File

@ -253,12 +253,17 @@ github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasO
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5 h1:LnC5Kc/wtumK+WB441p7ynQJzVuNRJiqddSIE3IlSEQ=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
@ -396,6 +401,7 @@ gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

View File

@ -0,0 +1,182 @@
package handler_test
import (
"crypto/ecdsa"
"crypto/rand"
"database/sql"
"encoding/hex"
"fmt"
"io"
"net/http"
"net/http/httptest"
"os"
"path"
"runtime"
"testing"
db_ "git.ekzyis.com/ekzyis/delphi.market/db"
"git.ekzyis.com/ekzyis/delphi.market/server/auth"
"git.ekzyis.com/ekzyis/delphi.market/server/router"
"git.ekzyis.com/ekzyis/delphi.market/server/router/context"
"git.ekzyis.com/ekzyis/delphi.market/server/router/handler"
"github.com/decred/dcrd/dcrec/secp256k1/v4"
"github.com/labstack/echo/v4"
"github.com/stretchr/testify/assert"
)
var (
dbName string = "delphi_test"
dbUrl string = fmt.Sprintf("postgres://delphi:delphi@localhost:5432/%s?sslmode=disable", dbName)
db *db_.DB
)
func init() {
// for ParseTemplates to work, cwd needs to be project root
_, filename, _, _ := runtime.Caller(0)
dir := path.Join(path.Dir(filename), "../../..")
err := os.Chdir(dir)
if err != nil {
panic(err)
}
if db, err = db_.New(dbUrl); err != nil {
panic(err)
}
}
func TestMain(m *testing.M) {
if err := db.Reset(dbName); err != nil {
panic(err)
}
retCode := m.Run()
if err := db.Clear(dbName); err != nil {
panic(err)
}
os.Exit(retCode)
}
func mocks(method string, target string, body io.Reader) (*echo.Echo, context.ServerContext, *http.Request, *httptest.ResponseRecorder) {
var (
e *echo.Echo
sc context.ServerContext
req *http.Request
rec *httptest.ResponseRecorder
)
e = echo.New()
e.Renderer = router.ParseTemplates("pages/**.html")
sc = context.ServerContext{
Db: db,
}
req = httptest.NewRequest(method, target, body)
rec = httptest.NewRecorder()
return e, sc, req, rec
}
func TestLogin(t *testing.T) {
var (
assert = assert.New(t)
e *echo.Echo
c echo.Context
sc context.ServerContext
req *http.Request
rec *httptest.ResponseRecorder
cookies []*http.Cookie
sessionId string
dbSessionId string
err error
)
e, sc, req, rec = mocks("GET", "/login", nil)
c = e.NewContext(req, rec)
err = handler.HandleLogin(sc)(c)
assert.NoErrorf(err, "handler returned error")
// Set-Cookie header present
cookies = rec.Result().Cookies()
assert.Equalf(len(cookies), 1, "wrong number of Set-Cookie headers")
assert.Equalf(cookies[0].Name, "session", "wrong cookie name")
// new challenge inserted
sessionId = cookies[0].Value
err = db.QueryRow("SELECT session_id FROM lnauth WHERE session_id = $1", sessionId).Scan(&dbSessionId)
if !assert.NoError(err) {
return
}
// inserted challenge matches cookie value
assert.Equalf(sessionId, dbSessionId, "wrong session id")
}
func TestLoginCallback(t *testing.T) {
var (
assert = assert.New(t)
e *echo.Echo
c echo.Context
sc context.ServerContext
req *http.Request
rec *httptest.ResponseRecorder
lnAuth *auth.LNAuth
dbLnAuth *db_.LNAuth
u *db_.User
s *db_.Session
key string
sig string
err error
)
lnAuth, err = auth.NewLNAuth()
if !assert.NoErrorf(err, "error creating challenge") {
return
}
dbLnAuth = &db_.LNAuth{K1: lnAuth.K1, LNURL: lnAuth.LNURL}
err = db.CreateLNAuth(dbLnAuth)
if !assert.NoErrorf(err, "error inserting challenge") {
return
}
key, sig, err = sign(lnAuth.K1)
if !assert.NoErrorf(err, "error signing k1") {
return
}
e, sc, req, rec = mocks("GET", fmt.Sprintf("/api/login?k1=%s&key=%s&sig=%s", lnAuth.K1, key, sig), nil)
c = e.NewContext(req, rec)
err = handler.HandleLoginCallback(sc)(c)
assert.NoErrorf(err, "handler returned error")
// user created
u = new(db_.User)
err = db.FetchUser(key, u)
if assert.NoErrorf(err, "error fetching user") {
assert.Equalf(u.Pubkey, key, "pubkeys do not match")
}
// session created
s = &db_.Session{SessionId: dbLnAuth.SessionId}
err = db.FetchSession(s)
if assert.NoErrorf(err, "error fetching session") {
assert.Equalf(s.Pubkey, u.Pubkey, "session pubkey does not match user pubkey")
}
// challenge deleted
err = db.FetchSessionId(u.Pubkey, &dbLnAuth.SessionId)
assert.ErrorIs(err, sql.ErrNoRows, "challenge not deleted")
}
func sign(k1_ string) (string, string, error) {
var (
sk *secp256k1.PrivateKey
k1 []byte
sig []byte
err error
)
if k1, err = hex.DecodeString(k1_); err != nil {
return "", "", err
}
if sk, err = secp256k1.GeneratePrivateKey(); err != nil {
return "", "", err
}
if sig, err = ecdsa.SignASN1(rand.Reader, sk.ToECDSA(), k1); err != nil {
return "", "", err
}
return hex.EncodeToString(sk.PubKey().SerializeCompressed()), hex.EncodeToString(sig), nil
}

View File

@ -20,12 +20,8 @@ func (t *Template) Render(w io.Writer, name string, data interface{}, c echo.Con
return t.templates.ExecuteTemplate(w, name, data)
}
var (
T *Template
)
func init() {
T = &Template{
func ParseTemplates(pattern string) *Template {
return &Template{
templates: template.Must(template.New("").Funcs(template.FuncMap{
"add": add[int64],
"sub": sub[int64],

View File

@ -20,7 +20,7 @@ func New(ctx ServerContext) *Server {
)
e = echo.New()
e.Static("/", "public")
e.Renderer = router.T
e.Renderer = router.ParseTemplates("pages/**.html")
e.Use(middleware.LoggerWithConfig(middleware.LoggerConfig{
Format: "${time_custom} ${method} ${uri} ${status}\n",
CustomTimeFormat: "2006-01-02 15:04:05.00000-0700",