Skip to content

Commit

Permalink
feat: auth with alby account before node setup (#779)
Browse files Browse the repository at this point in the history
* feat: auth with alby account before node setup

* fix: redirect to auth page from start page if auth is needed

* fix: do not show auth page twice if user goes back after authenticating in setup

* fix: re-add ldk directory check to calculate if setup has completed
  • Loading branch information
rolznz authored Nov 7, 2024
1 parent 5fdc803 commit f590c6f
Show file tree
Hide file tree
Showing 8 changed files with 106 additions and 63 deletions.
6 changes: 2 additions & 4 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,19 +117,17 @@ func (cfg *config) init(env *AppConfig) error {
}

func (cfg *config) SetupCompleted() bool {
// TODO: remove AlbyUserIdentifier and hasLdkDir checks after 2025/01/01
// TODO: remove hasLdkDir check after 2025/01/01
// to give time for users to update to 1.6.0+
albyUserIdentifier, _ := cfg.Get("AlbyUserIdentifier", "")
nodeLastStartTime, _ := cfg.Get("NodeLastStartTime", "")
ldkDir, err := os.Stat(path.Join(cfg.GetEnv().Workdir, "ldk"))
hasLdkDir := err == nil && ldkDir != nil && ldkDir.IsDir()

logger.Logger.WithFields(logrus.Fields{
"has_ldk_dir": hasLdkDir,
"has_alby_user_identifier": albyUserIdentifier != "",
"has_node_last_start_time": nodeLastStartTime != "",
}).Debug("Checking if setup is completed")
return albyUserIdentifier != "" || nodeLastStartTime != "" || hasLdkDir
return nodeLastStartTime != "" || hasLdkDir
}

func (cfg *config) GetJWTSecret() string {
Expand Down
56 changes: 38 additions & 18 deletions frontend/src/components/redirects/HomeRedirect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,31 +13,51 @@ export function HomeRedirect() {
if (!info) {
return;
}

const setupReturnTo = window.localStorage.getItem(
localStorageKeys.setupReturnTo
);

let to: string | undefined;
if (info.setupCompleted && info.running) {
if (info.unlocked) {
if (info.albyAccountConnected || !info.albyUserIdentifier) {
const returnTo = window.localStorage.getItem(
localStorageKeys.returnTo
);
// setTimeout hack needed for React strict mode (in development)
// because the effect runs twice before the navigation occurs
setTimeout(() => {
window.localStorage.removeItem(localStorageKeys.returnTo);
}, 100);
to = returnTo || "/home";
if (!setupReturnTo) {
if (info.setupCompleted && info.running) {
if (info.unlocked) {
if (info.albyAccountConnected || !info.albyUserIdentifier) {
const returnTo = window.localStorage.getItem(
localStorageKeys.returnTo
);
// setTimeout hack needed for React strict mode (in development)
// because the effect runs twice before the navigation occurs
setTimeout(() => {
window.localStorage.removeItem(localStorageKeys.returnTo);
}, 100);
to = returnTo || "/home";
} else {
to = "/alby/auth";
}
} else {
to = "/alby/auth";
to = "/unlock";
}
} else if (info.setupCompleted && !info.running) {
to = "/start";
} else if (info.albyAccountConnected) {
// in case user goes back after authenticating in setup
// we don't want to show the intro twice
to = "/welcome";
} else {
to = "/unlock";
to = "/intro";
}
} else if (info.setupCompleted && !info.running) {
to = "/start";
} else {
to = "/intro";
// setTimeout hack needed for React strict mode (in development)
// because the effect runs twice before the navigation occurs
setTimeout(() => {
window.localStorage.removeItem(localStorageKeys.setupReturnTo);
}, 100);
to = setupReturnTo;
}
navigate(to);
navigate(to, {
replace: true,
});
}, [info, location, navigate]);

if (!info) {
Expand Down
8 changes: 6 additions & 2 deletions frontend/src/components/redirects/StartRedirect.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useInfo } from "src/hooks/useInfo";
import { useLocation, useNavigate } from "react-router-dom";
import React from "react";
import { useLocation, useNavigate } from "react-router-dom";
import Loading from "src/components/Loading";
import { useInfo } from "src/hooks/useInfo";

export function StartRedirect({ children }: React.PropsWithChildren) {
const { data: info } = useInfo();
Expand All @@ -10,8 +10,12 @@ export function StartRedirect({ children }: React.PropsWithChildren) {

React.useEffect(() => {
if (!info || (info.setupCompleted && !info.running)) {
if (info && !info.albyAccountConnected && info.albyUserIdentifier) {
navigate("/alby/auth");
}
return;
}

navigate("/");
}, [info, location, navigate]);

Expand Down
1 change: 1 addition & 0 deletions frontend/src/constants.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export const localStorageKeys = {
returnTo: "returnTo",
setupReturnTo: "setupReturnTo",
channelOrder: "channelOrder",
authToken: "authToken",
};
Expand Down
12 changes: 8 additions & 4 deletions frontend/src/routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,14 @@ const routes = [
{
element: <Navigate to="password" replace />,
},
{
path: "alby",
element: <ConnectAlbyAccount connectUrl="/setup/auth" />,
},
{
path: "auth",
element: <AlbyAuthRedirect />,
},
{
path: "password",
element: <SetupPassword />,
Expand Down Expand Up @@ -417,10 +425,6 @@ const routes = [
},
],
},
{
path: "alby/auth",
element: <AlbyAuthRedirect />,
},
],
},
{
Expand Down
8 changes: 6 additions & 2 deletions frontend/src/screens/ConnectAlbyAccount.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,11 @@ import {
CardTitle,
} from "src/components/ui/card";

export function ConnectAlbyAccount() {
type ConnectAlbyAccountProps = {
connectUrl?: string;
};

export function ConnectAlbyAccount({ connectUrl }: ConnectAlbyAccountProps) {
return (
<div className="w-full h-full flex flex-col items-center justify-center gap-5">
<Container>
Expand Down Expand Up @@ -81,7 +85,7 @@ export function ConnectAlbyAccount() {
</Card>
</div>
<div className="flex flex-col items-center justify-center mt-8 gap-2">
<LinkButton to="/alby/auth" size="lg">
<LinkButton to={connectUrl || "/alby/auth"} size="lg">
Connect now
</LinkButton>
<LinkButton
Expand Down
57 changes: 40 additions & 17 deletions frontend/src/screens/Welcome.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from "react";
import { Link, useNavigate } from "react-router-dom";
import { useNavigate } from "react-router-dom";
import Container from "src/components/Container";
import { Button } from "src/components/ui/button";
import {
Expand All @@ -11,6 +11,7 @@ import {
DialogTitle,
DialogTrigger,
} from "src/components/ui/dialog";
import { localStorageKeys } from "src/constants";
import { useInfo } from "src/hooks/useInfo";

export function Welcome() {
Expand All @@ -24,6 +25,26 @@ export function Welcome() {
navigate("/");
}, [info, navigate]);

function navigateToAuthPage(returnTo: string) {
if (info?.albyAccountConnected) {
// in case user goes back after authenticating in setup
// we don't want to show the auth screen twice
navigate(returnTo);
return;
}

window.localStorage.setItem(localStorageKeys.setupReturnTo, returnTo);

// by default, allow the user to choose whether or not to connect to alby account
let navigateTo = "/setup/alby";
if (info?.oauthRedirect) {
// if using a custom OAuth client (e.g. Alby Cloud) the user must connect their Alby account
// but they are already logged in at getalby.com, so it should be an instant redirect.
navigateTo = "/alby/auth";
}
navigate(navigateTo);
}

return (
<Container>
<div className="grid text-center gap-5">
Expand All @@ -37,26 +58,28 @@ export function Welcome() {
</p>
</div>
<div className="grid gap-2">
<Link
to={
info?.backendType
? "/setup/password?node=preset" // node already setup through env variables
: "/setup/password?node=ldk"
}
<Button
className="w-full"
onClick={() =>
navigateToAuthPage(
info?.backendType
? "/setup/password?node=preset" // node already setup through env variables
: "/setup/password?node=ldk"
)
}
>
<Button className="w-full">
Get Started
{info?.backendType && ` (${info?.backendType})`}
</Button>
</Link>
Get Started
{info?.backendType && ` (${info?.backendType})`}
</Button>

{info?.enableAdvancedSetup && (
<Link to="/setup/advanced" className="w-full">
<Button variant="secondary" className="w-full">
Advanced Setup
</Button>
</Link>
<Button
variant="secondary"
className="w-full"
onClick={() => navigateToAuthPage("/setup/advanced")}
>
Advanced Setup
</Button>
)}
</div>
<div className="text-sm text-muted-foreground">
Expand Down
21 changes: 5 additions & 16 deletions frontend/src/screens/setup/SetupFinish.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import animationData from "src/assets/lotties/loading.json";
import Container from "src/components/Container";
import { Button } from "src/components/ui/button";
import { ToastSignature, useToast } from "src/components/ui/use-toast";
import { localStorageKeys } from "src/constants";

import { useInfo } from "src/hooks/useInfo";
import { saveAuthToken } from "src/lib/auth";
Expand All @@ -17,7 +16,6 @@ import { request } from "src/utils/request";
let lastStartupErrorTime: string;
export function SetupFinish() {
const navigate = useNavigate();
const { nodeInfo, unlockPassword } = useSetupStore();
const { toast } = useToast();
const { data: info } = useInfo(true); // poll the info endpoint to auto-redirect when app is running

Expand Down Expand Up @@ -86,18 +84,17 @@ export function SetupFinish() {
(async () => {
setLoading(true);
const succeeded = await finishSetup(
nodeInfo,
unlockPassword,
toast,
info.oauthRedirect
useSetupStore.getState().nodeInfo,
useSetupStore.getState().unlockPassword,
toast
);
// only setup call is successful as start is async
if (!succeeded) {
setLoading(false);
setConnectionError(true);
}
})();
}, [nodeInfo, navigate, unlockPassword, toast, info]);
}, [navigate, toast, info]);

if (connectionError) {
return (
Expand Down Expand Up @@ -134,17 +131,9 @@ export function SetupFinish() {
const finishSetup = async (
nodeInfo: SetupNodeInfo,
unlockPassword: string,
toast: ToastSignature,
autoAuth: boolean
toast: ToastSignature
): Promise<boolean> => {
try {
let redirectTo = "/alby/account";
if (autoAuth) {
redirectTo = "/alby/auth";
}

window.localStorage.setItem(localStorageKeys.returnTo, redirectTo);

await request("/api/setup", {
method: "POST",
headers: {
Expand Down

0 comments on commit f590c6f

Please sign in to comment.