add CLI command to create a new card, fixes #1
This commit is contained in:
parent
efeb32b09f
commit
136d8db799
10 changed files with 240 additions and 5 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -4,7 +4,8 @@
|
||||||
*.dll
|
*.dll
|
||||||
*.so
|
*.so
|
||||||
*.dylib
|
*.dylib
|
||||||
lnurlw
|
boltcard
|
||||||
|
createboltcard/createboltcard
|
||||||
|
|
||||||
# Test binary, built with `go test -c`
|
# Test binary, built with `go test -c`
|
||||||
*.test
|
*.test
|
||||||
|
|
|
||||||
|
|
@ -25,8 +25,8 @@ Environment="DB_USER=cardapp"
|
||||||
Environment="DB_PASSWORD=database_password"
|
Environment="DB_PASSWORD=database_password"
|
||||||
Environment="DB_NAME=card_db"
|
Environment="DB_NAME=card_db"
|
||||||
|
|
||||||
# LNURLW_CB_URL is the URL prefix for the lnurlw callback
|
# BASE_URL is the URL base prefix for the lnurlw calls
|
||||||
Environment="LNURLW_CB_URL=https://card.yourdomain.com/cb"
|
Environment="HOST_DOMAIN=card.whitewolftech.com"
|
||||||
|
|
||||||
# MIN_WITHDRAW_SATS & MAX_WITHDRAW_SATS set the values for the lnurlw response
|
# MIN_WITHDRAW_SATS & MAX_WITHDRAW_SATS set the values for the lnurlw response
|
||||||
Environment="MIN_WITHDRAW_SATS=1"
|
Environment="MIN_WITHDRAW_SATS=1"
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ CREATE DATABASE card_db;
|
||||||
|
|
||||||
CREATE TABLE cards (
|
CREATE TABLE cards (
|
||||||
card_id INT GENERATED ALWAYS AS IDENTITY,
|
card_id INT GENERATED ALWAYS AS IDENTITY,
|
||||||
|
lock_key CHAR(32) NOT NULL,
|
||||||
aes_cmac CHAR(32) NOT NULL,
|
aes_cmac CHAR(32) NOT NULL,
|
||||||
uid CHAR(14) NOT NULL,
|
uid CHAR(14) NOT NULL,
|
||||||
last_counter_value INTEGER NOT NULL,
|
last_counter_value INTEGER NOT NULL,
|
||||||
|
|
@ -15,6 +16,9 @@ CREATE TABLE cards (
|
||||||
tx_limit_sats INT NOT NULL,
|
tx_limit_sats INT NOT NULL,
|
||||||
day_limit_sats INT NOT NULL,
|
day_limit_sats INT NOT NULL,
|
||||||
card_description VARCHAR(100) NOT NULL DEFAULT '',
|
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)
|
PRIMARY KEY(card_id)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
76
createboltcard/database.go
Normal file
76
createboltcard/database.go
Normal file
|
|
@ -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
|
||||||
|
}
|
||||||
51
createboltcard/main.go
Normal file
51
createboltcard/main.go
Normal file
|
|
@ -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))
|
||||||
|
}
|
||||||
31
database.go
31
database.go
|
|
@ -19,6 +19,8 @@ type card struct {
|
||||||
enable_flag string
|
enable_flag string
|
||||||
tx_limit_sats int
|
tx_limit_sats int
|
||||||
day_limit_sats int
|
day_limit_sats int
|
||||||
|
one_time_code string
|
||||||
|
lock_key string
|
||||||
}
|
}
|
||||||
|
|
||||||
type payment struct {
|
type payment struct {
|
||||||
|
|
@ -47,6 +49,35 @@ func db_open() (*sql.DB, error) {
|
||||||
return db, nil
|
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) {
|
func db_get_card_from_uid(card_uid string) (*card, error) {
|
||||||
|
|
||||||
c := card{}
|
c := card{}
|
||||||
|
|
|
||||||
|
|
@ -179,7 +179,8 @@ func lnurlw_response(w http.ResponseWriter, req *http.Request) {
|
||||||
return
|
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_str := os.Getenv("MIN_WITHDRAW_SATS")
|
||||||
min_withdraw_sats, err := strconv.Atoi(min_withdraw_sats_str)
|
min_withdraw_sats, err := strconv.Atoi(min_withdraw_sats_str)
|
||||||
|
|
|
||||||
1
main.go
1
main.go
|
|
@ -19,6 +19,7 @@ func main() {
|
||||||
|
|
||||||
mux := http.NewServeMux()
|
mux := http.NewServeMux()
|
||||||
|
|
||||||
|
mux.HandleFunc("/new", new_card_request)
|
||||||
mux.HandleFunc("/ln", lnurlw_response)
|
mux.HandleFunc("/ln", lnurlw_response)
|
||||||
mux.HandleFunc("/cb", lnurlw_callback)
|
mux.HandleFunc("/cb", lnurlw_callback)
|
||||||
|
|
||||||
|
|
|
||||||
71
new_card_request.go
Normal file
71
new_card_request.go
Normal file
|
|
@ -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)
|
||||||
|
}
|
||||||
1
s_build
1
s_build
|
|
@ -2,4 +2,3 @@ go build
|
||||||
sudo systemctl daemon-reload
|
sudo systemctl daemon-reload
|
||||||
sudo systemctl stop boltcard
|
sudo systemctl stop boltcard
|
||||||
sudo systemctl start boltcard
|
sudo systemctl start boltcard
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue