From 136d8db79994c68f341e3f4da36d7c6793b45b63 Mon Sep 17 00:00:00 2001 From: Peter Rounce Date: Thu, 4 Aug 2022 15:06:01 +0000 Subject: [PATCH] add CLI command to create a new card, fixes #1 --- .gitignore | 3 +- boltcard.service | 4 +- create_db.sql | 4 ++ createboltcard/database.go | 76 ++++++++++++++++++++++++++++++++++++++ createboltcard/main.go | 51 +++++++++++++++++++++++++ database.go | 31 ++++++++++++++++ lnurlw_request.go | 3 +- main.go | 1 + new_card_request.go | 71 +++++++++++++++++++++++++++++++++++ s_build | 1 - 10 files changed, 240 insertions(+), 5 deletions(-) create mode 100644 createboltcard/database.go create mode 100644 createboltcard/main.go create mode 100644 new_card_request.go diff --git a/.gitignore b/.gitignore index 8d528c8..c6bb7a3 100644 --- a/.gitignore +++ b/.gitignore @@ -4,7 +4,8 @@ *.dll *.so *.dylib -lnurlw +boltcard +createboltcard/createboltcard # Test binary, built with `go test -c` *.test diff --git a/boltcard.service b/boltcard.service index b6a9149..5704ff6 100644 --- a/boltcard.service +++ b/boltcard.service @@ -25,8 +25,8 @@ Environment="DB_USER=cardapp" Environment="DB_PASSWORD=database_password" Environment="DB_NAME=card_db" -# LNURLW_CB_URL is the URL prefix for the lnurlw callback -Environment="LNURLW_CB_URL=https://card.yourdomain.com/cb" +# BASE_URL is the URL base prefix for the lnurlw calls +Environment="HOST_DOMAIN=card.whitewolftech.com" # MIN_WITHDRAW_SATS & MAX_WITHDRAW_SATS set the values for the lnurlw response Environment="MIN_WITHDRAW_SATS=1" diff --git a/create_db.sql b/create_db.sql index 553e343..20a0306 100644 --- a/create_db.sql +++ b/create_db.sql @@ -7,6 +7,7 @@ CREATE DATABASE card_db; CREATE TABLE cards ( card_id INT GENERATED ALWAYS AS IDENTITY, + lock_key CHAR(32) NOT NULL, aes_cmac CHAR(32) NOT NULL, uid CHAR(14) NOT NULL, last_counter_value INTEGER NOT NULL, @@ -15,6 +16,9 @@ CREATE TABLE cards ( tx_limit_sats INT NOT NULL, day_limit_sats INT NOT NULL, card_description VARCHAR(100) NOT NULL DEFAULT '', + one_time_code CHAR(32) NOT NULL DEFAULT '', + one_time_code_expiry TIMESTAMPTZ DEFAULT NOW() + INTERVAL '1 DAY', + one_time_code_used CHAR(1) NOT NULL DEFAULT 'Y', PRIMARY KEY(card_id) ); diff --git a/createboltcard/database.go b/createboltcard/database.go new file mode 100644 index 0000000..9dce657 --- /dev/null +++ b/createboltcard/database.go @@ -0,0 +1,76 @@ +package main + +import ( + "database/sql" + "errors" + "fmt" + _ "github.com/lib/pq" + "os" +) + +func db_open() (*sql.DB, error) { + + // get connection string from environment variables + + conn := fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=disable", + os.Getenv("DB_HOST"), + os.Getenv("DB_PORT"), + os.Getenv("DB_USER"), + os.Getenv("DB_PASSWORD"), + os.Getenv("DB_NAME")) + + db, err := sql.Open("postgres", conn) + if err != nil { + return db, err + } + + return db, nil +} + +func db_delete_expired() error { + + db, err := db_open() + if err != nil { + return err + } + defer db.Close() + + // delete expired one time code records + + sqlStatement := `DELETE FROM cards WHERE one_time_code_expiry < NOW() AND one_time_code_used = 'N';` + _, err = db.Exec(sqlStatement) + if err != nil { + return err + } + + return nil +} + +func db_insert_card(one_time_code string, lock_key string, aes_cmac string) error { + + db, err := db_open() + if err != nil { + return err + } + defer db.Close() + + // insert a new record into cards + + sqlStatement := `INSERT INTO cards` + + ` (one_time_code, lock_key, aes_cmac, uid, last_counter_value,` + + ` lnurlw_request_timeout_sec, tx_limit_sats, day_limit_sats, one_time_code_used)` + + ` VALUES ($1, $2, $3, '', 0, 60, 1000, 10000, 'N');` + res, err := db.Exec(sqlStatement, one_time_code, lock_key, aes_cmac) + if err != nil { + return err + } + count, err := res.RowsAffected() + if err != nil { + return err + } + if count != 1 { + return errors.New("not one card record inserted") + } + + return nil +} diff --git a/createboltcard/main.go b/createboltcard/main.go new file mode 100644 index 0000000..6768cd5 --- /dev/null +++ b/createboltcard/main.go @@ -0,0 +1,51 @@ +package main + +import ( + "crypto/rand" + "encoding/hex" + "fmt" + log "github.com/sirupsen/logrus" + qrcode "github.com/skip2/go-qrcode" + "os" +) + +func random_hex() string { + b := make([]byte, 16) + _, err := rand.Read(b) + if err != nil { + log.Warn(err.Error()) + return "" + } + + return hex.EncodeToString(b) +} + +func main() { + one_time_code := random_hex() + lock_key := random_hex() + aes_cmac := random_hex() + + // create the new card record + + err := db_insert_card(one_time_code, lock_key, aes_cmac) + if err != nil { + log.Warn(err.Error()) + return + } + + // remove any expired records + + err = db_delete_expired() + if err != nil { + log.Warn(err.Error()) + return + } + + // show a QR code on the console for the URI + one_time_code + + hostdomain := os.Getenv("HOST_DOMAIN") + url := "https://" + hostdomain + "/new?a=" + one_time_code + fmt.Println(url) + q, err := qrcode.New(url, qrcode.Medium) + fmt.Println(q.ToSmallString(false)) +} diff --git a/database.go b/database.go index b007843..e3244dd 100644 --- a/database.go +++ b/database.go @@ -19,6 +19,8 @@ type card struct { enable_flag string tx_limit_sats int day_limit_sats int + one_time_code string + lock_key string } type payment struct { @@ -47,6 +49,35 @@ func db_open() (*sql.DB, error) { return db, nil } +func db_get_new_card(one_time_code string) (*card, error) { + c := card{} + + db, err := db_open() + if err != nil { + return &c, err + } + defer db.Close() + + sqlStatement := `SELECT lock_key, aes_cmac` + + ` FROM cards WHERE one_time_code=$1 AND` + + ` one_time_code_expiry > NOW() AND one_time_code_used = 'N';` + row := db.QueryRow(sqlStatement, one_time_code) + err = row.Scan( + &c.lock_key, + &c.aes_cmac) + if err != nil { + return &c, err + } + + sqlStatement = `UPDATE cards SET one_time_code_used = 'Y' WHERE one_time_code = $1;` + _, err = db.Exec(sqlStatement, one_time_code) + if err != nil { + return &c, err + } + + return &c, nil +} + func db_get_card_from_uid(card_uid string) (*card, error) { c := card{} diff --git a/lnurlw_request.go b/lnurlw_request.go index dd6faf5..2a2283d 100644 --- a/lnurlw_request.go +++ b/lnurlw_request.go @@ -179,7 +179,8 @@ func lnurlw_response(w http.ResponseWriter, req *http.Request) { return } - lnurlw_cb_url := os.Getenv("LNURLW_CB_URL") + host_domain := os.Getenv("HOST_DOMAIN") + lnurlw_cb_url := "https://" + host_domain + "/cb" min_withdraw_sats_str := os.Getenv("MIN_WITHDRAW_SATS") min_withdraw_sats, err := strconv.Atoi(min_withdraw_sats_str) diff --git a/main.go b/main.go index 5ec3aa1..87852a4 100644 --- a/main.go +++ b/main.go @@ -19,6 +19,7 @@ func main() { mux := http.NewServeMux() + mux.HandleFunc("/new", new_card_request) mux.HandleFunc("/ln", lnurlw_response) mux.HandleFunc("/cb", lnurlw_callback) diff --git a/new_card_request.go b/new_card_request.go new file mode 100644 index 0000000..3190714 --- /dev/null +++ b/new_card_request.go @@ -0,0 +1,71 @@ +package main + +import ( + "encoding/json" + log "github.com/sirupsen/logrus" + "net/http" + "os" +) + +type NewCardResponse struct { + K0 string `json:"k0"` + K1 string `json:"k1"` + K2 string `json:"k2"` +} + +func new_card_request(w http.ResponseWriter, req *http.Request) { + + url := req.URL.RequestURI() + log.Debug("new_card url: ", url) + + params_a, ok := req.URL.Query()["a"] + if !ok || len(params_a[0]) < 1 { + log.Debug("a not found") + return + } + + a := params_a[0] + + if a == "00000000000000000000000000000000" { + response := NewCardResponse{} + response.K0 = "11111111111111111111111111111111" + response.K1 = "22222222222222222222222222222222" + response.K2 = "33333333333333333333333333333333" + log.Debug("special a = 0...0") + + jsonData, err := json.Marshal(response) + if err != nil { + log.Warn(err) + return + } + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + w.Write(jsonData) + + return; + } + + c, err := db_get_new_card(a) + if err != nil { + log.Warn(err) + return + } + + aes_decrypt_key := os.Getenv("AES_DECRYPT_KEY") + + response := NewCardResponse{} + response.K0 = c.lock_key + response.K1 = aes_decrypt_key + response.K2 = c.aes_cmac + + jsonData, err := json.Marshal(response) + if err != nil { + log.Warn(err) + return + } + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + w.Write(jsonData) +} diff --git a/s_build b/s_build index e1722d5..c814186 100755 --- a/s_build +++ b/s_build @@ -2,4 +2,3 @@ go build sudo systemctl daemon-reload sudo systemctl stop boltcard sudo systemctl start boltcard -