login with email
This commit is contained in:
parent
06fc618def
commit
9ef6086e76
@ -12,6 +12,7 @@ import { Label } from "@/components/ui/label";
|
|||||||
import AddPassphrase from "./AddPassphrase";
|
import AddPassphrase from "./AddPassphrase";
|
||||||
import { decryptMessage } from "@/lib/nostr";
|
import { decryptMessage } from "@/lib/nostr";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
|
import { generateSchnorrKeyPair } from "@/lib/keys";
|
||||||
|
|
||||||
export default function LoginModal() {
|
export default function LoginModal() {
|
||||||
const { loginWithNip07, loginWithSecret } = useNDK();
|
const { loginWithNip07, loginWithSecret } = useNDK();
|
||||||
@ -19,8 +20,10 @@ export default function LoginModal() {
|
|||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const [showExtensionLogin, setShowExtensionLogin] = useState(true);
|
const [showExtensionLogin, setShowExtensionLogin] = useState(true);
|
||||||
const [showPassphraseLogin, setShowPassphraseLogin] = useState(false);
|
const [showPassphraseLogin, setShowPassphraseLogin] = useState(false);
|
||||||
|
const [showEmailLogin, setShowEmailLogin] = useState(false);
|
||||||
const [nsec, setNsec] = useState("");
|
const [nsec, setNsec] = useState("");
|
||||||
const [passphrase, setPassphrase] = useState("");
|
const [passphrase, setPassphrase] = useState("");
|
||||||
|
const [email, setEmail] = useState("");
|
||||||
const [encryptedNsec, setEncryptedNsec] = useState("");
|
const [encryptedNsec, setEncryptedNsec] = useState("");
|
||||||
const modal = useModal();
|
const modal = useModal();
|
||||||
|
|
||||||
@ -92,6 +95,25 @@ export default function LoginModal() {
|
|||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
modal?.hide();
|
modal?.hide();
|
||||||
}
|
}
|
||||||
|
async function handleLoginEmail() {
|
||||||
|
if (!email || !passphrase) return;
|
||||||
|
setIsLoading(true);
|
||||||
|
const seedString = email + passphrase;
|
||||||
|
const { privateKey, publicKey } = generateSchnorrKeyPair(seedString);
|
||||||
|
console.log("privateKey", privateKey, "publicKey", publicKey);
|
||||||
|
const emailNsec = nip19.nsecEncode(privateKey);
|
||||||
|
const user = await loginWithSecret(emailNsec);
|
||||||
|
console.log("User", user);
|
||||||
|
if (!user) {
|
||||||
|
throw new Error("NO auth");
|
||||||
|
}
|
||||||
|
await loginWithPubkey(nip19.decode(user.npub).data.toString());
|
||||||
|
if (typeof window.webln !== "undefined") {
|
||||||
|
await window.webln.enable();
|
||||||
|
}
|
||||||
|
setIsLoading(false);
|
||||||
|
modal?.hide();
|
||||||
|
}
|
||||||
async function handleLoginPassphrase() {
|
async function handleLoginPassphrase() {
|
||||||
if (!encryptedNsec || !passphrase) return;
|
if (!encryptedNsec || !passphrase) return;
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
@ -138,7 +160,51 @@ export default function LoginModal() {
|
|||||||
Connect with extension
|
Connect with extension
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
{showPassphraseLogin ? (
|
{showEmailLogin ? (
|
||||||
|
<div className="space-y-3">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label>Email</Label>
|
||||||
|
<Input
|
||||||
|
value={email}
|
||||||
|
onChange={(e) => setEmail(e.target.value)}
|
||||||
|
placeholder="email"
|
||||||
|
type="email"
|
||||||
|
className="text-[16px]"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label>Password</Label>
|
||||||
|
<Input
|
||||||
|
value={passphrase}
|
||||||
|
onChange={(e) => setPassphrase(e.target.value)}
|
||||||
|
placeholder="password..."
|
||||||
|
type="password"
|
||||||
|
className="text-[16px]"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
variant={"outline"}
|
||||||
|
onClick={() => void handleLoginEmail()}
|
||||||
|
loading={isLoading}
|
||||||
|
className="w-full"
|
||||||
|
>
|
||||||
|
Login
|
||||||
|
</Button>
|
||||||
|
<div className="center">
|
||||||
|
<Button
|
||||||
|
variant={"link"}
|
||||||
|
size={"sm"}
|
||||||
|
className="h-0 pt-1"
|
||||||
|
onClick={() => {
|
||||||
|
setShowEmailLogin(false);
|
||||||
|
setShowPassphraseLogin(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Or, use Nsec
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : showPassphraseLogin ? (
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<Label>Passphrase</Label>
|
<Label>Passphrase</Label>
|
||||||
<Input
|
<Input
|
||||||
@ -184,18 +250,27 @@ export default function LoginModal() {
|
|||||||
>
|
>
|
||||||
Connect with Nsec
|
Connect with Nsec
|
||||||
</Button>
|
</Button>
|
||||||
{!!encryptedNsec && (
|
|
||||||
<div className="center">
|
<div className="center text-xs font-medium text-primary">
|
||||||
<Button
|
Or, use
|
||||||
variant={"link"}
|
{!!encryptedNsec && (
|
||||||
size={"sm"}
|
<>
|
||||||
className="h-0 pt-1"
|
<button
|
||||||
onClick={() => setShowPassphraseLogin(true)}
|
className="ml-1 hover:underline"
|
||||||
>
|
onClick={() => setShowPassphraseLogin(true)}
|
||||||
Or, use Passphrase
|
>
|
||||||
</Button>
|
Passphrase
|
||||||
</div>
|
</button>
|
||||||
)}
|
<span className="mx-1">or</span>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
<button
|
||||||
|
className="ml-1 hover:underline"
|
||||||
|
onClick={() => setShowEmailLogin(true)}
|
||||||
|
>
|
||||||
|
Email
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
@ -12,7 +12,7 @@ export const useKeyboardShortcut = (keys: Key[], callback: () => void) => {
|
|||||||
(key === "ctrl" && (event.metaKey || event.ctrlKey)) ||
|
(key === "ctrl" && (event.metaKey || event.ctrlKey)) ||
|
||||||
(key === "shift" && event.shiftKey) ||
|
(key === "shift" && event.shiftKey) ||
|
||||||
(key === "alt" && event.altKey) ||
|
(key === "alt" && event.altKey) ||
|
||||||
(typeof key === "string" && event.key.toLowerCase() === key),
|
(typeof key === "string" && event.key?.toLowerCase() === key),
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
callback();
|
callback();
|
||||||
|
24
lib/keys/index.ts
Normal file
24
lib/keys/index.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import { createHmac } from "crypto";
|
||||||
|
import { ec as EC } from "elliptic";
|
||||||
|
|
||||||
|
export function generateSchnorrKeyPair(seed: string): {
|
||||||
|
privateKey: string;
|
||||||
|
publicKey: string;
|
||||||
|
} {
|
||||||
|
// Use HMAC-SHA256 with your seed as the message and a secret key (salt)
|
||||||
|
const hmac = createHmac("sha256", "secret-salt");
|
||||||
|
hmac.update(seed);
|
||||||
|
const derivedKey = hmac.digest();
|
||||||
|
|
||||||
|
// Create a key pair using the derived private key
|
||||||
|
const ec = new EC("secp256k1");
|
||||||
|
const keyPair = ec.keyFromPrivate(derivedKey);
|
||||||
|
|
||||||
|
// Get the public key in hexadecimal format
|
||||||
|
const publicKey = keyPair.getPublic("hex");
|
||||||
|
|
||||||
|
return {
|
||||||
|
privateKey: derivedKey.toString("hex"),
|
||||||
|
publicKey,
|
||||||
|
};
|
||||||
|
}
|
@ -44,6 +44,7 @@
|
|||||||
"dayjs": "^1.11.10",
|
"dayjs": "^1.11.10",
|
||||||
"dexie": "^3.2.4",
|
"dexie": "^3.2.4",
|
||||||
"dexie-react-hooks": "^1.1.6",
|
"dexie-react-hooks": "^1.1.6",
|
||||||
|
"elliptic": "^6.5.4",
|
||||||
"focus-trap-react": "^10.2.3",
|
"focus-trap-react": "^10.2.3",
|
||||||
"framer-motion": "^10.16.4",
|
"framer-motion": "^10.16.4",
|
||||||
"jotai": "^2.4.3",
|
"jotai": "^2.4.3",
|
||||||
@ -75,6 +76,7 @@
|
|||||||
"@tailwindcss/typography": "^0.5.10",
|
"@tailwindcss/typography": "^0.5.10",
|
||||||
"@total-typescript/ts-reset": "^0.5.1",
|
"@total-typescript/ts-reset": "^0.5.1",
|
||||||
"@types/crypto-js": "^4.1.2",
|
"@types/crypto-js": "^4.1.2",
|
||||||
|
"@types/elliptic": "^6.4.16",
|
||||||
"@types/latlon-geohash": "^2.0.2",
|
"@types/latlon-geohash": "^2.0.2",
|
||||||
"@types/node": "^20",
|
"@types/node": "^20",
|
||||||
"@types/ramda": "^0.29.6",
|
"@types/ramda": "^0.29.6",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user