2023-02-09 18:05:45 +00:00
import "./Login.css" ;
import { CSSProperties , useEffect , useState } from "react" ;
2022-12-27 23:46:13 +00:00
import { useDispatch , useSelector } from "react-redux" ;
2022-12-29 22:23:41 +00:00
import { useNavigate } from "react-router-dom" ;
2023-02-07 20:04:50 +00:00
import * as secp from "@noble/secp256k1" ;
2023-02-12 12:31:48 +00:00
import { useIntl , FormattedMessage } from "react-intl" ;
2022-12-29 22:23:41 +00:00
2023-01-20 11:11:50 +00:00
import { RootState } from "State/Store" ;
2023-02-09 12:26:54 +00:00
import { setPrivateKey , setPublicKey , setRelays , setGeneratedPrivateKey } from "State/Login" ;
2023-02-01 11:47:05 +00:00
import { DefaultRelays , EmailRegex } from "Const" ;
2023-02-09 18:05:45 +00:00
import { bech32ToHex , unwrap } from "Util" ;
2023-02-11 20:05:46 +00:00
import { HexKey } from "@snort/nostr" ;
2023-02-09 18:05:45 +00:00
import ZapButton from "Element/ZapButton" ;
2023-02-12 22:06:21 +00:00
// import useImgProxy from "Feed/ImgProxy";
2023-02-09 18:05:45 +00:00
2023-02-12 12:31:48 +00:00
import messages from "./messages" ;
2023-02-09 18:05:45 +00:00
interface ArtworkEntry {
name : string ;
pubkey : HexKey ;
link : string ;
}
// todo: fill more
const Artwork : Array < ArtworkEntry > = [
{
name : "" ,
pubkey : bech32ToHex ( "npub1r0rs5q2gk0e3dk3nlc7gnu378ec6cnlenqp8a3cjhyzu6f8k5sgs4sq9ac" ) ,
2023-02-09 18:49:12 +00:00
link : "https://void.cat/d/VKhPayp9ekeXYZGzAL9CxP" ,
2023-02-09 18:05:45 +00:00
} ,
{
name : "" ,
pubkey : bech32ToHex ( "npub1r0rs5q2gk0e3dk3nlc7gnu378ec6cnlenqp8a3cjhyzu6f8k5sgs4sq9ac" ) ,
2023-02-09 18:49:12 +00:00
link : "https://void.cat/d/3H2h8xxc3aEN6EVeobd8tw" ,
2023-02-09 18:05:45 +00:00
} ,
{
name : "" ,
pubkey : bech32ToHex ( "npub1r0rs5q2gk0e3dk3nlc7gnu378ec6cnlenqp8a3cjhyzu6f8k5sgs4sq9ac" ) ,
2023-02-09 18:49:12 +00:00
link : "https://void.cat/d/7i9W9PXn3TV86C4RUefNC9" ,
2023-02-09 18:05:45 +00:00
} ,
2023-02-09 18:49:12 +00:00
{
name : "" ,
pubkey : bech32ToHex ( "npub1r0rs5q2gk0e3dk3nlc7gnu378ec6cnlenqp8a3cjhyzu6f8k5sgs4sq9ac" ) ,
2023-02-09 18:51:50 +00:00
link : "https://void.cat/d/KtoX4ei6RYHY7HESg3Ve3k" ,
} ,
2023-02-09 18:05:45 +00:00
] ;
2022-12-27 23:46:13 +00:00
2022-12-18 14:51:47 +00:00
export default function LoginPage() {
2023-02-07 20:04:50 +00:00
const dispatch = useDispatch ( ) ;
const navigate = useNavigate ( ) ;
2023-02-09 12:26:54 +00:00
const publicKey = useSelector < RootState , HexKey | undefined > ( s = > s . login . publicKey ) ;
2023-02-07 20:04:50 +00:00
const [ key , setKey ] = useState ( "" ) ;
const [ error , setError ] = useState ( "" ) ;
2023-02-09 18:05:45 +00:00
const [ art , setArt ] = useState < ArtworkEntry > ( ) ;
2023-02-12 12:31:48 +00:00
const { formatMessage } = useIntl ( ) ;
//const { proxy } = useImgProxy();
2022-12-27 23:46:13 +00:00
2023-02-07 20:04:50 +00:00
useEffect ( ( ) = > {
if ( publicKey ) {
navigate ( "/" ) ;
2023-01-02 23:36:30 +00:00
}
2023-02-07 20:04:50 +00:00
} , [ publicKey , navigate ] ) ;
2023-01-02 23:36:30 +00:00
2023-02-09 18:05:45 +00:00
useEffect ( ( ) = > {
const ret = unwrap ( Artwork . at ( Artwork . length * Math . random ( ) ) ) ;
2023-02-10 10:54:51 +00:00
// disable for now because imgproxy is ded
// proxy(ret.link).then(a => setArt({ ...ret, link: a }));
setArt ( ret ) ;
2023-02-09 18:05:45 +00:00
} , [ ] ) ;
2023-02-07 20:04:50 +00:00
async function getNip05PubKey ( addr : string ) {
2023-02-07 19:47:57 +00:00
const [ username , domain ] = addr . split ( "@" ) ;
2023-02-09 12:26:54 +00:00
const rsp = await fetch ( ` https:// ${ domain } /.well-known/nostr.json?name= ${ encodeURIComponent ( username ) } ` ) ;
2023-02-07 20:04:50 +00:00
if ( rsp . ok ) {
2023-02-07 19:47:57 +00:00
const data = await rsp . json ( ) ;
const pKey = data . names [ username ] ;
2023-02-07 20:04:50 +00:00
if ( pKey ) {
return pKey ;
}
}
throw new Error ( "User key not found" ) ;
}
2023-01-02 23:36:30 +00:00
2023-02-07 20:04:50 +00:00
async function doLogin() {
try {
if ( key . startsWith ( "nsec" ) ) {
2023-02-07 19:47:57 +00:00
const hexKey = bech32ToHex ( key ) ;
2023-02-07 20:04:50 +00:00
if ( secp . utils . isValidPrivateKey ( hexKey ) ) {
dispatch ( setPrivateKey ( hexKey ) ) ;
} else {
throw new Error ( "INVALID PRIVATE KEY" ) ;
}
} else if ( key . startsWith ( "npub" ) ) {
2023-02-07 19:47:57 +00:00
const hexKey = bech32ToHex ( key ) ;
2023-02-07 20:04:50 +00:00
dispatch ( setPublicKey ( hexKey ) ) ;
} else if ( key . match ( EmailRegex ) ) {
2023-02-07 19:47:57 +00:00
const hexKey = await getNip05PubKey ( key ) ;
2023-02-07 20:04:50 +00:00
dispatch ( setPublicKey ( hexKey ) ) ;
} else {
if ( secp . utils . isValidPrivateKey ( key ) ) {
dispatch ( setPrivateKey ( key ) ) ;
} else {
throw new Error ( "INVALID PRIVATE KEY" ) ;
2022-12-27 23:46:13 +00:00
}
2023-02-07 20:04:50 +00:00
}
} catch ( e ) {
setError ( ` Failed to load NIP-05 pub key ( ${ e } ) ` ) ;
console . error ( e ) ;
2022-12-27 23:46:13 +00:00
}
2023-02-07 20:04:50 +00:00
}
2022-12-27 23:46:13 +00:00
2023-02-07 20:04:50 +00:00
async function makeRandomKey() {
2023-02-07 19:47:57 +00:00
const newKey = secp . utils . bytesToHex ( secp . utils . randomPrivateKey ( ) ) ;
2023-02-07 20:04:50 +00:00
dispatch ( setGeneratedPrivateKey ( newKey ) ) ;
navigate ( "/new" ) ;
}
2023-01-01 10:44:38 +00:00
2023-02-07 20:04:50 +00:00
async function doNip07Login() {
2023-02-07 19:47:57 +00:00
const pubKey = await window . nostr . getPublicKey ( ) ;
2023-02-07 20:04:50 +00:00
dispatch ( setPublicKey ( pubKey ) ) ;
2023-01-26 22:16:35 +00:00
2023-02-07 20:04:50 +00:00
if ( "getRelays" in window . nostr ) {
2023-02-07 19:47:57 +00:00
const relays = await window . nostr . getRelays ( ) ;
2023-02-07 20:04:50 +00:00
dispatch (
setRelays ( {
relays : {
. . . relays ,
. . . Object . fromEntries ( DefaultRelays . entries ( ) ) ,
} ,
createdAt : 1 ,
} )
) ;
2022-12-29 10:51:32 +00:00
}
2023-02-07 20:04:50 +00:00
}
2022-12-29 10:51:32 +00:00
2023-02-07 20:04:50 +00:00
function altLogins() {
2023-02-07 19:47:57 +00:00
const nip07 = "nostr" in window ;
2023-02-07 20:04:50 +00:00
if ( ! nip07 ) {
return null ;
2022-12-29 10:51:32 +00:00
}
2022-12-18 14:51:47 +00:00
return (
2023-02-09 18:05:45 +00:00
< button type = "button" onClick = { doNip07Login } >
2023-02-10 10:40:16 +00:00
< FormattedMessage
defaultMessage = "Login with Extension (NIP-07)"
description = "Login button for NIP7 key manager extension"
/ >
2023-02-09 18:05:45 +00:00
< / button >
2022-12-18 14:51:47 +00:00
) ;
2023-02-07 20:04:50 +00:00
}
return (
2023-02-09 18:05:45 +00:00
< div className = "login" >
< div >
< div className = "login-container" >
< div className = "logo" onClick = { ( ) = > navigate ( "/" ) } >
Snort
< / div >
2023-02-13 16:57:51 +00:00
< h1 dir = "auto" >
2023-02-09 18:05:45 +00:00
< FormattedMessage defaultMessage = "Login" description = "Login header" / >
< / h1 >
2023-02-13 16:57:51 +00:00
< p dir = "auto" >
2023-02-09 18:05:45 +00:00
< FormattedMessage defaultMessage = "Your key" description = "Label for key input" / >
< / p >
2023-02-12 12:31:48 +00:00
< div className = "flex" >
2023-02-09 18:05:45 +00:00
< input
2023-02-13 16:57:51 +00:00
dir = "auto"
2023-02-09 18:05:45 +00:00
type = "text"
2023-02-12 12:31:48 +00:00
placeholder = { formatMessage ( messages . KeyPlaceholder ) }
2023-02-09 18:05:45 +00:00
className = "f-grow"
onChange = { e = > setKey ( e . target . value ) }
/ >
< / div >
{ error . length > 0 ? < b className = "error" > { error } < / b > : null }
2023-02-12 12:31:48 +00:00
< p className = "login-note" >
2023-02-09 18:05:45 +00:00
< FormattedMessage
defaultMessage = "Only the secret key can be used to publish (sign events), everything else logs you in read-only mode."
description = "Explanation for public key only login is read-only"
/ >
< / p >
2023-02-10 10:40:16 +00:00
{ / * < a h r e f = " " >
2023-02-09 18:05:45 +00:00
< FormattedMessage
defaultMessage = "Why is there no password field?"
description = "Link to why your private key is your password"
/ >
2023-02-10 10:40:16 +00:00
< / a > * / }
2023-02-13 16:57:51 +00:00
< div dir = "auto" className = "login-actions" >
2023-02-09 18:05:45 +00:00
< button type = "button" onClick = { doLogin } >
< FormattedMessage defaultMessage = "Login" description = "Login button" / >
< / button >
{ altLogins ( ) }
< / div >
2023-02-12 12:31:48 +00:00
< div className = "flex login-or" >
< FormattedMessage defaultMessage = "OR" description = "Seperator text for Login / Generate Key" / >
2023-02-09 18:05:45 +00:00
< div className = "divider w-max" > < / div >
< / div >
2023-02-13 16:57:51 +00:00
< h1 dir = "auto" >
2023-02-09 18:05:45 +00:00
< FormattedMessage defaultMessage = "Create an Account" description = "Heading for generate key flow" / >
< / h1 >
2023-02-12 12:31:48 +00:00
< p >
2023-02-09 18:05:45 +00:00
< FormattedMessage
defaultMessage = "Generate a public / private key pair. Do not share your private key with anyone, this acts as your password. Once lost, it cannot be “reset” or recovered. Keep safe!"
description = "Note about key security before generating a new key"
/ >
< / p >
2023-02-12 12:31:48 +00:00
< div className = "login-actions" >
2023-02-09 18:05:45 +00:00
< button type = "button" onClick = { ( ) = > makeRandomKey ( ) } >
< FormattedMessage defaultMessage = "Generate Key" description = "Button: Generate a new key" / >
< / button >
< / div >
< / div >
2023-02-07 20:04:50 +00:00
< / div >
2023-02-09 18:05:45 +00:00
< div >
2023-02-12 12:31:48 +00:00
< div className = "artwork" style = { { [ "--img-src" ] : ` url(' ${ art ? . link } ') ` } as CSSProperties } >
< div className = "attribution" >
2023-02-09 18:05:45 +00:00
< FormattedMessage
defaultMessage = "Art by {name}"
description = "Artwork attribution label"
values = { {
2023-02-12 12:31:48 +00:00
name : < span className = "artist" > Karnage < / span > ,
2023-02-09 18:05:45 +00:00
} }
/ >
< ZapButton pubkey = { art ? . pubkey ? ? "" } / >
< / div >
< / div >
2023-02-07 20:04:50 +00:00
< / div >
< / div >
) ;
2023-01-25 18:08:53 +00:00
}