feat: install extension on insecure connections
Some checks failed
Docker build on tag / build (push) Has been cancelled
Some checks failed
Docker build on tag / build (push) Has been cancelled
This commit is contained in:
@ -6,6 +6,10 @@
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
.login p,
|
||||
.login a {
|
||||
color: #999999;
|
||||
}
|
||||
|
||||
@ -21,7 +25,7 @@
|
||||
|
||||
.login .logo {
|
||||
margin-top: 16px;
|
||||
margin-bottom: 67px;
|
||||
margin-bottom: 50px;
|
||||
}
|
||||
|
||||
.login > div:nth-child(1) {
|
||||
@ -73,14 +77,16 @@
|
||||
}
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
.login {
|
||||
width: unset;
|
||||
height: unset;
|
||||
}
|
||||
|
||||
.login > div:nth-child(2) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.login .login-note {
|
||||
}
|
||||
|
||||
.login .login-actions {
|
||||
margin-top: 32px;
|
||||
}
|
||||
@ -99,20 +105,14 @@
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
line-height: 24px;
|
||||
margin-top: 56px;
|
||||
margin-bottom: 64px;
|
||||
}
|
||||
|
||||
@media (max-width: 520px) {
|
||||
.login .login-or {
|
||||
margin-top: 32px;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
margin-top: 40px;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
.light .login-or {
|
||||
color: #a1a1aa;
|
||||
}
|
||||
|
||||
.login-container input[type="text"] {
|
||||
border: none;
|
||||
background-color: var(--gray-secondary);
|
||||
|
@ -5,14 +5,14 @@ import { useDispatch, useSelector } from "react-redux";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import * as secp from "@noble/secp256k1";
|
||||
import { useIntl, FormattedMessage } from "react-intl";
|
||||
import { HexKey } from "@snort/nostr";
|
||||
|
||||
import { RootState } from "State/Store";
|
||||
import { setPrivateKey, setPublicKey, setRelays, setGeneratedPrivateKey } from "State/Login";
|
||||
import { DefaultRelays, EmailRegex, MnemonicRegex } from "Const";
|
||||
import { bech32ToHex, generateBip39Entropy, entropyToDerivedKey, unwrap } from "Util";
|
||||
import { HexKey } from "@snort/nostr";
|
||||
import ZapButton from "Element/ZapButton";
|
||||
// import useImgProxy from "Feed/ImgProxy";
|
||||
import useImgProxy from "Hooks/useImgProxy";
|
||||
|
||||
import messages from "./messages";
|
||||
|
||||
@ -67,7 +67,9 @@ export default function LoginPage() {
|
||||
const [error, setError] = useState("");
|
||||
const [art, setArt] = useState<ArtworkEntry>();
|
||||
const { formatMessage } = useIntl();
|
||||
//const { proxy } = useImgProxy();
|
||||
const { proxy } = useImgProxy();
|
||||
const hasNip7 = "nostr" in window;
|
||||
const isSecure = window.location.protocol === "https:";
|
||||
|
||||
useEffect(() => {
|
||||
if (publicKey) {
|
||||
@ -77,14 +79,19 @@ export default function LoginPage() {
|
||||
|
||||
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);
|
||||
proxy(ret.link).then(a => setArt({ ...ret, link: a }));
|
||||
}, []);
|
||||
|
||||
async function doLogin() {
|
||||
const insecureMsg = formatMessage({
|
||||
defaultMessage:
|
||||
"Can't login with private key on an insecure connection, please use a Nostr key manager extension instead",
|
||||
});
|
||||
try {
|
||||
if (key.startsWith("nsec")) {
|
||||
if (!isSecure) {
|
||||
throw new Error(insecureMsg);
|
||||
}
|
||||
const hexKey = bech32ToHex(key);
|
||||
if (secp.utils.isValidPrivateKey(hexKey)) {
|
||||
dispatch(setPrivateKey(hexKey));
|
||||
@ -98,16 +105,30 @@ export default function LoginPage() {
|
||||
const hexKey = await getNip05PubKey(key);
|
||||
dispatch(setPublicKey(hexKey));
|
||||
} else if (key.match(MnemonicRegex)) {
|
||||
if (!isSecure) {
|
||||
throw new Error(insecureMsg);
|
||||
}
|
||||
const ent = generateBip39Entropy(key);
|
||||
const keyHex = entropyToDerivedKey(ent);
|
||||
dispatch(setPrivateKey(keyHex));
|
||||
} else if (secp.utils.isValidPrivateKey(key)) {
|
||||
if (!isSecure) {
|
||||
throw new Error(insecureMsg);
|
||||
}
|
||||
dispatch(setPrivateKey(key));
|
||||
} else {
|
||||
throw new Error("INVALID PRIVATE KEY");
|
||||
}
|
||||
} catch (e) {
|
||||
setError(`Failed to load NIP-05 pub key (${e})`);
|
||||
if (e instanceof Error) {
|
||||
setError(e.message);
|
||||
} else {
|
||||
setError(
|
||||
formatMessage({
|
||||
defaultMessage: "Unknown login error",
|
||||
})
|
||||
);
|
||||
}
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
@ -139,9 +160,8 @@ export default function LoginPage() {
|
||||
}
|
||||
|
||||
function altLogins() {
|
||||
const nip07 = "nostr" in window;
|
||||
if (!nip07) {
|
||||
return null;
|
||||
if (!hasNip7) {
|
||||
return;
|
||||
}
|
||||
|
||||
return (
|
||||
@ -154,6 +174,92 @@ export default function LoginPage() {
|
||||
);
|
||||
}
|
||||
|
||||
function generateKey() {
|
||||
if (!isSecure) return;
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex login-or">
|
||||
<FormattedMessage defaultMessage="OR" description="Seperator text for Login / Generate Key" />
|
||||
<div className="divider w-max"></div>
|
||||
</div>
|
||||
<h1 dir="auto">
|
||||
<FormattedMessage defaultMessage="Create an Account" description="Heading for generate key flow" />
|
||||
</h1>
|
||||
<p>
|
||||
<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="login-actions">
|
||||
<button type="button" onClick={() => makeRandomKey()}>
|
||||
<FormattedMessage defaultMessage="Generate Key" description="Button: Generate a new key" />
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function installExtension() {
|
||||
if (isSecure) return;
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex login-or">
|
||||
<FormattedMessage defaultMessage="OR" description="Seperator text for Login / Generate Key" />
|
||||
<div className="divider w-max"></div>
|
||||
</div>
|
||||
<h1 dir="auto">
|
||||
<FormattedMessage
|
||||
defaultMessage="Install Extension"
|
||||
description="Heading for install key manager extension"
|
||||
/>
|
||||
</h1>
|
||||
<p>
|
||||
<FormattedMessage defaultMessage="Key manager extensions are more secure and allow you to easily login to any Nostr client, here are some well known extensions:" />
|
||||
</p>
|
||||
<ul>
|
||||
<li>
|
||||
<a href="https://getalby.com/" target="_blank" rel="noreferrer">
|
||||
Alby
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="https://chrome.google.com/webstore/detail/nos2x/kpgefcfmnafjgpblomihpgmejjdanjjp"
|
||||
target="_blank"
|
||||
rel="noreferrer">
|
||||
nos2x
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
defaultMessage="If you want to try out some others, check out {link} for more!"
|
||||
values={{
|
||||
link: <a href="https://github.com/aljazceru/awesome-nostr#browser-extensions">awesome-nostr</a>,
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
<p>
|
||||
<FormattedMessage defaultMessage="Once you setup your key manager extension and generated a key, you can follow our new users flow to setup your profile and help you find some interesting people on Nostr to follow." />
|
||||
</p>
|
||||
{hasNip7 ? (
|
||||
<div className="login-actions">
|
||||
<button type="button" onClick={() => doNip07Login().then(() => navigate("/new/username"))}>
|
||||
<FormattedMessage defaultMessage="Setup Profile" />
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<b className="error">
|
||||
<FormattedMessage defaultMessage="Hmm, can't find a key manager extension.. try reloading the page." />
|
||||
</b>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="login">
|
||||
<div>
|
||||
@ -183,36 +289,14 @@ export default function LoginPage() {
|
||||
description="Explanation for public key only login is read-only"
|
||||
/>
|
||||
</p>
|
||||
{/* <a href="">
|
||||
<FormattedMessage
|
||||
defaultMessage="Why is there no password field?"
|
||||
description="Link to why your private key is your password"
|
||||
/>
|
||||
</a>*/}
|
||||
<div dir="auto" className="login-actions">
|
||||
<button type="button" onClick={doLogin}>
|
||||
<FormattedMessage defaultMessage="Login" description="Login button" />
|
||||
</button>
|
||||
{altLogins()}
|
||||
</div>
|
||||
<div className="flex login-or">
|
||||
<FormattedMessage defaultMessage="OR" description="Seperator text for Login / Generate Key" />
|
||||
<div className="divider w-max"></div>
|
||||
</div>
|
||||
<h1 dir="auto">
|
||||
<FormattedMessage defaultMessage="Create an Account" description="Heading for generate key flow" />
|
||||
</h1>
|
||||
<p>
|
||||
<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="login-actions">
|
||||
<button type="button" onClick={() => makeRandomKey()}>
|
||||
<FormattedMessage defaultMessage="Generate Key" description="Button: Generate a new key" />
|
||||
</button>
|
||||
</div>
|
||||
{generateKey()}
|
||||
{installExtension()}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
|
Reference in New Issue
Block a user