mirror of
https://github.com/hzrd149/nostrudel.git
synced 2025-04-11 13:20:37 +02:00
encrypt secret keys and require password
This commit is contained in:
parent
d734c67302
commit
09b80bdb4a
@ -1,8 +0,0 @@
|
||||
# Changesets
|
||||
|
||||
Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works
|
||||
with multi-package repos, or single-package repos to help you version and publish your code. You can
|
||||
find the full documentation for it [in our repository](https://github.com/changesets/changesets)
|
||||
|
||||
We have a quick list of common questions to get you started engaging with this project in
|
||||
[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md)
|
@ -1,11 +0,0 @@
|
||||
{
|
||||
"$schema": "https://unpkg.com/@changesets/config@2.3.0/schema.json",
|
||||
"changelog": "@changesets/cli/changelog",
|
||||
"commit": false,
|
||||
"fixed": [],
|
||||
"linked": [],
|
||||
"access": "restricted",
|
||||
"baseBranch": "main",
|
||||
"updateInternalDependencies": "patch",
|
||||
"ignore": []
|
||||
}
|
@ -13,8 +13,8 @@
|
||||
- [x] Broadcast events
|
||||
- [x] User tipping
|
||||
- [x] Manage followers ( Contact List )
|
||||
- [x] Relay management
|
||||
- [ ] Profile management
|
||||
- [ ] Relay management
|
||||
- [ ] Image upload
|
||||
- [ ] Reactions
|
||||
- [ ] Dynamically connect to relays (start with one relay then connect to others as required)
|
||||
@ -48,6 +48,7 @@
|
||||
- [ ] [NIP-42](https://github.com/nostr-protocol/nips/blob/master/42.md): Authentication of clients to relays
|
||||
- [ ] [NIP-50](https://github.com/nostr-protocol/nips/blob/master/50.md): Keywords filter
|
||||
- [ ] [NIP-56](https://github.com/nostr-protocol/nips/blob/master/56.md): Reporting
|
||||
- [ ] [NIP-57](https://github.com/nostr-protocol/nips/blob/master/57.md): Lightning Zaps
|
||||
- [x] [NIP-65](https://github.com/nostr-protocol/nips/blob/master/65.md): Relay List Metadata
|
||||
|
||||
## TODO
|
||||
@ -55,7 +56,6 @@
|
||||
- Create a "event posting" service that can show modals (for qr code scanning), warnings (signed by wrong pubkey), and results (what relays responded) when posting events.
|
||||
- Create notifications service that keeps track of read notifications. (show unread count in sidenav)
|
||||
- Rebuild relays view to show relay info and settings NIP-11
|
||||
- use `nostr-tools` to allow user to generate and use nsec keys for login.
|
||||
- filter list of followers by users the user has blocked/reported (stops bots/spammers from showing up at followers)
|
||||
- Add note embeds
|
||||
- Add "repost" button that mentions the note
|
||||
|
@ -30,7 +30,6 @@
|
||||
"webln": "^0.3.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@changesets/cli": "^2.26.0",
|
||||
"@types/identicon.js": "^2.3.1",
|
||||
"@types/react": "^18.0.26",
|
||||
"@types/react-dom": "^18.0.9",
|
||||
|
@ -1,5 +1,7 @@
|
||||
import { useToast } from "@chakra-ui/react";
|
||||
import React, { useCallback, useContext, useMemo } from "react";
|
||||
import useSubject from "../hooks/use-subject";
|
||||
import accountService from "../services/account";
|
||||
import signingService from "../services/signing";
|
||||
import { DraftNostrEvent, NostrEvent } from "../types/nostr-event";
|
||||
|
||||
@ -19,11 +21,13 @@ export function useSigningContext() {
|
||||
|
||||
export const SigningProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
const toast = useToast();
|
||||
const current = useSubject(accountService.current);
|
||||
|
||||
const requestSignature = useCallback(
|
||||
async (draft: DraftNostrEvent) => {
|
||||
try {
|
||||
return await signingService.requestSignature(draft);
|
||||
if (!current) throw new Error("no account");
|
||||
return await signingService.requestSignature(draft, current);
|
||||
} catch (e) {
|
||||
if (e instanceof Error) {
|
||||
toast({
|
||||
|
@ -5,7 +5,8 @@ export type Account = {
|
||||
pubkey: string;
|
||||
readonly?: boolean;
|
||||
relays?: string[];
|
||||
secKey?: string;
|
||||
secKey?: ArrayBuffer;
|
||||
iv?: Uint8Array;
|
||||
useExtension?: boolean;
|
||||
};
|
||||
|
||||
|
@ -75,7 +75,9 @@ async function savePending() {
|
||||
if (!draft) return;
|
||||
|
||||
savingDraft.next(true);
|
||||
const event = await signingService.requestSignature(draft);
|
||||
const current = accountService.current.value;
|
||||
if (!current) throw new Error("no account");
|
||||
const event = await signingService.requestSignature(draft, current);
|
||||
|
||||
const results = nostrPostAction(clientRelaysService.getWriteUrls(), event);
|
||||
await results.onComplete;
|
||||
|
@ -78,7 +78,9 @@ class ClientRelayService {
|
||||
const oldRelayUrls = this.relays.value.filter((r) => r.mode & RelayMode.WRITE).map((r) => r.url);
|
||||
const writeUrls = unique([...oldRelayUrls, ...newRelayUrls]);
|
||||
|
||||
const event = await signingService.requestSignature(draft);
|
||||
const current = accountService.current.value;
|
||||
if (!current) throw new Error("no account");
|
||||
const event = await signingService.requestSignature(draft, current);
|
||||
|
||||
const results = nostrPostAction(writeUrls, event);
|
||||
await results.onComplete;
|
||||
|
@ -1,11 +1,70 @@
|
||||
import { DraftNostrEvent, NostrEvent } from "../types/nostr-event";
|
||||
import accountService from "./account";
|
||||
import { Account } from "./account";
|
||||
import { signEvent, getEventHash, getPublicKey } from "nostr-tools";
|
||||
import db from "./db";
|
||||
|
||||
class SigningService {
|
||||
async requestSignature(draft: DraftNostrEvent) {
|
||||
const account = accountService.current.value;
|
||||
private async getSalt() {
|
||||
let salt = await db.get("settings", "salt");
|
||||
if (salt) {
|
||||
return salt as Uint8Array;
|
||||
} else {
|
||||
const newSalt = window.crypto.getRandomValues(new Uint8Array(16));
|
||||
await db.put("settings", newSalt, "salt");
|
||||
return newSalt;
|
||||
}
|
||||
}
|
||||
|
||||
private async getKeyMaterial() {
|
||||
const password = window.prompt("Enter local encryption password");
|
||||
if (!password) throw new Error("password required");
|
||||
const enc = new TextEncoder();
|
||||
return window.crypto.subtle.importKey("raw", enc.encode(password), "PBKDF2", false, ["deriveBits", "deriveKey"]);
|
||||
}
|
||||
private async getEncryptionKey() {
|
||||
const salt = await this.getSalt();
|
||||
const keyMaterial = await this.getKeyMaterial();
|
||||
return await window.crypto.subtle.deriveKey(
|
||||
{
|
||||
name: "PBKDF2",
|
||||
salt,
|
||||
iterations: 100000,
|
||||
hash: "SHA-256",
|
||||
},
|
||||
keyMaterial,
|
||||
{ name: "AES-GCM", length: 256 },
|
||||
true,
|
||||
["encrypt", "decrypt"]
|
||||
);
|
||||
}
|
||||
|
||||
async encryptSecKey(secKey: string) {
|
||||
const key = await this.getEncryptionKey();
|
||||
const encode = new TextEncoder();
|
||||
const iv = window.crypto.getRandomValues(new Uint8Array(96));
|
||||
|
||||
const encrypted = await window.crypto.subtle.encrypt({ name: "AES-GCM", iv }, key, encode.encode(secKey));
|
||||
|
||||
return {
|
||||
secKey: encrypted,
|
||||
iv,
|
||||
};
|
||||
}
|
||||
|
||||
async decryptSecKey(account: Account) {
|
||||
if (!account.secKey) throw new Error("account dose not have a secret key");
|
||||
const key = await this.getEncryptionKey();
|
||||
const decode = new TextDecoder();
|
||||
|
||||
try {
|
||||
const decrypted = await window.crypto.subtle.decrypt({ name: "AES-GCM", iv: account.iv }, key, account.secKey);
|
||||
return decode.decode(decrypted);
|
||||
} catch (e) {
|
||||
throw new Error("failed to decrypt secret key");
|
||||
}
|
||||
}
|
||||
|
||||
async requestSignature(draft: DraftNostrEvent, account: Account) {
|
||||
if (account?.readonly) throw new Error("cant sign in readonly mode");
|
||||
if (account?.useExtension) {
|
||||
if (window.nostr) {
|
||||
@ -14,8 +73,9 @@ class SigningService {
|
||||
return signed;
|
||||
} else throw new Error("missing nostr extension");
|
||||
} else if (account?.secKey) {
|
||||
const tmpDraft = { ...draft, pubkey: getPublicKey(account.secKey) };
|
||||
const signature = signEvent(tmpDraft, account.secKey);
|
||||
const secKey = await this.decryptSecKey(account);
|
||||
const tmpDraft = { ...draft, pubkey: getPublicKey(secKey) };
|
||||
const signature = signEvent(tmpDraft, secKey);
|
||||
const event: NostrEvent = {
|
||||
...tmpDraft,
|
||||
id: getEventHash(tmpDraft),
|
||||
|
@ -22,6 +22,7 @@ import { Bech32Prefix, normalizeToBech32, normalizeToHex } from "../../helpers/n
|
||||
import accountService from "../../services/account";
|
||||
import clientRelaysService from "../../services/client-relays";
|
||||
import { generatePrivateKey, getPublicKey } from "nostr-tools";
|
||||
import signingService from "../../services/signing";
|
||||
|
||||
export const LoginNsecView = () => {
|
||||
const navigate = useNavigate();
|
||||
@ -66,13 +67,14 @@ export const LoginNsecView = () => {
|
||||
[setInputValue, setHexKey, setNpub, setError]
|
||||
);
|
||||
|
||||
const handleSubmit: React.FormEventHandler<HTMLDivElement> = (e) => {
|
||||
const handleSubmit: React.FormEventHandler<HTMLDivElement> = async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
if (!hexKey) return;
|
||||
const pubkey = getPublicKey(hexKey);
|
||||
|
||||
accountService.addAccount({ pubkey, relays: [relayUrl], secKey: hexKey });
|
||||
const encrypted = await signingService.encryptSecKey(hexKey);
|
||||
accountService.addAccount({ pubkey, relays: [relayUrl], ...encrypted });
|
||||
clientRelaysService.bootstrapRelays.add(relayUrl);
|
||||
accountService.switchAccount(pubkey);
|
||||
};
|
||||
|
Loading…
x
Reference in New Issue
Block a user