diff --git a/src/element/NoteCreator.js b/src/element/NoteCreator.js index 832567a4..e33595dc 100644 --- a/src/element/NoteCreator.js +++ b/src/element/NoteCreator.js @@ -23,8 +23,8 @@ export function NoteCreator(props) { return ( <> {replyTo ? {`Reply to: ${replyTo.Id.substring(0, 8)}`} : null} -
- setNote(e.target.value)}> +
+ setNote(e.target.value)} className="f-grow mr10">
sendNote()}>Send
diff --git a/src/feed/EventPublisher.js b/src/feed/EventPublisher.js index 792f1a2a..45f1789b 100644 --- a/src/feed/EventPublisher.js +++ b/src/feed/EventPublisher.js @@ -22,7 +22,6 @@ export default function useEventPublisher() { if (nip07 === true && hasNip07) { ev.Id = await ev.CreateId(); let tmpEv = await window.nostr.signEvent(ev.ToObject()); - console.log(tmpEv); return Event.FromObject(tmpEv); } else { await ev.Sign(privKey); diff --git a/src/index.css b/src/index.css index e3ca0af1..62039705 100644 --- a/src/index.css +++ b/src/index.css @@ -172,4 +172,8 @@ body.scroll-lock { .tabs > div { margin-right: 10px; +} + +.error { + color: red; } \ No newline at end of file diff --git a/src/pages/Login.js b/src/pages/Login.js index f8f2b706..14318156 100644 --- a/src/pages/Login.js +++ b/src/pages/Login.js @@ -4,13 +4,16 @@ import { useNavigate } from "react-router-dom"; import * as secp from '@noble/secp256k1'; import { bech32 } from "bech32"; -import { setPrivateKey, setNip07PubKey } from "../state/Login"; +import { setPrivateKey, setPublicKey } from "../state/Login"; + +const EmailRegex = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; export default function LoginPage() { const dispatch = useDispatch(); const navigate = useNavigate(); const publicKey = useSelector(s => s.login.publicKey); const [key, setKey] = useState(""); + const [error, setError] = useState(""); useEffect(() => { if (publicKey) { @@ -18,22 +21,51 @@ export default function LoginPage() { } }, [publicKey]); - function doLogin() { - if (key.startsWith("nsec")) { - let nKey = bech32.decode(key); - let buff = bech32.fromWords(nKey.words); - let hexKey = secp.utils.bytesToHex(Uint8Array.from(buff)); - if (secp.utils.isValidPrivateKey(hexKey)) { - dispatch(setPrivateKey(hexKey)); - } else { - throw "INVALID PRIVATE KEY"; + function bech32ToHex(str) { + let nKey = bech32.decode(str); + let buff = bech32.fromWords(nKey.words); + return secp.utils.bytesToHex(Uint8Array.from(buff)); + } + + async function getNip05PubKey(addr) { + let [username, domain] = addr.split("@"); + let rsp = await fetch(`https://${domain}/.well-known/nostr.json?name=${encodeURIComponent(username)}`); + if (rsp.ok) { + let data = await rsp.json(); + let pKey = data.names[username]; + if (pKey) { + return pKey; } - } else { - if (secp.utils.isValidPrivateKey(key)) { - dispatch(setPrivateKey(key)); + } + throw "User key not found" + } + + async function doLogin() { + + try { + if (key.startsWith("nsec")) { + let hexKey = bech32ToHex(key); + if (secp.utils.isValidPrivateKey(hexKey)) { + dispatch(setPrivateKey(hexKey)); + } else { + throw "INVALID PRIVATE KEY"; + } + } else if (key.startsWith("npub")) { + let hexKey = bech32ToHex(key); + dispatch(setPublicKey(hexKey)); + } else if (key.match(EmailRegex)) { + let hexKey = await getNip05PubKey(key); + dispatch(setPublicKey(hexKey)); } else { - throw "INVALID PRIVATE KEY"; + if (secp.utils.isValidPrivateKey(key)) { + dispatch(setPrivateKey(key)); + } else { + throw "INVALID PRIVATE KEY"; + } } + } catch (e) { + setError(`Failed to load NIP-05 pub key (${e})`); + console.error(e); } } @@ -45,7 +77,7 @@ export default function LoginPage() { async function doNip07Login() { let pubKey = await window.nostr.getPublicKey(); - dispatch(setNip07PubKey(pubKey)); + dispatch(setPublicKey(pubKey)); } function altLogins() { @@ -67,10 +99,10 @@ export default function LoginPage() { return ( <>

Login

-

Enter your private key:

- setKey(e.target.value)} /> + setKey(e.target.value)} />
+ {error.length > 0 ? {error} : null}
doLogin()}>Login
makeRandomKey()}>Generate Key
diff --git a/src/pages/Root.css b/src/pages/Root.css deleted file mode 100644 index 1a59296d..00000000 --- a/src/pages/Root.css +++ /dev/null @@ -1,16 +0,0 @@ -.send-note { - display: flex; - margin-bottom: 10px; -} - -.send-note > input[type="text"] { - flex-grow: 1; -} - -.send-note > .btn { - margin: 0 5px; -} - -.login { - margin-bottom: 10px; -} \ No newline at end of file diff --git a/src/pages/Root.js b/src/pages/Root.js index 883174a9..2659d26f 100644 --- a/src/pages/Root.js +++ b/src/pages/Root.js @@ -1,4 +1,3 @@ -import "./Root.css"; import { useSelector } from "react-redux"; import Note from "../element/Note"; import useTimelineFeed from "../feed/TimelineFeed"; @@ -19,9 +18,7 @@ export default function RootPage() { <> {pubKey ? : null} {followHints()} -
- {notes?.sort((a, b) => b.created_at - a.created_at).map(e => )} -
+ {notes?.sort((a, b) => b.created_at - a.created_at).map(e => )} ); } \ No newline at end of file diff --git a/src/state/Login.js b/src/state/Login.js index 5b09c7bd..0308fcbe 100644 --- a/src/state/Login.js +++ b/src/state/Login.js @@ -2,7 +2,7 @@ import { createSlice } from '@reduxjs/toolkit' import * as secp from '@noble/secp256k1'; const PrivateKeyItem = "secret"; -const Nip07PublicKeyItem = "nip07:pubkey"; +const PublicKeyItem = "pubkey"; const NotificationsReadItem = "notifications-read"; const LoginSlice = createSlice({ @@ -33,11 +33,6 @@ const LoginSlice = createSlice({ */ follows: [], - /** - * Login keys are managed by extension - */ - nip07: false, - /** * Notifications for this login session */ @@ -52,7 +47,7 @@ const LoginSlice = createSlice({ init: (state) => { state.privateKey = window.localStorage.getItem(PrivateKeyItem); if (state.privateKey) { - window.localStorage.removeItem(Nip07PublicKeyItem); // reset nip07 if using private key + window.localStorage.removeItem(PublicKeyItem); // reset nip07 if using private key state.publicKey = secp.utils.bytesToHex(secp.schnorr.getPublicKey(state.privateKey, true)); state.loggedOut = false; } else { @@ -65,11 +60,10 @@ const LoginSlice = createSlice({ "wss://nostr-pub.wellorder.net": { read: true, write: true } }; - // check nip07 pub key - let nip07PubKey = window.localStorage.getItem(Nip07PublicKeyItem); - if (nip07PubKey && !state.privateKey) { - state.publicKey = nip07PubKey; - state.nip07 = true; + // check pub key only + let pubKey = window.localStorage.getItem(PublicKeyItem); + if (pubKey && !state.privateKey) { + state.publicKey = pubKey; state.loggedOut = false; } @@ -80,18 +74,16 @@ const LoginSlice = createSlice({ } }, setPrivateKey: (state, action) => { + state.loggedOut = false; state.privateKey = action.payload; window.localStorage.setItem(PrivateKeyItem, action.payload); state.publicKey = secp.utils.bytesToHex(secp.schnorr.getPublicKey(action.payload, true)); }, setPublicKey: (state, action) => { + window.localStorage.setItem(PublicKeyItem, action.payload); + state.loggedOut = false; state.publicKey = action.payload; }, - setNip07PubKey: (state, action) => { - window.localStorage.setItem(Nip07PublicKeyItem, action.payload); - state.publicKey = action.payload; - state.nip07 = true; - }, setRelays: (state, action) => { // filter out non-websocket urls let filtered = Object.entries(action.payload) @@ -119,12 +111,14 @@ const LoginSlice = createSlice({ }, logout: (state) => { window.localStorage.removeItem(PrivateKeyItem); - window.localStorage.removeItem(Nip07PublicKeyItem); + window.localStorage.removeItem(PublicKeyItem); + window.localStorage.removeItem(NotificationsReadItem); state.privateKey = null; state.publicKey = null; state.follows = []; state.notifications = []; state.loggedOut = true; + state.readNotifications = 0; }, markNotificationsRead: (state) => { state.readNotifications = new Date().getTime(); @@ -133,5 +127,5 @@ const LoginSlice = createSlice({ } }); -export const { init, setPrivateKey, setPublicKey, setNip07PubKey, setRelays, setFollows, addNotifications, logout, markNotificationsRead } = LoginSlice.actions; +export const { init, setPrivateKey, setPublicKey, setRelays, setFollows, addNotifications, logout, markNotificationsRead } = LoginSlice.actions; export const reducer = LoginSlice.reducer; \ No newline at end of file