Merge pull request #60 from boltcard/card-pin-number

Card pin number
This commit is contained in:
Peter Rounce 2023-07-20 12:05:51 +01:00 committed by GitHub
commit de14c32867
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 163 additions and 43 deletions

View file

@ -29,6 +29,9 @@ type Card struct {
One_time_code string
Card_name string
Allow_negative_balance string
Pin_enable string
Pin_number string
Pin_limit_sats int
}
type Payment struct {
@ -350,7 +353,8 @@ func Get_card_from_card_id(card_id int) (*Card, error) {
`last_counter_value, lnurlw_request_timeout_sec, ` +
`lnurlw_enable, tx_limit_sats, day_limit_sats, ` +
`email_enable, email_address, card_name, ` +
`allow_negative_balance FROM cards WHERE card_id=$1;`
`allow_negative_balance, pin_enable, pin_number, ` +
`pin_limit_sats FROM cards WHERE card_id=$1;`
row := db.QueryRow(sqlStatement, card_id)
err = row.Scan(
&c.Card_id,
@ -364,7 +368,10 @@ func Get_card_from_card_id(card_id int) (*Card, error) {
&c.Email_enable,
&c.Email_address,
&c.Card_name,
&c.Allow_negative_balance)
&c.Allow_negative_balance,
&c.Pin_enable,
&c.Pin_number,
&c.Pin_limit_sats)
if err != nil {
return &c, err
}
@ -385,7 +392,7 @@ func Get_card_from_card_name(card_name string) (*Card, error) {
sqlStatement := `SELECT card_id, k2_cmac_key, uid,` +
` last_counter_value, lnurlw_request_timeout_sec,` +
` lnurlw_enable, tx_limit_sats, day_limit_sats` +
` lnurlw_enable, tx_limit_sats, day_limit_sats, pin_enable, pin_limit_sats` +
` FROM cards WHERE card_name=$1 AND wiped = 'N';`
row := db.QueryRow(sqlStatement, card_name)
err = row.Scan(
@ -396,7 +403,9 @@ func Get_card_from_card_name(card_name string) (*Card, error) {
&c.Lnurlw_request_timeout_sec,
&c.Lnurlw_enable,
&c.Tx_limit_sats,
&c.Day_limit_sats)
&c.Day_limit_sats,
&c.Pin_enable,
&c.Pin_limit_sats)
if err != nil {
return &c, err
}
@ -773,7 +782,7 @@ func Get_card_name_count(card_name string) (card_count int, err error) {
func Insert_card(one_time_code string, k0_auth_key string, k2_cmac_key string, k3 string, k4 string,
tx_limit_sats int, day_limit_sats int, lnurlw_enable bool, card_name string, uid_privacy bool,
allow_neg_bal_ptr bool) error {
allow_neg_bal_ptr bool, pin_enable bool, pin_number string, pin_limit_sats int) error {
lnurlw_enable_yn := "N"
if lnurlw_enable {
@ -790,6 +799,11 @@ func Insert_card(one_time_code string, k0_auth_key string, k2_cmac_key string, k
allow_neg_bal_yn = "Y"
}
pin_enable_yn := "N"
if pin_enable {
pin_enable_yn = "Y"
}
db, err := open()
if err != nil {
return err
@ -811,11 +825,12 @@ func Insert_card(one_time_code string, k0_auth_key string, k2_cmac_key string, k
sqlStatement = `INSERT INTO cards` +
` (one_time_code, k0_auth_key, k2_cmac_key, k3, k4, uid, last_counter_value,` +
` lnurlw_request_timeout_sec, tx_limit_sats, day_limit_sats, lnurlw_enable,` +
` one_time_code_used, card_name, uid_privacy, allow_negative_balance)` +
` VALUES ($1, $2, $3, $4, $5, '', 0, 60, $6, $7, $8, 'N', $9, $10, $11);`
` one_time_code_used, card_name, uid_privacy, allow_negative_balance,` +
` pin_enable, pin_number, pin_limit_sats)` +
` VALUES ($1, $2, $3, $4, $5, '', 0, 60, $6, $7, $8, 'N', $9, $10, $11, $12, $13, $14);`
res, err = db.Exec(sqlStatement, one_time_code, k0_auth_key, k2_cmac_key, k3, k4,
tx_limit_sats, day_limit_sats, lnurlw_enable_yn, card_name, uid_privacy_yn,
allow_neg_bal_yn)
allow_neg_bal_yn, pin_enable_yn, pin_number, pin_limit_sats)
if err != nil {
return err
}
@ -871,13 +886,19 @@ func Wipe_card(card_name string) (*Card_wipe_info, error) {
return &card_wipe_info, nil
}
func Update_card(card_name string, lnurlw_enable bool, tx_limit_sats int, day_limit_sats int) error {
func Update_card(card_name string, lnurlw_enable bool, tx_limit_sats int, day_limit_sats int,
pin_enable bool, pin_number string, pin_limit_sats int) error {
lnurlw_enable_yn := "N"
if lnurlw_enable {
lnurlw_enable_yn = "Y"
}
pin_enable_yn := "N"
if pin_enable {
pin_enable_yn = "Y"
}
db, err := open()
if err != nil {
@ -886,10 +907,11 @@ func Update_card(card_name string, lnurlw_enable bool, tx_limit_sats int, day_li
defer db.Close()
sqlStatement := `UPDATE cards SET lnurlw_enable = $2, tx_limit_sats = $3, day_limit_sats = $4 ` +
`WHERE card_name = $1 AND wiped = 'N';`
sqlStatement := `UPDATE cards SET lnurlw_enable = $2, tx_limit_sats = $3, day_limit_sats = $4, ` +
`pin_enable = $5, pin_number = $6, pin_limit_sats = $7 WHERE card_name = $1 AND wiped = 'N';`
res, err := db.Exec(sqlStatement, card_name, lnurlw_enable_yn, tx_limit_sats, day_limit_sats)
res, err := db.Exec(sqlStatement, card_name, lnurlw_enable_yn, tx_limit_sats, day_limit_sats,
pin_enable_yn, pin_number, pin_limit_sats)
if err != nil {
return err

View file

@ -83,12 +83,33 @@ func Createboltcard(w http.ResponseWriter, r *http.Request) {
return
}
pin_enable_flag_str := r.URL.Query().Get("enable_pin")
pin_enable_flag, err := strconv.ParseBool(pin_enable_flag_str)
if err != nil {
msg := "updateboltcard: enable_pin is not a valid boolean"
log.Warn(msg)
resp_err.Write_message(w, msg)
return
}
pin_number := r.URL.Query().Get("pin_number")
pin_limit_sats_str := r.URL.Query().Get("pin_limit_sats")
pin_limit_sats, err := strconv.Atoi(pin_limit_sats_str)
if err != nil {
msg := "updateboltcard: pin_limit_sats is not a valid integer"
log.Warn(msg)
resp_err.Write_message(w, msg)
return
}
// log the request
log.WithFields(log.Fields{
"card_name": card_name, "tx_max": tx_max, "day_max": day_max,
"enable": enable_flag, "uid_privacy": uid_privacy_flag,
"allow_neg_bal": allow_neg_bal_flag}).Info("createboltcard API request")
"allow_neg_bal": allow_neg_bal_flag, "enable_pin": pin_enable_flag,
"pin_number": pin_number, "pin_limit_sats": pin_limit_sats}).Info("createboltcard API request")
// create the keys
@ -102,7 +123,7 @@ func Createboltcard(w http.ResponseWriter, r *http.Request) {
err = db.Insert_card(one_time_code, k0_auth_key, k2_cmac_key, k3, k4,
tx_max, day_max, enable_flag, card_name,
uid_privacy_flag, allow_neg_bal_flag)
uid_privacy_flag, allow_neg_bal_flag, pin_enable_flag, pin_number, pin_limit_sats)
if err != nil {
log.Warn(err.Error())
return

View file

@ -37,7 +37,9 @@ func Getboltcard(w http.ResponseWriter, r *http.Request) {
`"uid": "` + c.Db_uid + `",` +
`"lnurlw_enable": "` + c.Lnurlw_enable + `",` +
`"tx_limit_sats": "` + strconv.Itoa(c.Tx_limit_sats) + `",` +
`"day_limit_sats": "` + strconv.Itoa(c.Day_limit_sats) + `"}`)
`"day_limit_sats": "` + strconv.Itoa(c.Day_limit_sats) + `", ` +
`"pin_enable": "` + c.Pin_enable + `", ` +
`"pin_limit_sats": "` + strconv.Itoa(c.Pin_limit_sats) + `"}`)
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)

View file

@ -43,6 +43,26 @@ func Updateboltcard(w http.ResponseWriter, r *http.Request) {
return
}
pin_enable_flag_str := r.URL.Query().Get("enable_pin")
pin_enable_flag, err := strconv.ParseBool(pin_enable_flag_str)
if err != nil {
msg := "updateboltcard: enable_pin is not a valid boolean"
log.Warn(msg)
resp_err.Write_message(w, msg)
return
}
pin_number := r.URL.Query().Get("pin_number")
pin_limit_sats_str := r.URL.Query().Get("pin_limit_sats")
pin_limit_sats, err := strconv.Atoi(pin_limit_sats_str)
if err != nil {
msg := "updateboltcard: pin_limit_sats is not a valid integer"
log.Warn(msg)
resp_err.Write_message(w, msg)
return
}
card_name := r.URL.Query().Get("card_name")
// check if card_name exists
@ -64,11 +84,12 @@ func Updateboltcard(w http.ResponseWriter, r *http.Request) {
log.WithFields(log.Fields{
"card_name": card_name, "tx_max": tx_max, "day_max": day_max,
"enable": enable_flag}).Info("updateboltcard API request")
"enable": enable_flag, "enable_pin": pin_enable_flag,
"pin_number": pin_number, "pin_limit_sats": pin_limit_sats}).Info("updateboltcard API request")
// update the card record
err = db.Update_card(card_name, enable_flag, tx_max, day_max)
err = db.Update_card(card_name, enable_flag, tx_max, day_max, pin_enable_flag, pin_number, pin_limit_sats)
if err != nil {
log.Warn(err.Error())
return

View file

@ -225,17 +225,15 @@ func Callback(w http.ResponseWriter, req *http.Request) {
url := req.URL.RequestURI()
log.WithFields(log.Fields{"url": url}).Debug("cb request")
// check k1 value
params_k1, ok := req.URL.Query()["k1"]
// get k1 value
param_k1 := req.URL.Query().Get("k1")
if !ok || len(params_k1[0]) < 1 {
if param_k1 == "" {
log.WithFields(log.Fields{"url": url}).Debug("k1 not found")
resp_err.Write(w)
return
}
param_k1 := params_k1[0]
p, err := db.Get_payment_k1(param_k1)
if err != nil {
log.WithFields(log.Fields{"url": url, "k1": param_k1}).Warn(err)
@ -263,14 +261,14 @@ func Callback(w http.ResponseWriter, req *http.Request) {
return
}
params_pr, ok := req.URL.Query()["pr"]
if !ok || len(params_pr[0]) < 1 {
// get the payment request
param_pr := req.URL.Query().Get("pr")
if param_pr == "" {
log.WithFields(log.Fields{"card_payment_id": p.Card_payment_id}).Warn("pr field not found")
resp_err.Write(w)
return
}
param_pr := params_pr[0]
bolt11, _ := decodepay.Decodepay(param_pr)
// record the lightning invoice
@ -283,6 +281,23 @@ func Callback(w http.ResponseWriter, req *http.Request) {
log.WithFields(log.Fields{"card_payment_id": p.Card_payment_id}).Debug("checking payment rules")
// get the pin if it has been passed in
param_pin := req.URL.Query().Get("pin")
c, err := db.Get_card_from_card_id(p.Card_id)
if err != nil {
log.WithFields(log.Fields{"card_payment_id": p.Card_payment_id}).Warn(err)
resp_err.Write(w)
return
}
// check the pin if needed
if c.Pin_enable == "Y" && int(bolt11.MSatoshi/1000) >= c.Pin_limit_sats && c.Pin_number != param_pin {
log.WithFields(log.Fields{"card_payment_id": p.Card_payment_id}).Warn("incorrect pin provided")
resp_err.Write(w)
return
}
// check if we are only sending funds to a defined test node
testnode := db.Get_setting("LN_TESTNODE")
if testnode != "" && bolt11.Payee != testnode {

View file

@ -14,15 +14,6 @@ import (
"strings"
)
type ResponseData struct {
Tag string `json:"tag"`
Callback string `json:"callback"`
LnurlwK1 string `json:"k1"`
DefaultDescription string `json:"defaultDescription"`
MinWithdrawable int `json:"minWithdrawable"`
MaxWithdrawable int `json:"maxWithdrawable"`
}
func get_p_c(req *http.Request, p_name string, c_name string) (p string, c string) {
params_p, ok := req.URL.Query()[p_name]
@ -255,6 +246,7 @@ func parse_request(req *http.Request) (int, error) {
func Response(w http.ResponseWriter, req *http.Request) {
env_host_domain := db.Get_setting("HOST_DOMAIN")
if req.Host != env_host_domain {
log.Warn("wrong host domain")
resp_err.Write(w)
@ -312,15 +304,29 @@ func Response(w http.ResponseWriter, req *http.Request) {
return
}
defalut_description := db.Get_setting("DEFAULT_DESCRIPTION")
// get pin_enable & pin_limit_sats
response := ResponseData{}
response.Tag = "withdrawRequest"
response.Callback = lnurlw_cb_url
response.LnurlwK1 = lnurlw_k1
response.DefaultDescription = defalut_description
response.MinWithdrawable = min_withdraw_sats * 1000 // milliSats
response.MaxWithdrawable = max_withdraw_sats * 1000 // milliSats
c, err := db.Get_card_from_card_id(card_id)
if err != nil {
log.WithFields(log.Fields{"card_id": card_id}).Warn(err)
resp_err.Write(w)
return
}
default_description := db.Get_setting("DEFAULT_DESCRIPTION")
response := make(map[string]interface{})
response["tag"] = "withdrawRequest"
response["callback"] = lnurlw_cb_url
response["k1"] = lnurlw_k1
response["defaultDescription"] = default_description
response["minWithdrawable"] = min_withdraw_sats * 1000 // milliSats
response["maxWithdrawable"] = max_withdraw_sats * 1000 // milliSats
if c.Pin_enable == "Y" {
response["pinLimit"] = c.Pin_limit_sats * 1000 // milliSats
}
jsonData, err := json.Marshal(response)

View file

@ -15,7 +15,7 @@ if [ "$x" = "y" ]; then
psql postgres -f sql/create_db_init.sql
psql postgres -f sql/create_db.sql
psql postgres -f sql/create_db_user.sql
psql postgres -f sql/settings.sql
psql postgres -f sql/settings.sql.secret
echo Database created
else
echo No action

6
script/s_setup_test_data Executable file
View file

@ -0,0 +1,6 @@
# to close any database connections
sudo systemctl stop postgresql
sudo systemctl start postgresql
psql postgres -f sql/data.test.sql
echo Test data added

View file

@ -28,6 +28,9 @@ CREATE TABLE cards (
one_time_code_expiry TIMESTAMPTZ DEFAULT NOW() + INTERVAL '1 DAY',
one_time_code_used CHAR(1) NOT NULL DEFAULT 'Y',
allow_negative_balance CHAR(1) NOT NULL DEFAULT 'N',
pin_enable CHAR(1) NOT NULL DEFAULT 'N',
pin_number CHAR(4) NOT NULL DEFAULT '0000',
pin_limit_sats INT NOT NULL,
wiped CHAR(1) NOT NULL DEFAULT 'N',
PRIMARY KEY(card_id)
);

24
sql/data.test.sql Normal file
View file

@ -0,0 +1,24 @@
-- connect to card_db
\c card_db;
-- clear out table data
DELETE FROM settings;
DELETE FROM card_payments;
DELETE FROM card_receipts;
DELETE FROM cards;
-- set up test data
INSERT INTO settings (name, value) VALUES ('LOG_LEVEL', 'DEBUG');
INSERT INTO settings (name, value) VALUES ('AES_DECRYPT_KEY', '994de7f8156609a0effafbdb049337b1');
INSERT INTO settings (name, value) VALUES ('HOST_DOMAIN', 'localhost:9000');
INSERT INTO settings (name, value) VALUES ('FUNCTION_INTERNAL_API', 'ENABLE');
INSERT INTO settings (name, value) VALUES ('MIN_WITHDRAW_SATS', '1');
INSERT INTO settings (name, value) VALUES ('MAX_WITHDRAW_SATS', '1000');
INSERT INTO cards
(k0_auth_key, k2_cmac_key, k3, k4, lnurlw_enable, last_counter_value, lnurlw_request_timeout_sec,
tx_limit_sats, day_limit_sats, card_name, pin_enable, pin_number, pin_limit_sats)
VALUES
('', 'd3dffa1e12d2477e443a6ee9fcfeab18', '', '', 'Y', 0, 10,
0, 0, 'test_card', 'Y', '1234', 1000);