add secret key login

This commit is contained in:
hzrd149 2023-02-16 09:07:31 -06:00
parent ee34fd2470
commit e4c40d4a16
10 changed files with 234 additions and 12 deletions

View File

@ -19,6 +19,7 @@
"light-bolt11-decoder": "^2.1.0",
"moment": "^2.29.4",
"noble-secp256k1": "^1.2.14",
"nostr-tools": "^1.4.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-error-boundary": "^3.1.4",

View File

@ -26,6 +26,7 @@ import useSubject from "./hooks/use-subject";
import { LoginNip05View } from "./views/login/nip05";
import { Button, Flex, Spinner, Text } from "@chakra-ui/react";
import { deleteDatabase } from "./services/db";
import { LoginNsecView } from "./views/login/nsec";
const RequireCurrentAccount = ({ children }: { children: JSX.Element }) => {
let location = useLocation();
@ -66,6 +67,7 @@ const router = createBrowserRouter([
{ path: "", element: <LoginStartView /> },
{ path: "npub", element: <LoginNpubView /> },
{ path: "nip05", element: <LoginNip05View /> },
{ path: "nsec", element: <LoginNsecView /> },
],
},
{

View File

@ -59,7 +59,7 @@ export function AccountSwitcherList() {
{otherAccounts.map((account) => (
<AccountItem key={account.pubkey} pubkey={account.pubkey} />
))}
<Button size="sm" leftIcon={<AddIcon />}>
<Button size="sm" leftIcon={<AddIcon />} onClick={() => accountService.logout()}>
Add Account
</Button>
</Flex>

View File

@ -3,8 +3,10 @@ import db from "./db";
export type Account = {
pubkey: string;
readonly: boolean;
readonly?: boolean;
relays?: string[];
secKey?: string;
useExtension?: boolean;
};
class AccountService {
@ -28,9 +30,14 @@ class AccountService {
hasAccount(pubkey: string) {
return this.accounts.value.some((acc) => acc.pubkey === pubkey);
}
addAccount(pubkey: string, relays?: string[], readonly = false) {
const account: Account = { pubkey, relays, readonly };
this.accounts.next(this.accounts.value.concat(account));
addAccount(account: Account) {
if (this.hasAccount(account.pubkey)) {
// replace account
this.accounts.next(this.accounts.value.map((acc) => (acc.pubkey === account.pubkey ? account : acc)));
} else {
// add account
this.accounts.next(this.accounts.value.concat(account));
}
db.put("accounts", account);
}

View File

@ -73,7 +73,7 @@ export const LoginNip05View = () => {
}
}
accountService.addAccount(pubkey, Array.from(bootstrapRelays), true);
accountService.addAccount({ pubkey, relays: Array.from(bootstrapRelays), readonly: true });
}
accountService.switchAccount(pubkey);

View File

@ -21,7 +21,7 @@ export const LoginNpubView = () => {
}
if (!accountService.hasAccount(pubkey)) {
accountService.addAccount(pubkey, [relayUrl], true);
accountService.addAccount({ pubkey, relays: [relayUrl], readonly: true });
}
accountService.switchAccount(pubkey);

151
src/views/login/nsec.tsx Normal file
View File

@ -0,0 +1,151 @@
import { useCallback, useState } from "react";
import {
Alert,
AlertDescription,
AlertIcon,
AlertTitle,
Box,
Button,
Flex,
FormControl,
FormHelperText,
FormLabel,
Input,
InputGroup,
InputRightElement,
Link,
useToast,
} from "@chakra-ui/react";
import { useNavigate } from "react-router-dom";
import { RelayUrlInput } from "../../components/relay-url-input";
import { Bech32Prefix, normalizeToBech32, normalizeToHex } from "../../helpers/nip-19";
import accountService from "../../services/account";
import clientRelaysService from "../../services/client-relays";
import { generatePrivateKey, getPublicKey } from "nostr-tools";
export const LoginNsecView = () => {
const navigate = useNavigate();
const toast = useToast();
const [show, setShow] = useState(false);
const [error, setError] = useState(false);
const [inputValue, setInputValue] = useState("");
const [hexKey, setHexKey] = useState("");
const [relayUrl, setRelayUrl] = useState("");
const [npub, setNpub] = useState("");
const generateNewKey = useCallback(() => {
const hex = generatePrivateKey();
const pubkey = getPublicKey(hex);
setHexKey(hex);
setInputValue(normalizeToBech32(hex, Bech32Prefix.SecKey) ?? "");
setNpub(normalizeToBech32(pubkey, Bech32Prefix.Pubkey) ?? "");
setShow(true);
}, [setHexKey, setInputValue, setShow]);
const handleInputChange: React.ChangeEventHandler<HTMLInputElement> = useCallback(
(e) => {
setInputValue(e.target.value);
try {
const hex = normalizeToHex(e.target.value);
if (hex) {
const pubkey = getPublicKey(hex);
setHexKey(hex);
setNpub(normalizeToBech32(pubkey, Bech32Prefix.Pubkey) ?? "");
setError(false);
} else {
setError(true);
}
} catch (e) {
setError(true);
}
},
[setInputValue, setHexKey, setNpub, setError]
);
const handleSubmit: React.FormEventHandler<HTMLDivElement> = (e) => {
e.preventDefault();
if (!hexKey) return;
const pubkey = getPublicKey(hexKey);
accountService.addAccount({ pubkey, relays: [relayUrl], secKey: hexKey });
clientRelaysService.bootstrapRelays.add(relayUrl);
accountService.switchAccount(pubkey);
};
return (
<Flex as="form" direction="column" gap="4" onSubmit={handleSubmit} minWidth="350">
<Alert status="warning" maxWidth="30rem">
<AlertIcon />
<Box>
<AlertTitle>Using nsec keys is insecure.</AlertTitle>
<AlertDescription>
You should use a browser extension like{" "}
<Link isExternal href="https://getalby.com/" target="_blank">
Alby
</Link>
{" or "}
<Link
isExternal
href="https://chrome.google.com/webstore/detail/nos2x/kpgefcfmnafjgpblomihpgmejjdanjjp"
target="_blank"
>
Nos2x
</Link>
</AlertDescription>
</Box>
</Alert>
<FormControl>
<FormLabel>Enter user secret key (nsec)</FormLabel>
<InputGroup size="md">
<Input
pr="4.5rem"
type={show ? "text" : "password"}
placeholder="nsec or hex"
isRequired
value={inputValue}
onChange={handleInputChange}
isInvalid={error}
/>
<InputRightElement width="4.5rem">
<Button h="1.75rem" size="sm" onClick={() => setShow((v) => !v)}>
{show ? "Hide" : "Show"}
</Button>
</InputRightElement>
</InputGroup>
</FormControl>
<FormControl>
<FormLabel>Pubkey Key (npub)</FormLabel>
<Input type="text" readOnly isDisabled value={npub} />
</FormControl>
<FormControl>
<FormLabel>Bootstrap relay</FormLabel>
<RelayUrlInput
placeholder="wss://nostr.example.com"
isRequired
value={relayUrl}
onChange={(e) => setRelayUrl(e.target.value)}
/>
<FormHelperText>The first relay to connect to.</FormHelperText>
</FormControl>
<Flex justifyContent="space-between" gap="2">
<Button variant="link" onClick={() => navigate("../")}>
Back
</Button>
<Button ml="auto" onClick={generateNewKey}>
Generate New
</Button>
<Button colorScheme="brand" type="submit">
Login
</Button>
</Flex>
</Flex>
);
};

View File

@ -26,7 +26,7 @@ export const LoginStartView = () => {
relays = Object.keys(extRelays).filter((url) => extRelays[url].read);
}
accountService.addAccount(pubkey, relays, false);
accountService.addAccount({ pubkey, relays });
}
accountService.switchAccount(pubkey);
@ -50,7 +50,8 @@ export const LoginStartView = () => {
Use browser extension
</Button>
<Button onClick={() => navigate("./nip05")}>Login with Nip-05 Id</Button>
<Button onClick={() => navigate("./npub")}>Login with npub</Button>
<Button onClick={() => navigate("./npub")}>Login with pubkey key (npub)</Button>
<Button onClick={() => navigate("./nsec")}>Login with secret key (nsec)</Button>
{accounts.length > 0 && (
<>
<Heading size="md" mt="4">

View File

@ -6,15 +6,21 @@ import { Bech32Prefix, normalizeToBech32 } from "../../../helpers/nip-19";
import accountService from "../../../services/account";
import { useUserMetadata } from "../../../hooks/use-user-metadata";
import { getUserDisplayName } from "../../../helpers/user-metadata";
import { useUserRelays } from "../../../hooks/use-user-relays";
import { RelayMode } from "../../../classes/relay";
export const UserProfileMenu = ({ pubkey, ...props }: { pubkey: string } & Omit<MenuIconButtonProps, "children">) => {
const npub = normalizeToBech32(pubkey, Bech32Prefix.Pubkey);
const metadata = useUserMetadata(pubkey);
const userRelays = useUserRelays(pubkey);
const loginAsUser = () => {
if (!accountService.hasAccount(pubkey)) {
accountService.addAccount(pubkey, [], true);
}
const readRelays = userRelays?.relays.filter((r) => r.mode === RelayMode.READ).map((r) => r.url) ?? [];
accountService.addAccount({
pubkey,
relays: readRelays,
readonly: true,
});
accountService.switchAccount(pubkey);
};

View File

@ -2346,6 +2346,21 @@
hey-listen "^1.0.8"
tslib "^2.3.1"
"@noble/hashes@1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.0.0.tgz#d5e38bfbdaba174805a4e649f13be9a9ed3351ae"
integrity sha512-DZVbtY62kc3kkBtMHqwCOfXrT/hnoORy5BJ4+HU1IR59X0KWAOqsfzQPcUl/lQLlG7qXbe/fZ3r/emxtAl+sqg==
"@noble/hashes@~1.2.0":
version "1.2.0"
resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.2.0.tgz#a3150eeb09cc7ab207ebf6d7b9ad311a9bdbed12"
integrity sha512-FZfhjEDbT5GRswV3C6uvLPHMiVD6lQBmpoX5+eSiPaMTXte/IKqI5dykDxzZB/WBeK/CDuQRBWarPdi3FNY2zQ==
"@noble/secp256k1@^1.7.1", "@noble/secp256k1@~1.7.0":
version "1.7.1"
resolved "https://registry.yarnpkg.com/@noble/secp256k1/-/secp256k1-1.7.1.tgz#b251c70f824ce3ca7f8dc3df08d58f005cc0507c"
integrity sha512-hOUk6AyBFmqVrv7k5WAw/LpszxVbj9gGN4JRkIX52fdFAj1UA61KXmZDvqVEm+pOyec3+fIeZB02LYa/pWOArw==
"@nodelib/fs.scandir@2.1.5":
version "2.1.5"
resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5"
@ -2431,6 +2446,28 @@
estree-walker "^2.0.2"
picomatch "^2.3.1"
"@scure/base@^1.1.1", "@scure/base@~1.1.0":
version "1.1.1"
resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.1.1.tgz#ebb651ee52ff84f420097055f4bf46cfba403938"
integrity sha512-ZxOhsSyxYwLJj3pLZCefNitxsj093tb2vq90mp2txoYeBqbcjDjqFhyM8eUjq/uFm6zJ+mUuqxlS2FkuSY1MTA==
"@scure/bip32@^1.1.5":
version "1.1.5"
resolved "https://registry.yarnpkg.com/@scure/bip32/-/bip32-1.1.5.tgz#d2ccae16dcc2e75bc1d75f5ef3c66a338d1ba300"
integrity sha512-XyNh1rB0SkEqd3tXcXMi+Xe1fvg+kUIcoRIEujP1Jgv7DqW2r9lg3Ah0NkFaCs9sTkQAQA8kw7xiRXzENi9Rtw==
dependencies:
"@noble/hashes" "~1.2.0"
"@noble/secp256k1" "~1.7.0"
"@scure/base" "~1.1.0"
"@scure/bip39@^1.1.1":
version "1.1.1"
resolved "https://registry.yarnpkg.com/@scure/bip39/-/bip39-1.1.1.tgz#b54557b2e86214319405db819c4b6a370cf340c5"
integrity sha512-t+wDck2rVkh65Hmv280fYdVdY25J9YeEUIgn2LG1WM6gxFkGzcksoDiUkWVpVp3Oex9xGC68JU2dSbUfwZ2jPg==
dependencies:
"@noble/hashes" "~1.2.0"
"@scure/base" "~1.1.0"
"@surma/rollup-plugin-off-main-thread@^2.2.3":
version "2.2.3"
resolved "https://registry.yarnpkg.com/@surma/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-2.2.3.tgz#ee34985952ca21558ab0d952f00298ad2190c053"
@ -4152,6 +4189,18 @@ normalize-package-data@^2.5.0:
semver "2 || 3 || 4 || 5"
validate-npm-package-license "^3.0.1"
nostr-tools@^1.4.1:
version "1.4.1"
resolved "https://registry.yarnpkg.com/nostr-tools/-/nostr-tools-1.4.1.tgz#9a8ba4ae7b23e78fa495b1b98b9b26992f41f791"
integrity sha512-pFAbVNtRMfW5ducUmk0f20IZv4a6pXYchBQKuA6D3x6sg83KoWipuiAie/gfOgrkoCsBLV14DmpWsVmo3N7WXQ==
dependencies:
"@noble/hashes" "1.0.0"
"@noble/secp256k1" "^1.7.1"
"@scure/base" "^1.1.1"
"@scure/bip32" "^1.1.5"
"@scure/bip39" "^1.1.1"
prettier "^2.8.4"
object-assign@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
@ -4322,6 +4371,11 @@ prettier@^2.7.1, prettier@^2.8.1:
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.1.tgz#4e1fd11c34e2421bc1da9aea9bd8127cd0a35efc"
integrity sha512-lqGoSJBQNJidqCHE80vqZJHWHRFoNYsSpP9AjFhlhi9ODCJA541svILes/+/1GM3VaL/abZi7cpFzOpdR9UPKg==
prettier@^2.8.4:
version "2.8.4"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.4.tgz#34dd2595629bfbb79d344ac4a91ff948694463c3"
integrity sha512-vIS4Rlc2FNh0BySk3Wkd6xmwxB0FpOndW5fisM5H8hsZSxU2VWVB5CWIkIjWvrHjIhxk2g3bfMKM87zNTrZddw==
pretty-bytes@^5.3.0:
version "5.6.0"
resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.6.0.tgz#356256f643804773c82f64723fe78c92c62beaeb"