NIP-05 Login

This commit is contained in:
Kieran 2023-01-02 23:36:30 +00:00
parent 0d01e02f42
commit 98262463c4
Signed by: Kieran
GPG Key ID: DE71CEB3925BE941
7 changed files with 69 additions and 59 deletions

View File

@ -23,8 +23,8 @@ export function NoteCreator(props) {
return (
<>
{replyTo ? <small>{`Reply to: ${replyTo.Id.substring(0, 8)}`}</small> : null}
<div className="send-note">
<input type="text" placeholder="Sup?" value={note} onChange={(e) => setNote(e.target.value)}></input>
<div className="flex">
<input type="text" placeholder="Sup?" value={note} onChange={(e) => setNote(e.target.value)} className="f-grow mr10"></input>
<div className="btn" onClick={() => sendNote()}>Send</div>
</div>
</>

View File

@ -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);

View File

@ -172,4 +172,8 @@ body.scroll-lock {
.tabs > div {
margin-right: 10px;
}
.error {
color: red;
}

View File

@ -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 (
<>
<h1>Login</h1>
<p>Enter your private key:</p>
<div className="flex">
<input type="text" placeholder="Private key" className="f-grow" onChange={e => setKey(e.target.value)} />
<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}
<div className="tabs">
<div className="btn" onClick={(e) => doLogin()}>Login</div>
<div className="btn" onClick={() => makeRandomKey()}>Generate Key</div>

View File

@ -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;
}

View File

@ -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 ? <NoteCreator /> : null}
{followHints()}
<div className="timeline">
{notes?.sort((a, b) => b.created_at - a.created_at).map(e => <Note key={e.id} data={e} />)}
</div>
{notes?.sort((a, b) => b.created_at - a.created_at).map(e => <Note key={e.id} data={e} />)}
</>
);
}

View File

@ -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;