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: auth with alby account before node setup #779

Merged
merged 4 commits into from
Nov 7, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just curious, what changed after v1.6.0? Why can't we remove both checks at once?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In 1.6.0 we made the Alby Account optional, and the existence of the Alby Account was used to check if the user had setup their node (as it was the last thing that happened after setting up the node)

When we made the Alby Account optional, we instead introduced the NodeLastStartTime config entry which we use to detect if they ever went through the setup.

So for backward compatibility, we had these old checks. But now, the Alby Account can be connected before the node is setup, so I had to remove that one. I was going to remove both, but I decided to keep the LDK directory check in as this is more critical to ensure the user does not go through the setup again (and wipe their old data)

// 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
Loading