Skip to content

Commit

Permalink
feat: unlock screen (WIP)
Browse files Browse the repository at this point in the history
  • Loading branch information
rolznz committed Jan 30, 2024
1 parent 33ce4db commit b6071b6
Show file tree
Hide file tree
Showing 9 changed files with 154 additions and 13 deletions.
7 changes: 4 additions & 3 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@ import (
)

const (
LNDBackendType = "LND"
BreezBackendType = "BREEZ"
CookieName = "alby_nwc_session"
LNDBackendType = "LND"
BreezBackendType = "BREEZ"
SessionCookieName = "session"
SessionCookieAuthKey = "authenticated"
)

type AppConfig struct {
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import Start from "src/screens/Start";
import { AppsRedirect } from "src/components/redirects/AppsRedirect";
import { StartRedirect } from "src/components/redirects/StartRedirect";
import { HomeRedirect } from "src/components/redirects/HomeRedirect";
import Unlock from "src/screens/Unlock";

function App() {
return (
Expand Down Expand Up @@ -47,6 +48,7 @@ function App() {
<Route path="created" element={<AppCreated />} />
<Route path="*" element={<NotFound />} />
</Route>
<Route path="unlock" element={<Unlock />} />
<Route path="about" element={<About />} />
</Route>
<Route path="/*" element={<NotFound />} />
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/redirects/AppsRedirect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export function AppsRedirect() {

// TODO: re-add login redirect: https://github.com/getAlby/nostr-wallet-connect/commit/59b041886098dda4ff38191e3dd704ec36360673
React.useEffect(() => {
if (!info || info.running) {
if (!info || (info.running && info.unlocked)) {
return;
}
navigate("/");
Expand Down
6 changes: 5 additions & 1 deletion frontend/src/components/redirects/HomeRedirect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,11 @@ export function HomeRedirect() {
}
let to: string | undefined;
if (info.setupCompleted && info.running) {
to = "/apps";
if (info.unlocked) {
to = "/apps";
} else {
to = "/unlock";
}
} else if (info.setupCompleted && !info.running) {
to = "/start";
} else {
Expand Down
3 changes: 3 additions & 0 deletions frontend/src/screens/Start.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ import { useCSRF } from "src/hooks/useCSRF";
import { request } from "src/utils/request";
import ConnectButton from "src/components/ConnectButton";
import { handleRequestError } from "src/utils/handleRequestError";
import { useInfo } from "src/hooks/useInfo";

export default function Start() {
const [unlockPassword, setUnlockPassword] = React.useState("");
const [loading, setLoading] = React.useState(false);
const navigate = useNavigate();
const { data: csrf } = useCSRF();
const { mutate: refetchInfo } = useInfo();

async function onSubmit(e: React.FormEvent) {
e.preventDefault();
Expand All @@ -29,6 +31,7 @@ export default function Start() {
}),
});
console.log({ res });
await refetchInfo();

navigate("/apps");
} catch (error) {
Expand Down
69 changes: 69 additions & 0 deletions frontend/src/screens/Unlock.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import React from "react";
import { useNavigate } from "react-router-dom";
import { useCSRF } from "src/hooks/useCSRF";
import { request } from "src/utils/request";
import ConnectButton from "src/components/ConnectButton";
import { handleRequestError } from "src/utils/handleRequestError";
import { useInfo } from "src/hooks/useInfo";

export default function Unlock() {
const [unlockPassword, setUnlockPassword] = React.useState("");
const [loading, setLoading] = React.useState(false);
const navigate = useNavigate();
const { data: csrf } = useCSRF();
const { mutate: refetchInfo } = useInfo();

async function onSubmit(e: React.FormEvent) {
e.preventDefault();
try {
setLoading(true);
if (!csrf) {
throw new Error("info not loaded");
}
const res = await request("/api/unlock", {
method: "POST",
headers: {
"X-CSRF-Token": csrf,
"Content-Type": "application/json",
},
body: JSON.stringify({
unlockPassword,
}),
});
console.log({ res });
await refetchInfo();
navigate("/apps");
} catch (error) {
handleRequestError("Failed to connect", error);
} finally {
setLoading(false);
}
}

return (
<>
<h1 className="text-lg">Unlock NWC</h1>
<p className="text-lg mb-10">
To continue, please enter your unlock password
</p>
<form onSubmit={onSubmit} className="mb-10">
<>
<label
htmlFor="greenlight-invite-code"
className="block font-medium text-gray-900 dark:text-white"
>
Unlock password
</label>
<input
name="unlock"
onChange={(e) => setUnlockPassword(e.target.value)}
value={unlockPassword}
type="password"
className="dark:bg-surface-00dp block w-full rounded-lg border border-gray-300 bg-gray-50 p-2.5 text-sm text-gray-900 focus:ring-2 focus:ring-purple-700 dark:border-gray-700 dark:text-white dark:placeholder-gray-400 dark:ring-offset-gray-800 dark:focus:ring-purple-600"
/>
<ConnectButton isConnecting={loading} />
</>
</form>
</>
);
}
1 change: 1 addition & 0 deletions frontend/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ export interface InfoResponse {
backendType: BackendType;
setupCompleted: boolean;
running: boolean;
unlocked: boolean;
}

export interface CreateAppResponse {
Expand Down
72 changes: 64 additions & 8 deletions http_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@ import (
)

type HttpService struct {
svc *Service
api *API
svc *Service
api *API
encryptionKey string
}

func NewHttpService(svc *Service) *HttpService {
Expand All @@ -29,11 +30,9 @@ func NewHttpService(svc *Service) *HttpService {

func (httpSvc *HttpService) validateUserMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
// 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)
// }
if !httpSvc.isUnlocked(c) {
return c.NoContent(http.StatusUnauthorized)
}
return next(c)
}
}
Expand All @@ -59,6 +58,8 @@ func (httpSvc *HttpService) RegisterSharedRoutes(e *echo.Echo) {
e.POST("/api/logout", httpSvc.logoutHandler)
e.POST("/api/setup", httpSvc.setupHandler)
e.POST("/api/start", httpSvc.startHandler)
// TODO: add rate limiter
e.POST("/api/unlock", httpSvc.unlockHandler)

frontend.RegisterHandlers(e)
}
Expand All @@ -75,6 +76,7 @@ func (httpSvc *HttpService) csrfHandler(c echo.Context) error {

func (httpSvc *HttpService) infoHandler(c echo.Context) error {
responseBody := httpSvc.api.GetInfo()
responseBody.Unlocked = httpSvc.isUnlocked(c)
return c.JSON(http.StatusOK, responseBody)
}

Expand All @@ -92,11 +94,65 @@ func (httpSvc *HttpService) startHandler(c echo.Context) error {
Message: fmt.Sprintf("Failed to start node: %s", err.Error()),
})
}

err = httpSvc.saveSessionCookie(c)

if err != nil {
return c.JSON(http.StatusInternalServerError, ErrorResponse{
Message: fmt.Sprintf("Failed to save session: %s", err.Error()),
})
}

httpSvc.encryptionKey = startRequest.UnlockPassword

return c.NoContent(http.StatusNoContent)
}

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

if unlockRequest.UnlockPassword != httpSvc.encryptionKey {
return c.NoContent(http.StatusUnauthorized)
}

err := httpSvc.saveSessionCookie(c)

if err != nil {
return c.JSON(http.StatusInternalServerError, ErrorResponse{
Message: fmt.Sprintf("Failed to save session: %s", err.Error()),
})
}

return c.NoContent(http.StatusNoContent)
}

func (httpSvc *HttpService) isUnlocked(c echo.Context) bool {
sess, _ := session.Get(SessionCookieName, c)
return sess.Values[SessionCookieAuthKey] == true
}

func (httpSvc *HttpService) saveSessionCookie(c echo.Context) error {
sess, _ := session.Get("session", c)
sess.Options = &sessions.Options{
Path: "/",
MaxAge: 86400 * 7,
HttpOnly: true,
}
sess.Values[SessionCookieAuthKey] = true
err := sess.Save(c.Request(), c.Response())
if err != nil {
httpSvc.svc.Logger.Errorf("Failed to save session: %v", err)
}
return err
}

func (httpSvc *HttpService) logoutHandler(c echo.Context) error {
sess, err := session.Get(CookieName, c)
sess, err := session.Get(SessionCookieName, c)
if err != nil {
return c.JSON(http.StatusInternalServerError, ErrorResponse{
Message: "Failed to get session",
Expand Down
5 changes: 5 additions & 0 deletions models/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ type StartRequest struct {
UnlockPassword string `json:"unlockPassword"`
}

type UnlockRequest struct {
UnlockPassword string `json:"unlockPassword"`
}

type SetupRequest struct {
LNBackendType string `json:"backendType"`
// Breez fields
Expand Down Expand Up @@ -67,4 +71,5 @@ type InfoResponse struct {
BackendType string `json:"backendType"`
SetupCompleted bool `json:"setupCompleted"`
Running bool `json:"running"`
Unlocked bool `json:"unlocked"`
}

0 comments on commit b6071b6

Please sign in to comment.