NIP-05 Login
This commit is contained in:
parent
0d01e02f42
commit
98262463c4
@ -23,8 +23,8 @@ export function NoteCreator(props) {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{replyTo ? <small>{`Reply to: ${replyTo.Id.substring(0, 8)}`}</small> : null}
|
{replyTo ? <small>{`Reply to: ${replyTo.Id.substring(0, 8)}`}</small> : null}
|
||||||
<div className="send-note">
|
<div className="flex">
|
||||||
<input type="text" placeholder="Sup?" value={note} onChange={(e) => setNote(e.target.value)}></input>
|
<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 className="btn" onClick={() => sendNote()}>Send</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
@ -22,7 +22,6 @@ export default function useEventPublisher() {
|
|||||||
if (nip07 === true && hasNip07) {
|
if (nip07 === true && hasNip07) {
|
||||||
ev.Id = await ev.CreateId();
|
ev.Id = await ev.CreateId();
|
||||||
let tmpEv = await window.nostr.signEvent(ev.ToObject());
|
let tmpEv = await window.nostr.signEvent(ev.ToObject());
|
||||||
console.log(tmpEv);
|
|
||||||
return Event.FromObject(tmpEv);
|
return Event.FromObject(tmpEv);
|
||||||
} else {
|
} else {
|
||||||
await ev.Sign(privKey);
|
await ev.Sign(privKey);
|
||||||
|
@ -173,3 +173,7 @@ body.scroll-lock {
|
|||||||
.tabs > div {
|
.tabs > div {
|
||||||
margin-right: 10px;
|
margin-right: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.error {
|
||||||
|
color: red;
|
||||||
|
}
|
@ -4,13 +4,16 @@ import { useNavigate } from "react-router-dom";
|
|||||||
import * as secp from '@noble/secp256k1';
|
import * as secp from '@noble/secp256k1';
|
||||||
import { bech32 } from "bech32";
|
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() {
|
export default function LoginPage() {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const publicKey = useSelector(s => s.login.publicKey);
|
const publicKey = useSelector(s => s.login.publicKey);
|
||||||
const [key, setKey] = useState("");
|
const [key, setKey] = useState("");
|
||||||
|
const [error, setError] = useState("");
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (publicKey) {
|
if (publicKey) {
|
||||||
@ -18,16 +21,41 @@ export default function LoginPage() {
|
|||||||
}
|
}
|
||||||
}, [publicKey]);
|
}, [publicKey]);
|
||||||
|
|
||||||
function doLogin() {
|
function bech32ToHex(str) {
|
||||||
if (key.startsWith("nsec")) {
|
let nKey = bech32.decode(str);
|
||||||
let nKey = bech32.decode(key);
|
|
||||||
let buff = bech32.fromWords(nKey.words);
|
let buff = bech32.fromWords(nKey.words);
|
||||||
let hexKey = secp.utils.bytesToHex(Uint8Array.from(buff));
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw "User key not found"
|
||||||
|
}
|
||||||
|
|
||||||
|
async function doLogin() {
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (key.startsWith("nsec")) {
|
||||||
|
let hexKey = bech32ToHex(key);
|
||||||
if (secp.utils.isValidPrivateKey(hexKey)) {
|
if (secp.utils.isValidPrivateKey(hexKey)) {
|
||||||
dispatch(setPrivateKey(hexKey));
|
dispatch(setPrivateKey(hexKey));
|
||||||
} else {
|
} else {
|
||||||
throw "INVALID PRIVATE KEY";
|
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 {
|
} else {
|
||||||
if (secp.utils.isValidPrivateKey(key)) {
|
if (secp.utils.isValidPrivateKey(key)) {
|
||||||
dispatch(setPrivateKey(key));
|
dispatch(setPrivateKey(key));
|
||||||
@ -35,6 +63,10 @@ export default function LoginPage() {
|
|||||||
throw "INVALID PRIVATE KEY";
|
throw "INVALID PRIVATE KEY";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} catch (e) {
|
||||||
|
setError(`Failed to load NIP-05 pub key (${e})`);
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function makeRandomKey() {
|
async function makeRandomKey() {
|
||||||
@ -45,7 +77,7 @@ export default function LoginPage() {
|
|||||||
|
|
||||||
async function doNip07Login() {
|
async function doNip07Login() {
|
||||||
let pubKey = await window.nostr.getPublicKey();
|
let pubKey = await window.nostr.getPublicKey();
|
||||||
dispatch(setNip07PubKey(pubKey));
|
dispatch(setPublicKey(pubKey));
|
||||||
}
|
}
|
||||||
|
|
||||||
function altLogins() {
|
function altLogins() {
|
||||||
@ -67,10 +99,10 @@ export default function LoginPage() {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<h1>Login</h1>
|
<h1>Login</h1>
|
||||||
<p>Enter your private key:</p>
|
|
||||||
<div className="flex">
|
<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>
|
</div>
|
||||||
|
{error.length > 0 ? <b className="error">{error}</b> : null}
|
||||||
<div className="tabs">
|
<div className="tabs">
|
||||||
<div className="btn" onClick={(e) => doLogin()}>Login</div>
|
<div className="btn" onClick={(e) => doLogin()}>Login</div>
|
||||||
<div className="btn" onClick={() => makeRandomKey()}>Generate Key</div>
|
<div className="btn" onClick={() => makeRandomKey()}>Generate Key</div>
|
||||||
|
@ -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;
|
|
||||||
}
|
|
@ -1,4 +1,3 @@
|
|||||||
import "./Root.css";
|
|
||||||
import { useSelector } from "react-redux";
|
import { useSelector } from "react-redux";
|
||||||
import Note from "../element/Note";
|
import Note from "../element/Note";
|
||||||
import useTimelineFeed from "../feed/TimelineFeed";
|
import useTimelineFeed from "../feed/TimelineFeed";
|
||||||
@ -19,9 +18,7 @@ export default function RootPage() {
|
|||||||
<>
|
<>
|
||||||
{pubKey ? <NoteCreator /> : null}
|
{pubKey ? <NoteCreator /> : null}
|
||||||
{followHints()}
|
{followHints()}
|
||||||
<div className="timeline">
|
|
||||||
{notes?.sort((a, b) => b.created_at - a.created_at).map(e => <Note key={e.id} data={e} />)}
|
{notes?.sort((a, b) => b.created_at - a.created_at).map(e => <Note key={e.id} data={e} />)}
|
||||||
</div>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
@ -2,7 +2,7 @@ import { createSlice } from '@reduxjs/toolkit'
|
|||||||
import * as secp from '@noble/secp256k1';
|
import * as secp from '@noble/secp256k1';
|
||||||
|
|
||||||
const PrivateKeyItem = "secret";
|
const PrivateKeyItem = "secret";
|
||||||
const Nip07PublicKeyItem = "nip07:pubkey";
|
const PublicKeyItem = "pubkey";
|
||||||
const NotificationsReadItem = "notifications-read";
|
const NotificationsReadItem = "notifications-read";
|
||||||
|
|
||||||
const LoginSlice = createSlice({
|
const LoginSlice = createSlice({
|
||||||
@ -33,11 +33,6 @@ const LoginSlice = createSlice({
|
|||||||
*/
|
*/
|
||||||
follows: [],
|
follows: [],
|
||||||
|
|
||||||
/**
|
|
||||||
* Login keys are managed by extension
|
|
||||||
*/
|
|
||||||
nip07: false,
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Notifications for this login session
|
* Notifications for this login session
|
||||||
*/
|
*/
|
||||||
@ -52,7 +47,7 @@ const LoginSlice = createSlice({
|
|||||||
init: (state) => {
|
init: (state) => {
|
||||||
state.privateKey = window.localStorage.getItem(PrivateKeyItem);
|
state.privateKey = window.localStorage.getItem(PrivateKeyItem);
|
||||||
if (state.privateKey) {
|
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.publicKey = secp.utils.bytesToHex(secp.schnorr.getPublicKey(state.privateKey, true));
|
||||||
state.loggedOut = false;
|
state.loggedOut = false;
|
||||||
} else {
|
} else {
|
||||||
@ -65,11 +60,10 @@ const LoginSlice = createSlice({
|
|||||||
"wss://nostr-pub.wellorder.net": { read: true, write: true }
|
"wss://nostr-pub.wellorder.net": { read: true, write: true }
|
||||||
};
|
};
|
||||||
|
|
||||||
// check nip07 pub key
|
// check pub key only
|
||||||
let nip07PubKey = window.localStorage.getItem(Nip07PublicKeyItem);
|
let pubKey = window.localStorage.getItem(PublicKeyItem);
|
||||||
if (nip07PubKey && !state.privateKey) {
|
if (pubKey && !state.privateKey) {
|
||||||
state.publicKey = nip07PubKey;
|
state.publicKey = pubKey;
|
||||||
state.nip07 = true;
|
|
||||||
state.loggedOut = false;
|
state.loggedOut = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -80,18 +74,16 @@ const LoginSlice = createSlice({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
setPrivateKey: (state, action) => {
|
setPrivateKey: (state, action) => {
|
||||||
|
state.loggedOut = false;
|
||||||
state.privateKey = action.payload;
|
state.privateKey = action.payload;
|
||||||
window.localStorage.setItem(PrivateKeyItem, action.payload);
|
window.localStorage.setItem(PrivateKeyItem, action.payload);
|
||||||
state.publicKey = secp.utils.bytesToHex(secp.schnorr.getPublicKey(action.payload, true));
|
state.publicKey = secp.utils.bytesToHex(secp.schnorr.getPublicKey(action.payload, true));
|
||||||
},
|
},
|
||||||
setPublicKey: (state, action) => {
|
setPublicKey: (state, action) => {
|
||||||
|
window.localStorage.setItem(PublicKeyItem, action.payload);
|
||||||
|
state.loggedOut = false;
|
||||||
state.publicKey = action.payload;
|
state.publicKey = action.payload;
|
||||||
},
|
},
|
||||||
setNip07PubKey: (state, action) => {
|
|
||||||
window.localStorage.setItem(Nip07PublicKeyItem, action.payload);
|
|
||||||
state.publicKey = action.payload;
|
|
||||||
state.nip07 = true;
|
|
||||||
},
|
|
||||||
setRelays: (state, action) => {
|
setRelays: (state, action) => {
|
||||||
// filter out non-websocket urls
|
// filter out non-websocket urls
|
||||||
let filtered = Object.entries(action.payload)
|
let filtered = Object.entries(action.payload)
|
||||||
@ -119,12 +111,14 @@ const LoginSlice = createSlice({
|
|||||||
},
|
},
|
||||||
logout: (state) => {
|
logout: (state) => {
|
||||||
window.localStorage.removeItem(PrivateKeyItem);
|
window.localStorage.removeItem(PrivateKeyItem);
|
||||||
window.localStorage.removeItem(Nip07PublicKeyItem);
|
window.localStorage.removeItem(PublicKeyItem);
|
||||||
|
window.localStorage.removeItem(NotificationsReadItem);
|
||||||
state.privateKey = null;
|
state.privateKey = null;
|
||||||
state.publicKey = null;
|
state.publicKey = null;
|
||||||
state.follows = [];
|
state.follows = [];
|
||||||
state.notifications = [];
|
state.notifications = [];
|
||||||
state.loggedOut = true;
|
state.loggedOut = true;
|
||||||
|
state.readNotifications = 0;
|
||||||
},
|
},
|
||||||
markNotificationsRead: (state) => {
|
markNotificationsRead: (state) => {
|
||||||
state.readNotifications = new Date().getTime();
|
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;
|
export const reducer = LoginSlice.reducer;
|
Loading…
Reference in New Issue
Block a user