From 59b041886098dda4ff38191e3dd704ec36360673 Mon Sep 17 00:00:00 2001 From: Roland Bewick Date: Fri, 19 Jan 2024 11:57:44 +0700 Subject: [PATCH] feat: only support single-user flow WIP --- .env.example | 8 +- README.md | 66 +-- alby.go | 742 ----------------------------- api.go | 2 +- config.go | 7 - echo_handlers.go | 24 +- frontend/src/App.tsx | 2 - frontend/src/components/Navbar.tsx | 79 --- frontend/src/hooks/useLogin.ts | 33 -- frontend/src/screens/Login.tsx | 55 --- frontend/src/types.ts | 2 +- go.mod | 2 - models.go | 52 -- service.go | 25 - wails_handlers.go | 6 - 15 files changed, 27 insertions(+), 1078 deletions(-) delete mode 100644 alby.go delete mode 100644 frontend/src/hooks/useLogin.ts delete mode 100644 frontend/src/screens/Login.tsx diff --git a/.env.example b/.env.example index 5aa2ed4f..fcc0827b 100644 --- a/.env.example +++ b/.env.example @@ -10,10 +10,4 @@ PORT=8080 #LN_BACKEND_TYPE=LND #LND_CERT_FILE=/home/YOUR_USERNAME/.polar/networks/1/volumes/lnd/alice/tls.cert #LND_ADDRESS=127.0.0.1:10001 -#LND_MACAROON_FILE=/home/YOUR_USERNAME/.polar/networks/1/volumes/lnd/alice/data/chain/bitcoin/regtest/admin.macaroon - -# Alby Wallet API Client -#LN_BACKEND_TYPE=ALBY -#ALBY_CLIENT_SECRET= -#ALBY_CLIENT_ID= -#OAUTH_REDIRECT_URL=http://localhost:8080/alby/callback \ No newline at end of file +#LND_MACAROON_FILE=/home/YOUR_USERNAME/.polar/networks/1/volumes/lnd/alice/data/chain/bitcoin/regtest/admin.macaroon \ No newline at end of file diff --git a/README.md b/README.md index 5035e787..290d5c29 100644 --- a/README.md +++ b/README.md @@ -75,16 +75,26 @@ Go to `/frontend` - `CLIENT_NOSTR_PUBKEY`: if set, this service will only listen to events authored by this public key. You can set this to your own nostr public key. - `RELAY`: default: "wss://relay.getalby.com/v1" - `PUBLIC_RELAY`: optional relay URL to be used in connection strings if `RELAY` is an internal URL -- `LN_BACKEND_TYPE`: ALBY or LND -- `ALBY_CLIENT_SECRET`= Alby OAuth client secret (used with the Alby backend) -- `ALBY_CLIENT_ID`= Alby OAuth client ID (used with the Alby backend) -- `OAUTH_REDIRECT_URL`= OAuth redirect URL (e.g. http://localhost:8080/alby/callback) (used with the Alby backend) -- `LND_ADDRESS`: the LND gRPC address, eg. `localhost:10009` (used with the LND backend) -- `LND_CERT_FILE`: the location where LND's `tls.cert` file can be found (used with the LND backend) -- `LND_MACAROON_FILE`: the location where LND's `admin.macaroon` file can be found (used with the LND backend) - `COOKIE_SECRET`: a randomly generated secret string. - `DATABASE_URI`: a postgres connection string or sqlite filename. Default: nostr-wallet-connect.db (sqlite) - `PORT`: the port on which the app should listen on (default: 8080) +- `LN_BACKEND_TYPE`: LND or BREEZ + +### LND Backend parameters + +_For cert and macaroon, either hex or file options can be used._ + +- `LND_ADDRESS`: the LND gRPC address, eg. `localhost:10009` (used with the LND backend) +- `LND_CERT_FILE`: the location where LND's `tls.cert` file can be found (used with the LND backend) +- `LND_CERT_HEX`: LND's hex-encoded `tls.cert` (used with the LND backend) +- `LND_MACAROON_FILE`: the location where LND's `admin.macaroon` file can be found (used with the LND backend) +- `LND_MACAROON_HEX`: LND's hex-encoded `admin.macaroon` (used with the LND backend) + +### BREEZ Backend parameters + +- `BREEZ_MNEMONIC`: your bip39 mnemonic key phrase e.g. "define limit soccer guilt trim mechanic beyond outside best give south shine" +- `BREEZ_API_KEY`: contact breez for more info +- `GREENLIGHT_INVITE_CODE`: contact blockstream for more info ## Application deeplink options @@ -179,49 +189,11 @@ You can also contribute to our [bounty program](https://github.com/getAlby/light ❌ `multi_pay_invoice` -❌ `multi_pay_keysend (TBC)` - -### Alby OAuth API - -✅ `get_info` - -- ⚠️ block_hash not supported -- ⚠️ block_height not supported -- ⚠️ pubkey not supported -- ⚠️ color not supported -- ⚠️ network is always `mainnet` - -✅ `get_balance` - -✅ `pay_invoice` - -- ⚠️ amount not supported (for amountless invoices) - -✅ `pay_keysend` - -- ⚠️ preimage in request not supported - -✅ `make_invoice` - -- ⚠️ expiry in request not supported - -✅ `lookup_invoice` - -- ⚠️ fees_paid in response not supported - -✅ `list_transactions` - -- ⚠️ offset and unpaid in request not supported -- ⚠️ fees_paid in response not supported -- ⚠️ unsettled and failed transactions will not be returned - -❌ `multi_pay_invoice` - -❌ `multi_pay_keysend (TBC)` +❌ `multi_pay_keysend` ## Node Distributions Run NWC on your own node! - [https://github.com/getAlby/umbrel-community-app-store](Umbrel) -- [https://github.com/horologger/nostr-wallet-connect-startos](Start9) (WIP) +- [https://github.com/horologger/nostr-wallet-connect-startos](Start9) diff --git a/alby.go b/alby.go deleted file mode 100644 index 7481b5e2..00000000 --- a/alby.go +++ /dev/null @@ -1,742 +0,0 @@ -package main - -import ( - "bytes" - "context" - "encoding/json" - "errors" - "fmt" - "net/http" - "net/url" - "strconv" - - "github.com/labstack/echo-contrib/session" - "github.com/labstack/echo/v4" - "github.com/sirupsen/logrus" - "golang.org/x/oauth2" - "gorm.io/gorm" -) - -type AlbyOAuthService struct { - cfg *Config - oauthConf *oauth2.Config - db *gorm.DB - Logger *logrus.Logger -} - -func NewAlbyOauthService(svc *Service, e *echo.Echo) (result LNClient, err error) { - conf := &oauth2.Config{ - ClientID: svc.cfg.AlbyClientId, - ClientSecret: svc.cfg.AlbyClientSecret, - //Todo: do we really need all these permissions? - Scopes: []string{"account:read", "payments:send", "invoices:read", "transactions:read", "invoices:create", "balance:read"}, - Endpoint: oauth2.Endpoint{ - TokenURL: svc.cfg.OAuthTokenUrl, - AuthURL: svc.cfg.OAuthAuthUrl, - AuthStyle: 2, // use HTTP Basic Authorization https://pkg.go.dev/golang.org/x/oauth2#AuthStyle - }, - RedirectURL: svc.cfg.OAuthRedirectUrl, - } - - albySvc := &AlbyOAuthService{ - cfg: svc.cfg, - oauthConf: conf, - db: svc.db, - Logger: svc.Logger, - } - - e.GET("/alby/auth", albySvc.AuthHandler) - e.GET("/alby/callback", albySvc.CallbackHandler) - - return albySvc, err -} - -func (svc *AlbyOAuthService) Shutdown() error { - return nil -} - -func (svc *AlbyOAuthService) FetchUserToken(ctx context.Context, app App) (token *oauth2.Token, err error) { - user := app.User - tok, err := svc.oauthConf.TokenSource(ctx, &oauth2.Token{ - AccessToken: user.AccessToken, - RefreshToken: user.RefreshToken, - Expiry: user.Expiry, - }).Token() - if err != nil { - svc.Logger.WithFields(logrus.Fields{ - "senderPubkey": app.NostrPubkey, - "appId": app.ID, - "userId": app.User.ID, - }).Errorf("Token error: %v", err) - return nil, err - } - // we always update the user's token for future use - // the oauth library handles the token refreshing - user.AccessToken = tok.AccessToken - user.RefreshToken = tok.RefreshToken - user.Expiry = tok.Expiry // TODO; probably needs some calculation - err = svc.db.Save(&user).Error - if err != nil { - svc.Logger.WithError(err).Error("Error saving user") - return nil, err - } - return tok, nil -} - -func (svc *AlbyOAuthService) MakeInvoice(ctx context.Context, senderPubkey string, amount int64, description string, descriptionHash string, expiry int64) (transaction *Nip47Transaction, err error) { - // TODO: move to a shared function - app := App{} - err = svc.db.Preload("User").First(&app, &App{ - NostrPubkey: senderPubkey, - }).Error - if err != nil { - svc.Logger.WithFields(logrus.Fields{ - "senderPubkey": senderPubkey, - "amount": amount, - "description": description, - "descriptionHash": descriptionHash, - "expiry": expiry, - }).Errorf("App not found: %v", err) - return nil, err - } - - // amount provided in msat, but Alby API currently only supports sats. Will get truncated to a whole sat value - var amountSat int64 = amount / 1000 - // make sure amount is not converted to 0 - if amount > 0 && amountSat == 0 { - svc.Logger.WithFields(logrus.Fields{ - "senderPubkey": senderPubkey, - "amount": amount, - "description": description, - "descriptionHash": descriptionHash, - "expiry": expiry, - }).Errorf("amount must be 1000 msat or greater") - return nil, errors.New("amount must be 1000 msat or greater") - } - - svc.Logger.WithFields(logrus.Fields{ - "senderPubkey": senderPubkey, - "amount": amount, - "description": description, - "descriptionHash": descriptionHash, - "expiry": expiry, - "appId": app.ID, - "userId": app.User.ID, - }).Info("Processing make invoice request") - tok, err := svc.FetchUserToken(ctx, app) - if err != nil { - return nil, err - } - client := svc.oauthConf.Client(ctx, tok) - - body := bytes.NewBuffer([]byte{}) - payload := &MakeInvoiceRequest{ - Amount: amountSat, - Description: description, - DescriptionHash: descriptionHash, - // TODO: support expiry - } - err = json.NewEncoder(body).Encode(payload) - - // TODO: move to a shared function - req, err := http.NewRequest("POST", fmt.Sprintf("%s/invoices", svc.cfg.AlbyAPIURL), body) - if err != nil { - svc.Logger.WithError(err).Error("Error creating request /invoices") - return nil, err - } - - // TODO: move to creation of HTTP client - req.Header.Set("User-Agent", "NWC") - req.Header.Set("Content-Type", "application/json") - - resp, err := client.Do(req) - if err != nil { - svc.Logger.WithFields(logrus.Fields{ - "senderPubkey": senderPubkey, - "amount": amount, - "description": description, - "descriptionHash": descriptionHash, - "expiry": expiry, - "appId": app.ID, - "userId": app.User.ID, - }).Errorf("Failed to make invoice: %v", err) - return nil, err - } - - if resp.StatusCode < 300 { - responsePayload := &AlbyInvoice{} - err = json.NewDecoder(resp.Body).Decode(responsePayload) - if err != nil { - return nil, err - } - svc.Logger.WithFields(logrus.Fields{ - "senderPubkey": senderPubkey, - "amount": amount, - "description": description, - "descriptionHash": descriptionHash, - "expiry": expiry, - "appId": app.ID, - "userId": app.User.ID, - "paymentRequest": responsePayload.PaymentRequest, - "paymentHash": responsePayload.PaymentHash, - }).Info("Make invoice successful") - - transaction := albyInvoiceToTransaction(responsePayload) - return transaction, nil - } - - errorPayload := &ErrorResponse{} - err = json.NewDecoder(resp.Body).Decode(errorPayload) - svc.Logger.WithFields(logrus.Fields{ - "senderPubkey": senderPubkey, - "amount": amount, - "description": description, - "descriptionHash": descriptionHash, - "expiry": expiry, - "appId": app.ID, - "userId": app.User.ID, - "APIHttpStatus": resp.StatusCode, - }).Errorf("Make invoice failed %s", string(errorPayload.Message)) - return nil, errors.New(errorPayload.Message) -} - -func (svc *AlbyOAuthService) LookupInvoice(ctx context.Context, senderPubkey string, paymentHash string) (transaction *Nip47Transaction, err error) { - // TODO: move to a shared function - app := App{} - err = svc.db.Preload("User").First(&app, &App{ - NostrPubkey: senderPubkey, - }).Error - if err != nil { - svc.Logger.WithFields(logrus.Fields{ - "senderPubkey": senderPubkey, - "paymentHash": paymentHash, - }).Errorf("App not found: %v", err) - return nil, err - } - - svc.Logger.WithFields(logrus.Fields{ - "senderPubkey": senderPubkey, - "paymentHash": paymentHash, - "appId": app.ID, - "userId": app.User.ID, - }).Info("Processing lookup invoice request") - tok, err := svc.FetchUserToken(ctx, app) - if err != nil { - return nil, err - } - client := svc.oauthConf.Client(ctx, tok) - - body := bytes.NewBuffer([]byte{}) - - // TODO: move to a shared function - req, err := http.NewRequest("GET", fmt.Sprintf("%s/invoices/%s", svc.cfg.AlbyAPIURL, paymentHash), body) - if err != nil { - svc.Logger.WithError(err).Errorf("Error creating request /invoices/%s", paymentHash) - return nil, err - } - - req.Header.Set("User-Agent", "NWC") - req.Header.Set("Content-Type", "application/json") - - resp, err := client.Do(req) - if err != nil { - svc.Logger.WithFields(logrus.Fields{ - "senderPubkey": senderPubkey, - "paymentHash": paymentHash, - "appId": app.ID, - "userId": app.User.ID, - }).Errorf("Failed to lookup invoice: %v", err) - return nil, err - } - - if resp.StatusCode < 300 { - responsePayload := &AlbyInvoice{} - err = json.NewDecoder(resp.Body).Decode(responsePayload) - if err != nil { - return nil, err - } - svc.Logger.WithFields(logrus.Fields{ - "senderPubkey": senderPubkey, - "paymentHash": paymentHash, - "appId": app.ID, - "userId": app.User.ID, - "paymentRequest": responsePayload.PaymentRequest, - "settled": responsePayload.Settled, - }).Info("Lookup invoice successful") - - transaction = albyInvoiceToTransaction(responsePayload) - return transaction, nil - } - - errorPayload := &ErrorResponse{} - err = json.NewDecoder(resp.Body).Decode(errorPayload) - svc.Logger.WithFields(logrus.Fields{ - "senderPubkey": senderPubkey, - "paymentHash": paymentHash, - "appId": app.ID, - "userId": app.User.ID, - "APIHttpStatus": resp.StatusCode, - }).Errorf("Lookup invoice failed %s", string(errorPayload.Message)) - return nil, errors.New(errorPayload.Message) -} - -func (svc *AlbyOAuthService) GetInfo(ctx context.Context, senderPubkey string) (info *NodeInfo, err error) { - app := App{} - err = svc.db.Preload("User").First(&app, &App{ - NostrPubkey: senderPubkey, - }).Error - if err != nil { - svc.Logger.WithFields(logrus.Fields{ - "senderPubkey": senderPubkey, - }).Errorf("App not found: %v", err) - return nil, err - } - - svc.Logger.WithFields(logrus.Fields{ - "senderPubkey": senderPubkey, - "appId": app.ID, - "userId": app.User.ID, - }).Info("Info fetch successful") - return &NodeInfo{ - Alias: "getalby.com", - Color: "", - Pubkey: "", - Network: "mainnet", - BlockHeight: 0, - BlockHash: "", - }, err -} - -func (svc *AlbyOAuthService) GetBalance(ctx context.Context, senderPubkey string) (balance int64, err error) { - app := App{} - err = svc.db.Preload("User").First(&app, &App{ - NostrPubkey: senderPubkey, - }).Error - if err != nil { - svc.Logger.WithFields(logrus.Fields{ - "senderPubkey": senderPubkey, - }).Errorf("App not found: %v", err) - return 0, err - } - tok, err := svc.FetchUserToken(ctx, app) - if err != nil { - return 0, err - } - client := svc.oauthConf.Client(ctx, tok) - - req, err := http.NewRequest("GET", fmt.Sprintf("%s/balance", svc.cfg.AlbyAPIURL), nil) - if err != nil { - svc.Logger.WithError(err).Error("Error creating request /balance") - return 0, err - } - - req.Header.Set("User-Agent", "NWC") - - resp, err := client.Do(req) - if err != nil { - svc.Logger.WithFields(logrus.Fields{ - "senderPubkey": senderPubkey, - "appId": app.ID, - "userId": app.User.ID, - }).Errorf("Failed to fetch balance: %v", err) - return 0, err - } - - if resp.StatusCode < 300 { - responsePayload := &BalanceResponse{} - err = json.NewDecoder(resp.Body).Decode(responsePayload) - if err != nil { - return 0, err - } - svc.Logger.WithFields(logrus.Fields{ - "senderPubkey": senderPubkey, - "appId": app.ID, - "userId": app.User.ID, - }).Info("Balance fetch successful") - return int64(responsePayload.Balance), nil - } - - errorPayload := &ErrorResponse{} - err = json.NewDecoder(resp.Body).Decode(errorPayload) - svc.Logger.WithFields(logrus.Fields{ - "senderPubkey": senderPubkey, - "appId": app.ID, - "userId": app.User.ID, - "APIHttpStatus": resp.StatusCode, - }).Errorf("Balance fetch failed %s", string(errorPayload.Message)) - return 0, errors.New(errorPayload.Message) -} - -func (svc *AlbyOAuthService) ListTransactions(ctx context.Context, senderPubkey string, from, until, limit, offset uint64, unpaid bool, invoiceType string) (transactions []Nip47Transaction, err error) { - app := App{} - err = svc.db.Preload("User").First(&app, &App{ - NostrPubkey: senderPubkey, - }).Error - if err != nil { - svc.Logger.WithFields(logrus.Fields{ - "senderPubkey": senderPubkey, - }).Errorf("App not found: %v", err) - return nil, err - } - tok, err := svc.FetchUserToken(ctx, app) - if err != nil { - return nil, err - } - client := svc.oauthConf.Client(ctx, tok) - - urlParams := url.Values{} - //urlParams.Add("page", "1") - - // TODO: clarify gt/lt vs from to in NWC spec - if from != 0 { - urlParams.Add("q[created_at_gt]", strconv.FormatUint(from, 10)) - } - if until != 0 { - urlParams.Add("q[created_at_lt]", strconv.FormatUint(until, 10)) - } - if limit != 0 { - urlParams.Add("items", strconv.FormatUint(limit, 10)) - } - // TODO: Add Offset and Unpaid - - endpoint := "/invoices" - - switch invoiceType { - case "incoming": - endpoint += "/incoming" - case "outgoing": - endpoint += "/outgoing" - } - - requestUrl := fmt.Sprintf("%s%s?%s", svc.cfg.AlbyAPIURL, endpoint, urlParams.Encode()) - - req, err := http.NewRequest("GET", requestUrl, nil) - if err != nil { - svc.Logger.WithError(err).Error("Error creating request /invoices") - return nil, err - } - - req.Header.Set("User-Agent", "NWC") - - resp, err := client.Do(req) - if err != nil { - svc.Logger.WithFields(logrus.Fields{ - "senderPubkey": senderPubkey, - "appId": app.ID, - "userId": app.User.ID, - "requestUrl": requestUrl, - }).Errorf("Failed to fetch invoices: %v", err) - return nil, err - } - - var invoices []AlbyInvoice - - if resp.StatusCode < 300 { - err = json.NewDecoder(resp.Body).Decode(&invoices) - if err != nil { - svc.Logger.WithFields(logrus.Fields{ - "senderPubkey": senderPubkey, - "appId": app.ID, - "userId": app.User.ID, - "requestUrl": requestUrl, - }).Errorf("Failed to decode invoices: %v", err) - return nil, err - } - - transactions = []Nip47Transaction{} - for _, invoice := range invoices { - transaction := albyInvoiceToTransaction(&invoice) - - transactions = append(transactions, *transaction) - } - - svc.Logger.WithFields(logrus.Fields{ - "senderPubkey": senderPubkey, - "appId": app.ID, - "userId": app.User.ID, - "requestUrl": requestUrl, - }).Info("List transactions successful") - return transactions, nil - } - - errorPayload := &ErrorResponse{} - err = json.NewDecoder(resp.Body).Decode(errorPayload) - svc.Logger.WithFields(logrus.Fields{ - "senderPubkey": senderPubkey, - "appId": app.ID, - "userId": app.User.ID, - "APIHttpStatus": resp.StatusCode, - "requestUrl": requestUrl, - }).Errorf("List transactions failed %s", string(errorPayload.Message)) - return nil, errors.New(errorPayload.Message) -} - -func (svc *AlbyOAuthService) SendPaymentSync(ctx context.Context, senderPubkey, payReq string) (preimage string, err error) { - app := App{} - err = svc.db.Preload("User").First(&app, &App{ - NostrPubkey: senderPubkey, - }).Error - if err != nil { - svc.Logger.WithFields(logrus.Fields{ - "senderPubkey": senderPubkey, - "bolt11": payReq, - }).Errorf("App not found: %v", err) - return "", err - } - svc.Logger.WithFields(logrus.Fields{ - "senderPubkey": senderPubkey, - "bolt11": payReq, - "appId": app.ID, - "userId": app.User.ID, - }).Info("Processing payment request") - tok, err := svc.FetchUserToken(ctx, app) - if err != nil { - return "", err - } - client := svc.oauthConf.Client(ctx, tok) - - body := bytes.NewBuffer([]byte{}) - payload := &PayRequest{ - Invoice: payReq, - } - err = json.NewEncoder(body).Encode(payload) - - req, err := http.NewRequest("POST", fmt.Sprintf("%s/payments/bolt11", svc.cfg.AlbyAPIURL), body) - if err != nil { - svc.Logger.WithError(err).Error("Error creating request /payments/bolt11") - return "", err - } - - req.Header.Set("User-Agent", "NWC") - req.Header.Set("Content-Type", "application/json") - - resp, err := client.Do(req) - if err != nil { - svc.Logger.WithFields(logrus.Fields{ - "senderPubkey": senderPubkey, - "bolt11": payReq, - "appId": app.ID, - "userId": app.User.ID, - }).Errorf("Failed to pay invoice: %v", err) - return "", err - } - - if resp.StatusCode < 300 { - responsePayload := &PayResponse{} - err = json.NewDecoder(resp.Body).Decode(responsePayload) - if err != nil { - return "", err - } - svc.Logger.WithFields(logrus.Fields{ - "senderPubkey": senderPubkey, - "bolt11": payReq, - "appId": app.ID, - "userId": app.User.ID, - "paymentHash": responsePayload.PaymentHash, - }).Info("Payment successful") - return responsePayload.Preimage, nil - } - - errorPayload := &ErrorResponse{} - err = json.NewDecoder(resp.Body).Decode(errorPayload) - svc.Logger.WithFields(logrus.Fields{ - "senderPubkey": senderPubkey, - "bolt11": payReq, - "appId": app.ID, - "userId": app.User.ID, - "APIHttpStatus": resp.StatusCode, - }).Errorf("Payment failed %s", string(errorPayload.Message)) - return "", errors.New(errorPayload.Message) -} - -func (svc *AlbyOAuthService) SendKeysend(ctx context.Context, senderPubkey string, amount int64, destination, preimage string, custom_records []TLVRecord) (preImage string, err error) { - app := App{} - err = svc.db.Preload("User").First(&app, &App{ - NostrPubkey: senderPubkey, - }).Error - if err != nil { - svc.Logger.WithFields(logrus.Fields{ - "senderPubkey": senderPubkey, - "payeePubkey": destination, - }).Errorf("App not found: %v", err) - return "", err - } - svc.Logger.WithFields(logrus.Fields{ - "senderPubkey": senderPubkey, - "payeePubkey": destination, - "appId": app.ID, - "userId": app.User.ID, - }).Info("Processing keysend request") - tok, err := svc.FetchUserToken(ctx, app) - if err != nil { - return "", err - } - client := svc.oauthConf.Client(ctx, tok) - - customRecordsMap := make(map[string]string) - for _, record := range custom_records { - customRecordsMap[strconv.FormatUint(record.Type, 10)] = record.Value - } - - body := bytes.NewBuffer([]byte{}) - payload := &KeysendRequest{ - Amount: amount, - Destination: destination, - CustomRecords: customRecordsMap, - } - err = json.NewEncoder(body).Encode(payload) - - // here we don't use the preimage from params - req, err := http.NewRequest("POST", fmt.Sprintf("%s/payments/keysend", svc.cfg.AlbyAPIURL), body) - if err != nil { - svc.Logger.WithError(err).Error("Error creating request /payments/keysend") - return "", err - } - - req.Header.Set("User-Agent", "NWC") - req.Header.Set("Content-Type", "application/json") - - resp, err := client.Do(req) - if err != nil { - svc.Logger.WithFields(logrus.Fields{ - "senderPubkey": senderPubkey, - "payeePubkey": destination, - "appId": app.ID, - "userId": app.User.ID, - }).Errorf("Failed to pay keysend: %v", err) - return "", err - } - - if resp.StatusCode < 300 { - responsePayload := &PayResponse{} - err = json.NewDecoder(resp.Body).Decode(responsePayload) - if err != nil { - return "", err - } - svc.Logger.WithFields(logrus.Fields{ - "senderPubkey": senderPubkey, - "payeePubkey": destination, - "appId": app.ID, - "userId": app.User.ID, - "preimage": responsePayload.Preimage, - "paymentHash": responsePayload.PaymentHash, - }).Info("Keysend payment successful") - return responsePayload.Preimage, nil - } - - errorPayload := &ErrorResponse{} - err = json.NewDecoder(resp.Body).Decode(errorPayload) - svc.Logger.WithFields(logrus.Fields{ - "senderPubkey": senderPubkey, - "payeePubkey": destination, - "appId": app.ID, - "userId": app.User.ID, - "APIHttpStatus": resp.StatusCode, - }).Errorf("Payment failed %s", string(errorPayload.Message)) - return "", errors.New(errorPayload.Message) -} - -func (svc *AlbyOAuthService) AuthHandler(c echo.Context) error { - appName := c.QueryParam("c") // c - for client - // clear current session - sess, _ := session.Get(CookieName, c) - if sess.Values["user_id"] != nil { - delete(sess.Values, "user_id") - sess.Options.MaxAge = 0 - sess.Options.SameSite = http.SameSiteLaxMode - if svc.cfg.CookieDomain != "" { - sess.Options.Domain = svc.cfg.CookieDomain - } - sess.Save(c.Request(), c.Response()) - } - - url := svc.oauthConf.AuthCodeURL(appName) // pass on the appName as state - return c.Redirect(302, url) -} - -func (svc *AlbyOAuthService) CallbackHandler(c echo.Context) error { - code := c.QueryParam("code") - tok, err := svc.oauthConf.Exchange(c.Request().Context(), code) - if err != nil { - svc.Logger.WithError(err).Error("Failed to exchange token") - return err - } - client := svc.oauthConf.Client(c.Request().Context(), tok) - - req, err := http.NewRequest("GET", fmt.Sprintf("%s/user/me", svc.cfg.AlbyAPIURL), nil) - if err != nil { - svc.Logger.WithError(err).Error("Error creating request /me") - return err - } - - req.Header.Set("User-Agent", "NWC") - - res, err := client.Do(req) - if err != nil { - svc.Logger.WithError(err).Error("Failed to fetch /me") - return err - } - me := AlbyMe{} - err = json.NewDecoder(res.Body).Decode(&me) - if err != nil { - svc.Logger.WithError(err).Error("Failed to decode API response") - return err - } - - user := User{} - svc.db.FirstOrInit(&user, User{AlbyIdentifier: me.Identifier}) - user.AccessToken = tok.AccessToken - user.RefreshToken = tok.RefreshToken - user.Expiry = tok.Expiry // TODO; probably needs some calculation - user.Email = me.Email - user.LightningAddress = me.LightningAddress - svc.db.Save(&user) - - sess, _ := session.Get(CookieName, c) - sess.Options.MaxAge = 0 - sess.Options.SameSite = http.SameSiteLaxMode - if svc.cfg.CookieDomain != "" { - sess.Options.Domain = svc.cfg.CookieDomain - } - sess.Values["user_id"] = user.ID - sess.Save(c.Request(), c.Response()) - return c.Redirect(302, "/") -} - -func albyInvoiceToTransaction(invoice *AlbyInvoice) *Nip47Transaction { - description := invoice.Comment - if description == "" { - description = invoice.Memo - } - var preimage string - if invoice.SettledAt != nil { - preimage = invoice.Preimage - } - - var expiresAt *int64 - if invoice.ExpiresAt != nil { - expiresAtUnix := invoice.ExpiresAt.Unix() - expiresAt = &expiresAtUnix - } - - var settledAt *int64 - if invoice.SettledAt != nil { - settledAtUnix := invoice.SettledAt.Unix() - settledAt = &settledAtUnix - } - - return &Nip47Transaction{ - Type: invoice.Type, - Invoice: invoice.PaymentRequest, - Description: description, - DescriptionHash: invoice.DescriptionHash, - Preimage: preimage, - PaymentHash: invoice.PaymentHash, - Amount: invoice.Amount * 1000, - FeesPaid: 0, // TODO: support fees - CreatedAt: invoice.CreatedAt.Unix(), - ExpiresAt: expiresAt, - SettledAt: settledAt, - Metadata: invoice.Metadata, - } -} diff --git a/api.go b/api.go index 382ad767..9ee406cc 100644 --- a/api.go +++ b/api.go @@ -17,7 +17,7 @@ import ( // TODO: these methods should be moved to a separate object, not in Service // TODO: remove user parameter in single-user fork -func (svc *Service) CreateApp(user *User, createAppRequest *api.CreateAppRequest) (*api.CreateAppResponse, error) { +func (svc *Service) CreateApp(createAppRequest *api.CreateAppRequest) (*api.CreateAppResponse, error) { name := createAppRequest.Name var pairingPublicKey string var pairingSecretKey string diff --git a/config.go b/config.go index f033b64c..fe3aa56a 100644 --- a/config.go +++ b/config.go @@ -3,7 +3,6 @@ package main import "github.com/getAlby/nostr-wallet-connect/models/db" const ( - AlbyBackendType = "ALBY" LNDBackendType = "LND" BreezBackendType = "BREEZ" CookieName = "alby_nwc_session" @@ -26,12 +25,6 @@ type Config struct { BreezWorkdir string `envconfig:"BREEZ_WORK_DIR" default:".breez"` BasicAuthUser string `envconfig:"BASIC_AUTH_USER"` BasicAuthPassword string `envconfig:"BASIC_AUTH_PASSWORD"` - AlbyAPIURL string `envconfig:"ALBY_API_URL" default:"https://api.getalby.com"` - AlbyClientId string `envconfig:"ALBY_CLIENT_ID"` - AlbyClientSecret string `envconfig:"ALBY_CLIENT_SECRET"` - OAuthRedirectUrl string `envconfig:"OAUTH_REDIRECT_URL"` - OAuthAuthUrl string `envconfig:"OAUTH_AUTH_URL" default:"https://getalby.com/oauth"` - OAuthTokenUrl string `envconfig:"OAUTH_TOKEN_URL" default:"https://api.getalby.com/oauth/token"` Port string `envconfig:"PORT" default:"8080"` DatabaseUri string `envconfig:"DATABASE_URI" default:"nostr-wallet-connect.db"` DatabaseMaxConns int `envconfig:"DATABASE_MAX_CONNS" default:"10"` diff --git a/echo_handlers.go b/echo_handlers.go index 6088bdb0..894213d2 100644 --- a/echo_handlers.go +++ b/echo_handlers.go @@ -19,16 +19,11 @@ import ( func (svc *Service) ValidateUserMiddleware(next echo.HandlerFunc) echo.HandlerFunc { return func(c echo.Context) error { - user, err := svc.GetUser(c) - if err != nil { - return c.JSON(http.StatusBadRequest, ErrorResponse{ - Message: fmt.Sprintf("Bad arguments %s", err.Error()), - }) - } - if user == nil { - return c.NoContent(http.StatusUnauthorized) - } - c.Set("user", user) + // TODO: check if login is required and check if user is logged in + //sess, _ := session.Get(CookieName, c) + // if user == nil { + // return c.NoContent(http.StatusUnauthorized) + // } return next(c) } } @@ -44,7 +39,6 @@ func (svc *Service) RegisterSharedRoutes(e *echo.Echo) { e.Use(session.Middleware(sessions.NewCookieStore([]byte(svc.cfg.CookieSecret)))) authMiddleware := svc.ValidateUserMiddleware - e.GET("/api/user/me", svc.UserMeHandler, authMiddleware) e.GET("/api/apps", svc.AppsListHandler, authMiddleware) e.GET("/api/apps/:pubkey", svc.AppsShowHandler, authMiddleware) e.DELETE("/api/apps/:pubkey", svc.AppsDeleteHandler, authMiddleware) @@ -92,14 +86,6 @@ func (svc *Service) LogoutHandler(c echo.Context) error { return c.NoContent(http.StatusNoContent) } -func (svc *Service) UserMeHandler(c echo.Context) error { - user, _ := c.Get("user").(*User) - responseBody := api.User{ - Email: user.Email, - } - return c.JSON(http.StatusOK, responseBody) -} - func (svc *Service) AppsListHandler(c echo.Context) error { user, _ := c.Get("user").(*User) userApps := user.Apps diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index c58f04d4..4cc11f40 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -5,7 +5,6 @@ import Footer from "src/components/Footer"; import Toaster from "src/components/Toast/Toaster"; import About from "src/screens/About"; -import Login from "src/screens/Login"; import AppsList from "src/screens/apps/AppsList"; import ShowApp from "src/screens/apps/ShowApp"; import NewApp from "src/screens/apps/NewApp"; @@ -44,7 +43,6 @@ function App() { } /> } /> - } /> } />