snort/src/Pages/Login.tsx

229 lines
7.2 KiB
TypeScript
Raw Normal View History

2023-02-09 18:05:45 +00:00
import "./Login.css";
import { CSSProperties, useEffect, useState } from "react";
2022-12-27 23:46:13 +00:00
import { useDispatch, useSelector } from "react-redux";
2022-12-29 22:23:41 +00:00
import { useNavigate } from "react-router-dom";
import * as secp from "@noble/secp256k1";
2023-02-09 18:05:45 +00:00
import { FormattedMessage } from "react-intl";
2022-12-29 22:23:41 +00:00
2023-01-20 11:11:50 +00:00
import { RootState } from "State/Store";
2023-02-09 12:26:54 +00:00
import { setPrivateKey, setPublicKey, setRelays, setGeneratedPrivateKey } from "State/Login";
import { DefaultRelays, EmailRegex } from "Const";
2023-02-09 18:05:45 +00:00
import { bech32ToHex, unwrap } from "Util";
2023-01-20 11:11:50 +00:00
import { HexKey } from "Nostr";
2023-02-09 18:05:45 +00:00
import ZapButton from "Element/ZapButton";
// import useImgProxy from "Feed/ImgProxy";
2023-02-09 18:05:45 +00:00
interface ArtworkEntry {
name: string;
pubkey: HexKey;
link: string;
}
// todo: fill more
const Artwork: Array<ArtworkEntry> = [
{
name: "",
pubkey: bech32ToHex("npub1r0rs5q2gk0e3dk3nlc7gnu378ec6cnlenqp8a3cjhyzu6f8k5sgs4sq9ac"),
2023-02-09 18:49:12 +00:00
link: "https://void.cat/d/VKhPayp9ekeXYZGzAL9CxP",
2023-02-09 18:05:45 +00:00
},
{
name: "",
pubkey: bech32ToHex("npub1r0rs5q2gk0e3dk3nlc7gnu378ec6cnlenqp8a3cjhyzu6f8k5sgs4sq9ac"),
2023-02-09 18:49:12 +00:00
link: "https://void.cat/d/3H2h8xxc3aEN6EVeobd8tw",
2023-02-09 18:05:45 +00:00
},
{
name: "",
pubkey: bech32ToHex("npub1r0rs5q2gk0e3dk3nlc7gnu378ec6cnlenqp8a3cjhyzu6f8k5sgs4sq9ac"),
2023-02-09 18:49:12 +00:00
link: "https://void.cat/d/7i9W9PXn3TV86C4RUefNC9",
2023-02-09 18:05:45 +00:00
},
2023-02-09 18:49:12 +00:00
{
name: "",
pubkey: bech32ToHex("npub1r0rs5q2gk0e3dk3nlc7gnu378ec6cnlenqp8a3cjhyzu6f8k5sgs4sq9ac"),
2023-02-09 18:51:50 +00:00
link: "https://void.cat/d/KtoX4ei6RYHY7HESg3Ve3k",
},
2023-02-09 18:05:45 +00:00
];
2022-12-27 23:46:13 +00:00
2022-12-18 14:51:47 +00:00
export default function LoginPage() {
const dispatch = useDispatch();
const navigate = useNavigate();
2023-02-09 12:26:54 +00:00
const publicKey = useSelector<RootState, HexKey | undefined>(s => s.login.publicKey);
const [key, setKey] = useState("");
const [error, setError] = useState("");
2023-02-09 18:05:45 +00:00
const [art, setArt] = useState<ArtworkEntry>();
// const { proxy } = useImgProxy();
2022-12-27 23:46:13 +00:00
useEffect(() => {
if (publicKey) {
navigate("/");
2023-01-02 23:36:30 +00:00
}
}, [publicKey, navigate]);
2023-01-02 23:36:30 +00:00
2023-02-09 18:05:45 +00:00
useEffect(() => {
const ret = unwrap(Artwork.at(Artwork.length * Math.random()));
// disable for now because imgproxy is ded
// proxy(ret.link).then(a => setArt({ ...ret, link: a }));
setArt(ret);
2023-02-09 18:05:45 +00:00
}, []);
async function getNip05PubKey(addr: string) {
2023-02-07 19:47:57 +00:00
const [username, domain] = addr.split("@");
2023-02-09 12:26:54 +00:00
const rsp = await fetch(`https://${domain}/.well-known/nostr.json?name=${encodeURIComponent(username)}`);
if (rsp.ok) {
2023-02-07 19:47:57 +00:00
const data = await rsp.json();
const pKey = data.names[username];
if (pKey) {
return pKey;
}
}
throw new Error("User key not found");
}
2023-01-02 23:36:30 +00:00
async function doLogin() {
try {
if (key.startsWith("nsec")) {
2023-02-07 19:47:57 +00:00
const hexKey = bech32ToHex(key);
if (secp.utils.isValidPrivateKey(hexKey)) {
dispatch(setPrivateKey(hexKey));
} else {
throw new Error("INVALID PRIVATE KEY");
}
} else if (key.startsWith("npub")) {
2023-02-07 19:47:57 +00:00
const hexKey = bech32ToHex(key);
dispatch(setPublicKey(hexKey));
} else if (key.match(EmailRegex)) {
2023-02-07 19:47:57 +00:00
const hexKey = await getNip05PubKey(key);
dispatch(setPublicKey(hexKey));
} else {
if (secp.utils.isValidPrivateKey(key)) {
dispatch(setPrivateKey(key));
} else {
throw new Error("INVALID PRIVATE KEY");
2022-12-27 23:46:13 +00:00
}
}
} catch (e) {
setError(`Failed to load NIP-05 pub key (${e})`);
console.error(e);
2022-12-27 23:46:13 +00:00
}
}
2022-12-27 23:46:13 +00:00
async function makeRandomKey() {
2023-02-07 19:47:57 +00:00
const newKey = secp.utils.bytesToHex(secp.utils.randomPrivateKey());
dispatch(setGeneratedPrivateKey(newKey));
navigate("/new");
}
2023-01-01 10:44:38 +00:00
async function doNip07Login() {
2023-02-07 19:47:57 +00:00
const pubKey = await window.nostr.getPublicKey();
dispatch(setPublicKey(pubKey));
2023-01-26 22:16:35 +00:00
if ("getRelays" in window.nostr) {
2023-02-07 19:47:57 +00:00
const relays = await window.nostr.getRelays();
dispatch(
setRelays({
relays: {
...relays,
...Object.fromEntries(DefaultRelays.entries()),
},
createdAt: 1,
})
);
2022-12-29 10:51:32 +00:00
}
}
2022-12-29 10:51:32 +00:00
function altLogins() {
2023-02-07 19:47:57 +00:00
const nip07 = "nostr" in window;
if (!nip07) {
return null;
2022-12-29 10:51:32 +00:00
}
2022-12-18 14:51:47 +00:00
return (
2023-02-09 18:05:45 +00:00
<button type="button" onClick={doNip07Login}>
2023-02-10 10:40:16 +00:00
<FormattedMessage
defaultMessage="Login with Extension (NIP-07)"
description="Login button for NIP7 key manager extension"
/>
2023-02-09 18:05:45 +00:00
</button>
2022-12-18 14:51:47 +00:00
);
}
return (
2023-02-09 18:05:45 +00:00
<div className="login">
<div>
<div className="login-container">
<div className="logo" onClick={() => navigate("/")}>
Snort
</div>
<h1>
<FormattedMessage defaultMessage="Login" description="Login header" />
</h1>
<p>
<FormattedMessage defaultMessage="Your key" description="Label for key input" />
</p>
<div className="flex">
<input
type="text"
placeholder="nsec / npub / nip-05 / hex private key..."
className="f-grow"
onChange={e => setKey(e.target.value)}
/>
</div>
{error.length > 0 ? <b className="error">{error}</b> : null}
<p className="login-note">
<FormattedMessage
defaultMessage="Only the secret key can be used to publish (sign events), everything else logs you in read-only mode."
description="Explanation for public key only login is read-only"
/>
</p>
2023-02-10 10:40:16 +00:00
{/* <a href="">
2023-02-09 18:05:45 +00:00
<FormattedMessage
defaultMessage="Why is there no password field?"
description="Link to why your private key is your password"
/>
2023-02-10 10:40:16 +00:00
</a>*/}
2023-02-09 18:05:45 +00:00
<div className="login-actions">
<button type="button" onClick={doLogin}>
<FormattedMessage defaultMessage="Login" description="Login button" />
</button>
{altLogins()}
</div>
<div className="flex login-or">
<div className="login-note">
<FormattedMessage defaultMessage="OR" description="Seperator text for Login / Generate Key" />
</div>
<div className="divider w-max"></div>
</div>
<h1>
<FormattedMessage defaultMessage="Create an Account" description="Heading for generate key flow" />
</h1>
<p className="login-note">
<FormattedMessage
defaultMessage="Generate a public / private key pair. Do not share your private key with anyone, this acts as your password. Once lost, it cannot be “reset” or recovered. Keep safe!"
description="Note about key security before generating a new key"
/>
</p>
<div className="tabs">
<button type="button" onClick={() => makeRandomKey()}>
<FormattedMessage defaultMessage="Generate Key" description="Button: Generate a new key" />
</button>
</div>
</div>
</div>
2023-02-09 18:05:45 +00:00
<div>
<div className="artwork" style={{ ["--img-src"]: `url('${art?.link}')` } as CSSProperties}>
<div>
<FormattedMessage
defaultMessage="Art by {name}"
description="Artwork attribution label"
values={{
name: "Karnage",
}}
/>
<ZapButton pubkey={art?.pubkey ?? ""} />
</div>
</div>
</div>
</div>
);
2023-01-25 18:08:53 +00:00
}