support changing usernames with pins.

This commit is contained in:
fiatjaf
2021-08-20 09:32:45 -03:00
parent 15bab1fcfb
commit 2f6e17073f
8 changed files with 132 additions and 98 deletions

View File

@@ -1,4 +1,4 @@
satdress: $(shell find . -name "*.go") satdress: $(shell find . -name "*.go") index.html go.mod
CC=$$(which musl-gcc) go build -ldflags='-s -w -linkmode external -extldflags "-static"' -o ./satdress CC=$$(which musl-gcc) go build -ldflags='-s -w -linkmode external -extldflags "-static"' -o ./satdress
deploy: satdress deploy: satdress

53
db.go
View File

@@ -1,5 +1,16 @@
package main package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"github.com/cockroachdb/pebble"
)
type Params struct { type Params struct {
Name string Name string
Kind string Kind string
@@ -8,3 +19,45 @@ type Params struct {
Pak string Pak string
Waki string Waki string
} }
func SaveName(name string, params *Params, providedPin string) (pin string, err error) {
key := []byte(name + "@" + s.Domain)
mac := hmac.New(sha256.New, []byte(s.Secret))
mac.Write(key)
pin = hex.EncodeToString(mac.Sum(nil))
if _, closer, err := db.Get(key); err == nil {
defer closer.Close()
if pin != providedPin {
return "", errors.New("name already exists! must provide pin.")
}
}
// check if the given data works
if _, err := makeInvoice(params, 1000); err != nil {
return "", fmt.Errorf("couldn't make an invoice with the given data: %w", err)
}
// save it
data, _ := json.Marshal(params)
if err := db.Set(key, data, pebble.Sync); err != nil {
return "", err
}
return pin, nil
}
func GetName(name string) (params *Params, err error) {
val, closer, err := db.Get([]byte(name))
if err != nil {
return nil, err
}
defer closer.Close()
if err := json.Unmarshal(val, params); err != nil {
return nil, err
}
params.Name = name
return params, nil
}

1
go.mod
View File

@@ -7,6 +7,7 @@ require (
github.com/fiatjaf/go-lnurl v1.4.0 github.com/fiatjaf/go-lnurl v1.4.0
github.com/fiatjaf/makeinvoice v1.2.3 github.com/fiatjaf/makeinvoice v1.2.3
github.com/gorilla/mux v1.8.0 github.com/gorilla/mux v1.8.0
github.com/joho/godotenv v1.3.0 // indirect
github.com/kelseyhightower/envconfig v1.4.0 github.com/kelseyhightower/envconfig v1.4.0
github.com/lib/pq v1.10.2 github.com/lib/pq v1.10.2
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646

2
go.sum
View File

@@ -203,6 +203,8 @@ github.com/jackpal/go-nat-pmp v0.0.0-20170405195558-28a68d0c24ad/go.mod h1:QPH04
github.com/jedib0t/go-pretty v4.3.0+incompatible/go.mod h1:XemHduiw8R651AF9Pt4FwCTKeG3oo7hrHJAoznj9nag= github.com/jedib0t/go-pretty v4.3.0+incompatible/go.mod h1:XemHduiw8R651AF9Pt4FwCTKeG3oo7hrHJAoznj9nag=
github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
github.com/jrick/logrotate v1.0.0 h1:lQ1bL/n9mBNeIXoTUoYRlK4dHuNJVofX9oWqBtPnSzI= github.com/jrick/logrotate v1.0.0 h1:lQ1bL/n9mBNeIXoTUoYRlK4dHuNJVofX9oWqBtPnSzI=
github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=

View File

@@ -103,7 +103,17 @@
background-color: rgba(86, 46, 249, 0.85); background-color: rgba(86, 46, 249, 0.85);
} }
.label { .field {
display: flex;
flex-direction: column;
}
.row {
display: flex;
flex-direction: row;
}
label {
margin: 0; margin: 0;
font-size: 16px; font-size: 16px;
font-weight: 600; font-weight: 600;
@@ -147,11 +157,6 @@
background-color: #f3f3f3; background-color: #f3f3f3;
} }
label {
display: flex;
flex-direction: column;
}
.resources { .resources {
display: flex; display: flex;
align-items: center; align-items: center;
@@ -233,67 +238,79 @@
<div class="card"> <div class="card">
<div class="description">Use the form below to connect your own node to a <b>Lightning Address</b>.</div> <div class="description">Use the form below to connect your own node to a <b>Lightning Address</b>.</div>
<form action="/grab" method="post"> <form action="/grab" method="post">
<label> <div class="field">
<p class="label"> <div class="row" style="justify-content: space-between">
Desired Username <label for="name">
</p> Desired Username
</label>
<label style="float: right">
Is New?
<input type="checkbox" v-model="isNew">
</label>
</div>
<div style="position: relative;"> <div style="position: relative;">
<input class="input" name="name"> <input class="input" name="name" id="name">
<span class="suffix" style="position: absolute;">@{{ domain }}</span> <span class="suffix" style="position: absolute;">@{{ domain }}</span>
</div> </div>
</label> </div>
<label> <div class="field">
<p class="label"> <label for="kind">
Node Backend Type Node Backend Type
</p> </label>
<select name="kind" v-model="kind"> <select name="kind" id="kind" id="kind" v-model="kind">
<option value="lnd">LND</option> <option value="lnd">LND</option>
<option value="sparko">Sparko</option> <option value="sparko">Sparko</option>
<option value="lnpay">LNPay</option> <option value="lnpay">LNPay</option>
<option value="lnbits">LNbits</option> <option value="lnbits">LNbits</option>
</select> </select>
</label> </div>
<div v-if="kind == 'lnd'"> <div v-if="kind == 'lnd'">
<label> <div class="field">
<p class="label"> <label for="host">
Host (IP or Domain + Port) Host (IP or Domain + Port)
</p> </label>
<input class="input" name="host"> <input class="input" name="host" id="host">
</label> </div>
<label> <div class="field">
<p class="label"> <label for="key">
Invoice Macaroon Invoice Macaroon
</p> </label>
<input class="input" name="key"> <input class="input" name="key" id="key">
</label> </div>
</div> </div>
<div v-if="kind == 'sparko' || kind == 'lnbits'"> <div v-if="kind == 'sparko' || kind == 'lnbits'">
<label> <div class="field">
<p class="label"> <label for="host">
Host (IP or Domain + Port) Host (IP or Domain + Port)
</p> </label>
<input class="input" name="host"> <input class="input" name="host" id="host">
</label> </div>
<label> <div class="field">
<p class="label"> <label for="key">
Key Key
</p> </label>
<input class="input" name="key"> <input class="input" name="key" id="key">
</label> </div>
</div> </div>
<div v-if="kind == 'lnpay'"> <div v-if="kind == 'lnpay'">
<label> <div class="field">
<p class="label"> <label for="pak">
Public Access Key (pak) Public Access Key (pak)
</p> </label>
<input class="input" name="pak"> <input class="input" name="pak" id="pak">
</label> </div>
<label> <div class="field">
<p class="label"> <label for="waki">
Wallet Invoice Key (waki) Wallet Invoice Key (waki)
</p> </label>
<input class="input" name="waki"> <input class="input" name="waki" id="waki">
</div>
</div>
<div class="field" v-if="!isNew">
<label for="pin">
Pin
</label> </label>
<input class="input" name="pin" id="pin">
</div> </div>
<button class="submit">Submit</button> <button class="submit">Submit</button>
</form> </form>
@@ -323,6 +340,7 @@
data() { data() {
return { return {
kind: 'lnd', kind: 'lnd',
isNew: true,
...initial, ...initial,
} }
} }

View File

@@ -6,7 +6,6 @@ import (
"net/http" "net/http"
"strconv" "strconv"
"github.com/cockroachdb/pebble"
"github.com/fiatjaf/go-lnurl" "github.com/fiatjaf/go-lnurl"
"github.com/gorilla/mux" "github.com/gorilla/mux"
) )
@@ -14,25 +13,14 @@ import (
func handleLNURL(w http.ResponseWriter, r *http.Request) { func handleLNURL(w http.ResponseWriter, r *http.Request) {
username := mux.Vars(r)["username"] username := mux.Vars(r)["username"]
log.Info().Str("username", username).Msg("got lnurl request") params, err := GetName(username)
var params Params
val, closer, err := db.Get([]byte(username))
if err != nil { if err != nil {
if err != pebble.ErrNotFound { log.Error().Err(err).Str("name", username).Msg("failed to get name")
log.Error().Err(err).Str("name", username). json.NewEncoder(w).Encode(lnurl.ErrorResponse(fmt.Sprintf(
Msg("error getting data") "failed to get name %s", username)))
}
return
}
defer closer.Close()
if err := json.Unmarshal(val, &params); err != nil {
log.Error().Err(err).Str("name", username).Str("data", string(val)).
Msg("got broken json from db")
return
} }
params.Name = username log.Info().Str("username", username).Msg("got lnurl request")
if amount := r.URL.Query().Get("amount"); amount == "" { if amount := r.URL.Query().Get("amount"); amount == "" {
// check if the receiver accepts comments // check if the receiver accepts comments

36
main.go
View File

@@ -1,10 +1,7 @@
package main package main
import ( import (
"crypto/hmac"
"crypto/sha256"
_ "embed" _ "embed"
"encoding/hex"
"encoding/json" "encoding/json"
"fmt" "fmt"
"net/http" "net/http"
@@ -78,41 +75,16 @@ func main() {
router.Path("/grab").HandlerFunc( router.Path("/grab").HandlerFunc(
func(w http.ResponseWriter, r *http.Request) { func(w http.ResponseWriter, r *http.Request) {
name := []byte(r.FormValue("name") + "@" + s.Domain) pin, err := SaveName(r.FormValue("name"), &Params{
mac := hmac.New(sha256.New, []byte(s.Secret))
mac.Write(name)
pin := hex.EncodeToString(mac.Sum(nil))
if _, closer, err := db.Get(name); err == nil {
w.WriteHeader(401)
fmt.Fprint(w,
"name already exists! must provide pin (contact support).")
return
} else if err == nil {
closer.Close()
}
params := Params{
Kind: r.FormValue("kind"), Kind: r.FormValue("kind"),
Host: r.FormValue("host"), Host: r.FormValue("host"),
Key: r.FormValue("key"), Key: r.FormValue("key"),
Pak: r.FormValue("pak"), Pak: r.FormValue("pak"),
Waki: r.FormValue("waki"), Waki: r.FormValue("waki"),
} }, r.FormValue("pin"))
if err != nil {
// check if the given data works
if _, err := makeInvoice(params, 1000); err != nil {
w.WriteHeader(400)
fmt.Fprint(w, "couldn't make an invoice with the given data: "+err.Error())
return
}
// save it
data, _ := json.Marshal(params)
if err := db.Set(name, data, pebble.Sync); err != nil {
w.WriteHeader(500) w.WriteHeader(500)
fmt.Fprint(w, "error! "+err.Error()) fmt.Fprint(w, err.Error())
return return
} }

View File

@@ -9,7 +9,7 @@ import (
"github.com/tidwall/sjson" "github.com/tidwall/sjson"
) )
func makeMetadata(params Params) string { func makeMetadata(params *Params) string {
metadata, _ := sjson.Set("[]", "0.0", "text/identifier") metadata, _ := sjson.Set("[]", "0.0", "text/identifier")
metadata, _ = sjson.Set(metadata, "0.1", params.Name+"@"+s.Domain) metadata, _ = sjson.Set(metadata, "0.1", params.Name+"@"+s.Domain)
@@ -21,7 +21,7 @@ func makeMetadata(params Params) string {
return metadata return metadata
} }
func makeInvoice(params Params, msat int) (bolt11 string, err error) { func makeInvoice(params *Params, msat int) (bolt11 string, err error) {
// description_hash // description_hash
h := sha256.Sum256([]byte(makeMetadata(params))) h := sha256.Sum256([]byte(makeMetadata(params)))