Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: move mnemonic decryption to backend #417

Merged
merged 6 commits into from
Aug 8, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 9 additions & 5 deletions api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -609,11 +609,15 @@
return &info, nil
}

func (api *api) GetEncryptedMnemonic() *EncryptedMnemonicResponse {
resp := EncryptedMnemonicResponse{}
mnemonic, _ := api.cfg.Get("Mnemonic", "")
resp.Mnemonic = mnemonic
return &resp
func (api *api) GetMnemonic(unlockPassword string) (*MnemonicResponse, error) {
resp := MnemonicResponse{}
reneaaron marked this conversation as resolved.
Show resolved Hide resolved
mnemonic, err := api.cfg.Get("Mnemonic", unlockPassword)

Check failure on line 614 in api/api.go

View workflow job for this annotation

GitHub Actions / build

mnemonic declared and not used

Check failure on line 614 in api/api.go

View workflow job for this annotation

GitHub Actions / build (aarch64, linux-aarch64, aarch64-unknown-linux-gnu)

mnemonic declared and not used
if err != nil {
return &resp, fmt.Errorf("failed to fetch encryption key: %w", err)
reneaaron marked this conversation as resolved.
Show resolved Hide resolved
}

resp.Mnemonic = encryptedMnemonic

Check failure on line 619 in api/api.go

View workflow job for this annotation

GitHub Actions / build

undefined: encryptedMnemonic

Check failure on line 619 in api/api.go

View workflow job for this annotation

GitHub Actions / build (aarch64, linux-aarch64, aarch64-unknown-linux-gnu)

undefined: encryptedMnemonic
return &resp, err
}

func (api *api) SetNextBackupReminder(backupReminderRequest *BackupReminderRequest) error {
Expand Down
8 changes: 6 additions & 2 deletions api/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ type API interface {
LookupInvoice(ctx context.Context, paymentHash string) (*LookupInvoiceResponse, error)
RequestMempoolApi(endpoint string) (interface{}, error)
GetInfo(ctx context.Context) (*InfoResponse, error)
GetEncryptedMnemonic() *EncryptedMnemonicResponse
GetMnemonic(unlockPassword string) (*MnemonicResponse, error)
SetNextBackupReminder(backupReminderRequest *BackupReminderRequest) error
Start(startRequest *StartRequest) error
Setup(ctx context.Context, setupRequest *SetupRequest) error
Expand Down Expand Up @@ -159,7 +159,11 @@ type InfoResponse struct {
Network string `json:"network"`
}

type EncryptedMnemonicResponse struct {
type MnemonicRequest struct {
UnlockPassword string `json:"unlockPassword"`
}

type MnemonicResponse struct {
Mnemonic string `json:"mnemonic"`
}

Expand Down
11 changes: 0 additions & 11 deletions frontend/src/hooks/useEncryptedMnemonic.ts

This file was deleted.

26 changes: 16 additions & 10 deletions frontend/src/screens/BackupMnemonic.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import React, { useState } from "react";
import { useNavigate } from "react-router-dom";

import Container from "src/components/Container";
import Loading from "src/components/Loading";
import MnemonicInputs from "src/components/MnemonicInputs";
import SettingsHeader from "src/components/SettingsHeader";
import { Button } from "src/components/ui/button";
Expand All @@ -13,9 +12,8 @@ import { Label } from "src/components/ui/label";
import { LoadingButton } from "src/components/ui/loading-button";
import { useToast } from "src/components/ui/use-toast";
import { useCSRF } from "src/hooks/useCSRF";
import { useEncryptedMnemonic } from "src/hooks/useEncryptedMnemonic";
import { useInfo } from "src/hooks/useInfo";
import { aesGcmDecrypt } from "src/utils/aesgcm";
import { MnemonicResponse } from "src/types";
import { handleRequestError } from "src/utils/handleRequestError";
import { request } from "src/utils/request";

Expand All @@ -24,23 +22,31 @@ export function BackupMnemonic() {
const { data: csrf } = useCSRF();
const { toast } = useToast();
const { mutate: refetchInfo } = useInfo();
const { data: mnemonic } = useEncryptedMnemonic();

const [unlockPassword, setUnlockPassword] = React.useState("");
const [decryptedMnemonic, setDecryptedMnemonic] = React.useState("");
const [loading, setLoading] = React.useState(false);
const [backedUp, setIsBackedUp] = useState<boolean>(false);

if (!mnemonic) {
return <Loading />;
}

const onSubmitPassword = async (e: React.FormEvent) => {
e.preventDefault();
try {
setLoading(true);
const dec = await aesGcmDecrypt(mnemonic.mnemonic, unlockPassword);
setDecryptedMnemonic(dec);
if (!csrf) {
throw new Error("No CSRF token");
}
const result = await request<MnemonicResponse>("/api/mnemonic", {
method: "POST",
headers: {
"X-CSRF-Token": csrf,
"Content-Type": "application/json",
},
body: JSON.stringify({
unlockPassword,
}),
});

setDecryptedMnemonic(result?.mnemonic ?? "");
} catch (error) {
toast({
title: "Incorrect password",
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ export interface InfoResponse {

export type Network = "bitcoin" | "testnet" | "signet";

export interface EncryptedMnemonicResponse {
export interface MnemonicResponse {
mnemonic: string;
}

Expand Down
69 changes: 0 additions & 69 deletions frontend/src/utils/aesgcm.ts

This file was deleted.

26 changes: 23 additions & 3 deletions http/http_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ func (httpSvc *HttpService) RegisterSharedRoutes(e *echo.Echo) {
e.PATCH("/api/apps/:pubkey", httpSvc.appsUpdateHandler, authMiddleware)
e.DELETE("/api/apps/:pubkey", httpSvc.appsDeleteHandler, authMiddleware)
e.POST("/api/apps", httpSvc.appsCreateHandler, authMiddleware)
e.GET("/api/encrypted-mnemonic", httpSvc.encryptedMnemonicHandler, authMiddleware)
e.POST("/api/mnemonic", httpSvc.mnemonicHandler, authMiddleware)
e.PATCH("/api/backup-reminder", httpSvc.backupReminderHandler, authMiddleware)

e.GET("/api/csrf", httpSvc.csrfHandler)
Expand Down Expand Up @@ -169,8 +169,28 @@ func (httpSvc *HttpService) infoHandler(c echo.Context) error {
return c.JSON(http.StatusOK, responseBody)
}

func (httpSvc *HttpService) encryptedMnemonicHandler(c echo.Context) error {
responseBody := httpSvc.api.GetEncryptedMnemonic()
func (httpSvc *HttpService) mnemonicHandler(c echo.Context) error {
var mnemonicRequest api.MnemonicRequest
if err := c.Bind(&mnemonicRequest); err != nil {
return c.JSON(http.StatusBadRequest, ErrorResponse{
Message: fmt.Sprintf("Bad request: %s", err.Error()),
})
}

if !httpSvc.cfg.CheckUnlockPassword(mnemonicRequest.UnlockPassword) {
reneaaron marked this conversation as resolved.
Show resolved Hide resolved
reneaaron marked this conversation as resolved.
Show resolved Hide resolved
return c.JSON(http.StatusUnauthorized, ErrorResponse{
Message: "Invalid password",
})
}

responseBody, err := httpSvc.api.GetMnemonic(mnemonicRequest.UnlockPassword)

if err != nil {
return c.JSON(http.StatusInternalServerError, ErrorResponse{
Message: err.Error(),
})
}

return c.JSON(http.StatusOK, responseBody)
}

Expand Down
28 changes: 25 additions & 3 deletions wails/wails_handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -543,9 +543,31 @@ func (app *WailsApp) WailsRequestRouter(route string, method string, body string
}
res := WailsRequestRouterResponse{Error: ""}
return res
case "/api/encrypted-mnemonic":
infoResponse := app.api.GetEncryptedMnemonic()
res := WailsRequestRouterResponse{Body: *infoResponse, Error: ""}
case "/api/mnemonic":
mnemonicRequest := &api.MnemonicRequest{}
err := json.Unmarshal([]byte(body), mnemonicRequest)
if err != nil {
logger.Logger.WithFields(logrus.Fields{
"route": route,
"method": method,
// Skip logging the body for this request as we don't want the
// unlock password to end up in any logs
// "body": body,
}).WithError(err).Error("Failed to parse mnemonic request")
return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
}
mnemonicResponse, err := app.api.GetMnemonic(mnemonicRequest.UnlockPassword)
if err != nil {
logger.Logger.WithFields(logrus.Fields{
"route": route,
"method": method,
// Skip logging the body for this request as we don't want the
// unlock password to end up in any logs
// "body": body,
}).WithError(err).Error("Failed to get mnemonic")
return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
}
res := WailsRequestRouterResponse{Body: *mnemonicResponse, Error: ""}
return res
case "/api/backup-reminder":
backupReminderRequest := &api.BackupReminderRequest{}
Expand Down
Loading