diff --git a/frontend/src/components/MnemonicInputs.tsx b/frontend/src/components/MnemonicInputs.tsx index ed786cea..31335239 100644 --- a/frontend/src/components/MnemonicInputs.tsx +++ b/frontend/src/components/MnemonicInputs.tsx @@ -1,8 +1,10 @@ import { wordlist } from "@scure/bip39/wordlists/english"; import { useState } from "react"; +import PasswordViewAdornment from "src/components/PasswordAdornment"; import { Card, CardContent, + CardDescription, CardHeader, CardTitle, } from "src/components/ui/card"; @@ -12,6 +14,7 @@ type MnemonicInputsProps = { mnemonic?: string; setMnemonic?(mnemonic: string): void; readOnly?: boolean; + description?: string; }; export default function MnemonicInputs({ @@ -19,11 +22,8 @@ export default function MnemonicInputs({ setMnemonic, readOnly, children, + description, }: React.PropsWithChildren) { - const [revealedIndex, setRevealedIndex] = useState( - undefined - ); - const words = mnemonic?.split(" ") || []; while (words.length < 12) { words.push(""); @@ -33,30 +33,37 @@ export default function MnemonicInputs({ words.pop(); } + const [revealedIndex, setRevealedIndex] = useState( + undefined + ); + return ( <> - Recovery phrase to your wallet + + Wallet Recovery Phrase + + + {description} + -
+
{words.map((word, i) => { const isRevealed = revealedIndex === i; const inputId = `mnemonic-word-${i}`; return (
- - {i + 1}. - + {i + 1}.
setRevealedIndex(i)} onBlur={() => setRevealedIndex(undefined)} - readOnly={readOnly} - className="w-32 text-center" + className="w-32 text-center border-[#E4E4E7] text-muted-foreground" list={readOnly ? undefined : "wordlist"} value={isRevealed ? word : word.length ? "•••••" : ""} onChange={(e) => { @@ -71,6 +78,17 @@ export default function MnemonicInputs({ .trim() ); }} + endAdornment={ + { + if (passwordView) { + document.getElementById(inputId)?.focus(); + } + }} + iconClass="text-muted-foreground" + /> + } />
diff --git a/frontend/src/components/PasswordAdornment.tsx b/frontend/src/components/PasswordAdornment.tsx index 24464768..3dd1ff46 100644 --- a/frontend/src/components/PasswordAdornment.tsx +++ b/frontend/src/components/PasswordAdornment.tsx @@ -1,12 +1,18 @@ import { EyeIcon, EyeOffIcon } from "lucide-react"; import { useEffect, useState } from "react"; +import { cn } from "src/lib/utils"; type Props = { onChange: (viewingPassword: boolean) => void; isRevealed?: boolean; + iconClass?: string; }; -export default function PasswordViewAdornment({ onChange, isRevealed }: Props) { +export default function PasswordViewAdornment({ + onChange, + isRevealed, + iconClass, +}: Props) { const [_isRevealed, setRevealed] = useState(false); // toggle the button if password view is handled by component itself @@ -27,9 +33,9 @@ export default function PasswordViewAdornment({ onChange, isRevealed }: Props) { }} > {_isRevealed ? ( - + ) : ( - + )} ); diff --git a/frontend/src/components/SettingsHeader.tsx b/frontend/src/components/SettingsHeader.tsx index 7d6876a8..dea7128a 100644 --- a/frontend/src/components/SettingsHeader.tsx +++ b/frontend/src/components/SettingsHeader.tsx @@ -1,8 +1,9 @@ +import React from "react"; import { Separator } from "src/components/ui/separator"; type Props = { title: string; - description: string; + description: string | React.ReactNode; }; function SettingsHeader({ title, description }: Props) { @@ -10,8 +11,8 @@ function SettingsHeader({ title, description }: Props) { <>
-

{title}

-

{description}

+

{title}

+

{description}

diff --git a/frontend/src/components/SidebarHint.tsx b/frontend/src/components/SidebarHint.tsx index 059ec548..d98e0669 100644 --- a/frontend/src/components/SidebarHint.tsx +++ b/frontend/src/components/SidebarHint.tsx @@ -68,7 +68,6 @@ function SidebarHint() { ); } } - type SidebarHintCardProps = { title: string; description: string | ReactElement; diff --git a/frontend/src/components/layouts/SettingsLayout.tsx b/frontend/src/components/layouts/SettingsLayout.tsx index 94b26729..54e1c65b 100644 --- a/frontend/src/components/layouts/SettingsLayout.tsx +++ b/frontend/src/components/layouts/SettingsLayout.tsx @@ -1,7 +1,11 @@ -import { Power } from "lucide-react"; import React, { useState } from "react"; import { NavLink, Outlet, useNavigate } from "react-router-dom"; import AppHeader from "src/components/AppHeader"; +import { buttonVariants } from "src/components/ui/button"; + +import { useInfo } from "src/hooks/useInfo"; + +import { Power } from "lucide-react"; import { AlertDialog, AlertDialogAction, @@ -13,12 +17,8 @@ import { AlertDialogTitle, AlertDialogTrigger, } from "src/components/ui/alert-dialog"; -import { buttonVariants } from "src/components/ui/button"; import { LoadingButton } from "src/components/ui/loading-button"; import { useToast } from "src/components/ui/use-toast"; - -import { useInfo } from "src/hooks/useInfo"; - import { cn } from "src/lib/utils"; import { request } from "src/utils/request"; @@ -60,7 +60,7 @@ export default function SettingsLayout() { <> @@ -76,11 +76,11 @@ export default function SettingsLayout() { - Do you want to turn off Alby Hub? + Do you want to turn off your Alby Hub? This will turn off your Alby Hub and make your node offline. - You won't be able to send or receive bitcoin until you unlock + You won’t be able to send or receive bitcoin until you unlock it. @@ -94,6 +94,7 @@ export default function SettingsLayout() { } /> +
); } ); + Input.displayName = "Input"; export { Input }; diff --git a/frontend/src/routes.tsx b/frontend/src/routes.tsx index 033841b6..83dd79e9 100644 --- a/frontend/src/routes.tsx +++ b/frontend/src/routes.tsx @@ -45,10 +45,12 @@ import ConnectPeer from "src/screens/peers/ConnectPeer"; import Peers from "src/screens/peers/Peers"; import { AlbyAccount } from "src/screens/settings/AlbyAccount"; import { AutoUnlock } from "src/screens/settings/AutoUnlock"; +import Backup from "src/screens/settings/Backup"; import { ChangeUnlockPassword } from "src/screens/settings/ChangeUnlockPassword"; import DebugTools from "src/screens/settings/DebugTools"; import DeveloperSettings from "src/screens/settings/DeveloperSettings"; import Settings from "src/screens/settings/Settings"; + import { ImportMnemonic } from "src/screens/setup/ImportMnemonic"; import { RestoreNode } from "src/screens/setup/RestoreNode"; import { SetupAdvanced } from "src/screens/setup/SetupAdvanced"; @@ -183,9 +185,14 @@ const routes = [ }, { path: "backup", - element: , + element: , handle: { crumb: () => "Backup" }, }, + { + path: "mnemonic-backup", + element: , + handle: { crumb: () => "Key Backup" }, + }, { path: "node-backup", element: , diff --git a/frontend/src/screens/BackupMnemonic.tsx b/frontend/src/screens/BackupMnemonic.tsx index f086ed6e..530c240d 100644 --- a/frontend/src/screens/BackupMnemonic.tsx +++ b/frontend/src/screens/BackupMnemonic.tsx @@ -1,15 +1,9 @@ -import { - ExternalLinkIcon, - LifeBuoy, - ShieldAlert, - ShieldCheck, -} from "lucide-react"; +import { CopyIcon } from "lucide-react"; import React, { useState } from "react"; import { useNavigate } from "react-router-dom"; -import Container from "src/components/Container"; -import ExternalLink from "src/components/ExternalLink"; import MnemonicInputs from "src/components/MnemonicInputs"; +import PasswordViewAdornment from "src/components/PasswordAdornment"; import SettingsHeader from "src/components/SettingsHeader"; import { Button } from "src/components/ui/button"; import { Checkbox } from "src/components/ui/checkbox"; @@ -18,6 +12,7 @@ import { Label } from "src/components/ui/label"; import { LoadingButton } from "src/components/ui/loading-button"; import { useToast } from "src/components/ui/use-toast"; import { useInfo } from "src/hooks/useInfo"; +import { copyToClipboard } from "src/lib/clipboard"; import { MnemonicResponse } from "src/types"; import { handleRequestError } from "src/utils/handleRequestError"; import { request } from "src/utils/request"; @@ -29,6 +24,8 @@ export function BackupMnemonic() { const { mutate: refetchInfo } = useInfo(); const [unlockPassword, setUnlockPassword] = React.useState(""); + const [unlockPasswordVisible, setUnlockPasswordVisible] = + React.useState(false); const [decryptedMnemonic, setDecryptedMnemonic] = React.useState(""); const [loading, setLoading] = React.useState(false); const [backedUp, setIsBackedUp] = useState(false); @@ -92,96 +89,59 @@ export function BackupMnemonic() { return ( <> {!decryptedMnemonic ? ( - -

Please confirm it's you

-

- Enter your unlock password to continue +

+

+ Enter your unlock password to view your recovery phrase.

- <> -
- - setUnlockPassword(e.target.value)} - value={unlockPassword} - placeholder="Password" - /> -
- Continue - +
+ + setUnlockPassword(e.target.value)} + value={unlockPassword} + placeholder="Password" + endAdornment={ + + setUnlockPasswordVisible(passwordView) + } + /> + } + /> +
+
+ + View Recovery Phase + +
- +
) : (
-
-
-
- -
- - Your recovery phrase is a set of 12 words that{" "} - backs up your wallet on-chain balance.  - {info?.albyAccountConnected && ( - <> - Channel backups are saved automatically to your Alby - Account, encrypted with your recovery phrase. - - )} - {!info?.albyAccountConnected && ( - <> - Make sure to also backup your data directory as this - is required to recover funds on your channels. You can also - connect your Alby Account for automatic encrypted backups. - - )} - -
-
-
- -
- - Make sure to write them down somewhere safe and private. - -
-
-
- -
- - If you lose access to your hub and do not have your{" "} - recovery phrase - {!info?.albyAccountConnected && ( - <> or do not backup your data directory - )} - , you will lose access to your funds. - -
-
-
- - Learn more about backups - - -
- - -
+ +
{backedUp && !info?.albyAccountConnected && ( -
+
setIsBackedUp2(!backedUp2)} /> -
)} +
)} diff --git a/frontend/src/screens/settings/AlbyAccount.tsx b/frontend/src/screens/settings/AlbyAccount.tsx index 17c330ee..8e116047 100644 --- a/frontend/src/screens/settings/AlbyAccount.tsx +++ b/frontend/src/screens/settings/AlbyAccount.tsx @@ -1,36 +1,17 @@ -import { ExitIcon } from "@radix-ui/react-icons"; -import { ExternalLinkIcon } from "lucide-react"; +import { Link2Off, RefreshCcw, SquareArrowOutUpRight } from "lucide-react"; -import ExternalLink from "src/components/ExternalLink"; import Loading from "src/components/Loading"; import SettingsHeader from "src/components/SettingsHeader"; -import { - AlertDialog, - AlertDialogAction, - AlertDialogCancel, - AlertDialogContent, - AlertDialogDescription, - AlertDialogFooter, - AlertDialogHeader, - AlertDialogTitle, - AlertDialogTrigger, -} from "src/components/ui/alert-dialog"; -import { - Card, - CardDescription, - CardHeader, - CardTitle, -} from "src/components/ui/card"; -import { LoadingButton } from "src/components/ui/loading-button"; +import { Button, ExternalLinkButton } from "src/components/ui/button"; +import { Separator } from "src/components/ui/separator"; import { UnlinkAlbyAccount } from "src/components/UnlinkAlbyAccount"; import { useAlbyMe } from "src/hooks/useAlbyMe"; import { useInfo } from "src/hooks/useInfo"; -import { useMigrateLDKStorage } from "src/hooks/useMigrateLDKStorage"; export function AlbyAccount() { const { data: info } = useInfo(); const { data: me } = useAlbyMe(); - const { isMigratingStorage, migrateLDKStorage } = useMigrateLDKStorage(); + if (!info || !me) { return ; } @@ -39,109 +20,73 @@ export function AlbyAccount() { <> - - - - Your Alby Account - - Manage your Alby Account - Settings such as your lightning address on getalby.com - - - - - - - - Change Alby Account - - Link your Hub to a different Alby - Account - - - - - - - - - Disconnect Alby Account - - Use Alby Hub without an Alby - Account - - - - - {info?.vssSupported && ( - <> -
- -

- Versioned Storage Service (VSS) provides a secure, encrypted - server-side storage of essential lightning and onchain data. -

-

- This service is enabled by your Alby account and provides additional - backup security which allows you to recover your lightning data with - your recovery phrase alone, without having to close your channels. -

- {info.ldkVssEnabled && ( -

- ✅ VSS enabled.{" "} - {info.ldkVssEnabled && ( - <>Migration from VSS will be available shortly. - )} +

+
+
+

Manage Alby Account

+

+ Manage your Alby Account settings such as lightning address or + notifications on getalby.com +

+
+
+ + Alby Account Settings{" "} + + +
+
+ +
+
+

Change Alby Account

+

+ Link your Hub to a different Alby Account +

+
+
+ + + +
+
+ +
+
+

Unlink Alby Account

+

+ Use your Alby Hub without an Alby Account.

- )} - {!me.subscription.buzz && ( -

VSS is only available to Alby users with a paid subscription.

- )} - {!info.ldkVssEnabled && me.subscription.buzz && ( - - - { - - Enable VSS - - } - - - - Alby Hub Restart Required - -
-

- As part of enabling VSS your hub will be shut down, and - you will need to enter your unlock password to start it - again. -

-

- Please ensure you have no pending payments or channel - closures before continuing. -

-
-
-
- - Cancel - migrateLDKStorage("VSS")}> - Confirm - - -
-
- )} - - )} +
+
+ + + +
+
+
); } diff --git a/frontend/src/screens/settings/AutoUnlock.tsx b/frontend/src/screens/settings/AutoUnlock.tsx index 6788eb51..1ff1fe4a 100644 --- a/frontend/src/screens/settings/AutoUnlock.tsx +++ b/frontend/src/screens/settings/AutoUnlock.tsx @@ -1,7 +1,6 @@ import { AlertTriangle } from "lucide-react"; import React from "react"; -import Container from "src/components/Container"; import Loading from "src/components/Loading"; import SettingsHeader from "src/components/SettingsHeader"; import { Alert, AlertDescription, AlertTitle } from "src/components/ui/alert"; @@ -63,7 +62,7 @@ export function AutoUnlock() { title="Auto Unlock" description="Configure Alby Hub will automatically unlock on start (e.g. after machine reboot)" /> - +

In some situations it can be impractical to manually unlock the wallet every time Alby Hub is started. In those cases you can save the unlock @@ -82,9 +81,9 @@ export function AutoUnlock() { <>

-
+
- - - Enable Auto Unlock - +
+ + Enable Auto Unlock + +
)} @@ -106,15 +106,17 @@ export function AutoUnlock() { <>
- - Disable Auto Unlock - +
+ + Disable Auto Unlock + +
)} - +
); } diff --git a/frontend/src/screens/settings/Backup.tsx b/frontend/src/screens/settings/Backup.tsx new file mode 100644 index 00000000..19ade020 --- /dev/null +++ b/frontend/src/screens/settings/Backup.tsx @@ -0,0 +1,272 @@ +import React from "react"; +import { Link, useNavigate } from "react-router-dom"; +import PasswordViewAdornment from "src/components/PasswordAdornment"; +import SettingsHeader from "src/components/SettingsHeader"; +import { Alert } from "src/components/ui/alert"; +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, + AlertDialogTrigger, +} from "src/components/ui/alert-dialog"; +import { Badge } from "src/components/ui/badge"; +import { Button } from "src/components/ui/button"; +import { Input } from "src/components/ui/input"; +import { Label } from "src/components/ui/label"; +import { LoadingButton } from "src/components/ui/loading-button"; +import { Separator } from "src/components/ui/separator"; +import { useToast } from "src/components/ui/use-toast"; +import { useAlbyMe } from "src/hooks/useAlbyMe"; +import { useInfo } from "src/hooks/useInfo"; +import { useMigrateLDKStorage } from "src/hooks/useMigrateLDKStorage"; +import { MnemonicResponse } from "src/types"; +import { request } from "src/utils/request"; + +export default function Backup() { + const navigate = useNavigate(); + + const { toast } = useToast(); + const { data: info, hasNodeBackup, hasMnemonic } = useInfo(); + const { data: me } = useAlbyMe(); + const { isMigratingStorage, migrateLDKStorage } = useMigrateLDKStorage(); + const [unlockPassword, setUnlockPassword] = React.useState(""); + const [unlockPasswordVisible, setUnlockPasswordVisible] = + React.useState(false); + + const [loading, setLoading] = React.useState(false); + + const onSubmitPassword = async (e: React.FormEvent) => { + e.preventDefault(); + try { + setLoading(true); + const result = await request("/api/mnemonic", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + unlockPassword, + }), + }); + + if (result?.mnemonic) { + navigate("/settings/mnemonic-backup"); + } + } catch (error) { + toast({ + title: "Incorrect password", + description: "Failed to decrypt mnemonic.", + variant: "destructive", + }); + } finally { + setLoading(false); + } + }; + + return ( + <> + + + Backup your wallet recovery phrase and or your channel states in + order to migrate your node.{" "} + + + {" "} + Learn more about backups + + + } + /> + + {hasMnemonic && ( +
+
+

Wallet Keys Backup

+

+ Key recovery phrase is a group of 12 random words that back up + your wallet on-chain balance. Using them is the only way to + recover access to your wallet on another machine or when you loose + your unlock password. .  + {info?.albyAccountConnected && ( + <> + Channel backups are saved automatically to your Alby Account, + encrypted with your recovery phrase. + + )} + {!info?.albyAccountConnected && ( + <> + Make sure to also backup your data directory as this is + required to recover funds on your channels. You can also + connect your Alby Account for automatic encrypted backups. + + )} +

+
+

+ If you loose access to your Hub and do not have your recovery + phrase, you will loose access to your funds. +

+
+
+ + setUnlockPassword(e.target.value)} + value={unlockPassword} + placeholder="Password" + endAdornment={ + + setUnlockPasswordVisible(passwordView) + } + /> + } + /> +

+ Enter your unlock password to view your recovery phrase. +

+
+
+ + View Recovery Phase + +
+
+
+ )} + + {(info?.vssSupported || hasNodeBackup) && ( + <> + +
+

Channels Backup

+ + {info?.vssSupported && ( + <> +
+
+

+ Automated Channels Backups +

+ + {me?.subscription.buzz && info.ldkVssEnabled + ? "Premium" + : "Alby Cloud"} + +
+

+ When enabled, channels backups are saved automatically a + virtual disk encrypted with your wallet recovery phrase + thanks to Versioned Storage Service (VSS). This allows you + to recover or migrate your Hub without having to close your + channels. +

+ + {me?.subscription.buzz && ( + + + { + + {info.ldkVssEnabled + ? "Automated Backups Enabled" + : "Enable Automated Backups"} + + } + + + + + Alby Hub Restart Required + + +
+

+ As part of enabling VSS your hub will be shut + down, and you will need to enter your unlock + password to start it again. +

+

+ Please ensure you have no pending payments or + channel closures before continuing. +

+
+
+
+ + Cancel + migrateLDKStorage("VSS")} + > + Confirm + + +
+
+ )} + + {!me?.subscription.buzz && ( + + VSS is only available to Alby users with a Alby Cloud + subcscription. + + )} +
+ + )} + + {hasNodeBackup && ( +
+

Migrate Alby Hub

+

+ If you’d like to import or migrate your Hub onto another + device or server, you’ll need your channels’ backup file to + import your channels state. This instance of Hub will be + stopped. +

+ + + +
+ )} +
+ + )} + + {!hasMnemonic && !hasNodeBackup && !info?.vssSupported && ( +

+ No wallet recovery phrase or channel state backup present. +

+ )} + + ); +} diff --git a/frontend/src/screens/settings/ChangeUnlockPassword.tsx b/frontend/src/screens/settings/ChangeUnlockPassword.tsx index 70b06974..3f43701e 100644 --- a/frontend/src/screens/settings/ChangeUnlockPassword.tsx +++ b/frontend/src/screens/settings/ChangeUnlockPassword.tsx @@ -1,7 +1,9 @@ +import { ExclamationTriangleIcon } from "@radix-ui/react-icons"; import React from "react"; +import PasswordViewAdornment from "src/components/PasswordAdornment"; -import Container from "src/components/Container"; import SettingsHeader from "src/components/SettingsHeader"; +import { Alert, AlertDescription, AlertTitle } from "src/components/ui/alert"; import { Input } from "src/components/ui/input"; import { Label } from "src/components/ui/label"; import { LoadingButton } from "src/components/ui/loading-button"; @@ -18,6 +20,13 @@ export function ChangeUnlockPassword() { const [newUnlockPassword, setNewUnlockPassword] = React.useState(""); const [confirmNewUnlockPassword, setConfirmNewUnlockPassword] = React.useState(""); + const [currentUnlockPasswordVisible, setCurrentUnlockPasswordVisible] = + React.useState(false); + const [newUnlockPasswordVisible, setNewUnlockPasswordVisible] = + React.useState(false); + const [confirmNewUnlockPasswordVisible, setConfirmNewUnlockPasswordVisible] = + React.useState(false); + const [loading, setLoading] = React.useState(false); const onSubmit = async (e: React.FormEvent) => { @@ -57,48 +66,86 @@ export function ChangeUnlockPassword() { return ( <> - -
+
+ + +
+ Important! +
+
+ + Password can't be reset or recovered. Make sure to back it up! + +
+
setCurrentUnlockPassword(e.target.value)} value={currentUnlockPassword} placeholder="Password" + endAdornment={ + + setCurrentUnlockPasswordVisible(passwordView) + } + /> + } />
setNewUnlockPassword(e.target.value)} value={newUnlockPassword} placeholder="Password" + endAdornment={ + + setNewUnlockPasswordVisible(passwordView) + } + /> + } />
setConfirmNewUnlockPassword(e.target.value)} value={confirmNewUnlockPassword} placeholder="Password" + endAdornment={ + + setConfirmNewUnlockPasswordVisible(passwordView) + } + /> + } />
- Change Password +
+ Change Password +
- +
); } diff --git a/frontend/src/screens/settings/DebugTools.tsx b/frontend/src/screens/settings/DebugTools.tsx index 2895408b..aab6db83 100644 --- a/frontend/src/screens/settings/DebugTools.tsx +++ b/frontend/src/screens/settings/DebugTools.tsx @@ -256,9 +256,9 @@ export default function DebugTools() {
-
+
{ if (!open) { @@ -266,26 +266,39 @@ export default function DebugTools() { } }} > - - - - - - {info?.backendType === "LDK" && ( - diff --git a/frontend/src/screens/settings/DeveloperSettings.tsx b/frontend/src/screens/settings/DeveloperSettings.tsx index a6d9ddf1..e4919d55 100644 --- a/frontend/src/screens/settings/DeveloperSettings.tsx +++ b/frontend/src/screens/settings/DeveloperSettings.tsx @@ -1,11 +1,11 @@ -import { Copy, Rocket, TriangleAlert } from "lucide-react"; +import { Copy, SquareArrowOutUpRight } from "lucide-react"; import React from "react"; import SettingsHeader from "src/components/SettingsHeader"; -import { Alert, AlertDescription, AlertTitle } from "src/components/ui/alert"; import { Button, ExternalLinkButton } from "src/components/ui/button"; import { Input } from "src/components/ui/input"; import { Label } from "src/components/ui/label"; import { LoadingButton } from "src/components/ui/loading-button"; +import { Separator } from "src/components/ui/separator"; import { useToast } from "src/components/ui/use-toast"; import { useAlbyMe } from "src/hooks/useAlbyMe"; import { copyToClipboard } from "src/lib/clipboard"; @@ -59,116 +59,137 @@ export default function DeveloperSettings() { <> - - - Power your Apps with NWC - -
+
+
+

Power your apps with NWC

+ +
Alby Hub can power your lightning apps and services in all - environments using the amazing open protocol{" "} - Nostr Wallet Connect. + environments using Nostr Wallet Connect, an open protocol to connect + lightning wallets and apps
- - Learn More +
+
+ + Learn More on nwc.dev{" "} + - - - - - Experimental - -
+
+
+ +
+
+

Experimental

+ +
You can use your auth token to access the Alby Hub internal API. However, whenever possible, we recommend using the NWC API directly for more stability. Please note that the internal API may change or be removed entirely in the future.
- {!token && !showCreateTokenForm && ( -
+ {!token && !showCreateTokenForm && ( +
+ - )} - {showCreateTokenForm && !token && ( -
- <> -
- - setExpiryDays(e.target.value)} - value={expiryDays} - /> -
-
- - setUnlockPassword(e.target.value)} - value={unlockPassword} - placeholder="Password" - /> -
- Create Token - -
- )} - {token && ( +
+ )} + {showCreateTokenForm && !token && ( +
<> -
+
+ + setExpiryDays(e.target.value)} + value={expiryDays} + /> +
+
+ setUnlockPassword(e.target.value)} + value={unlockPassword} + placeholder="Password" /> -
-
-

- To make requests from your application to{" "} +

+ Create Token +
+ + + )} + {token && ( + <> +
+ + +
+
+

+ To make requests from your application to{" "} + + {window.location.origin}/api + {" "} + add the following header{albyMe?.hub.name && "s"}: +

+
    +
  1. - {window.location.origin}/api + Authorization: Bearer YOUR_TOKEN {" "} - add the following header{albyMe?.hub.name && "s"}: -

    -
      + + {albyMe?.hub.name && (
    1. - Authorization: Bearer YOUR_TOKEN + AlbyHub-Name: {albyMe.hub.name} {" "}
    2. - {albyMe?.hub.name && ( -
    3. - - AlbyHub-Name: {albyMe.hub.name} - {" "} -
    4. - )} -
    -
-

- This token grants full access to your hub. Please keep it - secure. If you suspect that the token has been compromised, - immediately change your JWT_SECRET environment variable or - contact support@getalby.com. -

- - )} - - + )} + +
+

+ This token grants full access to your hub. Please keep it secure. + If you suspect that the token has been compromised, immediately + change your JWT_SECRET environment variable or contact + support@getalby.com. +

+ + )} +
); } diff --git a/frontend/src/screens/settings/Settings.tsx b/frontend/src/screens/settings/Settings.tsx index 79248194..f55bdd83 100644 --- a/frontend/src/screens/settings/Settings.tsx +++ b/frontend/src/screens/settings/Settings.tsx @@ -23,10 +23,10 @@ function Settings() { <> -
-
+ +
-
- +
+