From 006971409c525c3cf8df133ce9cc31b6b116a047 Mon Sep 17 00:00:00 2001 From: hzrd149 Date: Fri, 2 Feb 2024 10:27:51 +0000 Subject: [PATCH] finish Auth0 signup flow --- .changeset/popular-tools-breathe.md | 5 +++ src/helpers/nostr/filter.ts | 3 +- src/helpers/regexp.ts | 1 + src/helpers/relay.ts | 4 +- src/services/account.ts | 10 +++++ src/services/nostr-connect.ts | 19 +++++++-- src/views/signin/address/create.tsx | 27 ++++++------- src/views/signin/address/index.tsx | 60 ++++++++++++----------------- src/views/signin/nostr-connect.tsx | 8 +--- 9 files changed, 72 insertions(+), 65 deletions(-) create mode 100644 .changeset/popular-tools-breathe.md diff --git a/.changeset/popular-tools-breathe.md b/.changeset/popular-tools-breathe.md new file mode 100644 index 000000000..5480ee3d5 --- /dev/null +++ b/.changeset/popular-tools-breathe.md @@ -0,0 +1,5 @@ +--- +"nostrudel": minor +--- + +Add support for OAuth signup flow diff --git a/src/helpers/nostr/filter.ts b/src/helpers/nostr/filter.ts index a3710bf59..97277b99a 100644 --- a/src/helpers/nostr/filter.ts +++ b/src/helpers/nostr/filter.ts @@ -1,6 +1,7 @@ import stringify from "json-stringify-deterministic"; import { NostrRequestFilter, RelayQueryMap } from "../../types/nostr-query"; import { Filter } from "nostr-tools"; +import { safeRelayUrls } from "../relay"; export function addQueryToFilter(filter: NostrRequestFilter, query: Filter) { if (Array.isArray(filter)) { @@ -28,6 +29,6 @@ export function mapQueryMap(queryMap: RelayQueryMap, fn: (filter: NostrRequestFi export function createSimpleQueryMap(relays: Iterable, filter: NostrRequestFilter) { const map: RelayQueryMap = {}; - for (const relay of relays) map[relay] = filter; + for (const relay of safeRelayUrls(relays)) map[relay] = filter; return map; } diff --git a/src/helpers/regexp.ts b/src/helpers/regexp.ts index 18a782632..ca9eee6af 100644 --- a/src/helpers/regexp.ts +++ b/src/helpers/regexp.ts @@ -5,6 +5,7 @@ export const getMatchLink = () => /https?:\/\/([a-zA-Z0-9\.\-]+\.[a-zA-Z]+)([\p{L}\p{N}\p{M}&\.-\/\?=#\-@%\+_,:!~*]*)/gu; export const getMatchEmoji = () => /:([a-zA-Z0-9_-]+):/gi; export const getMatchCashu = () => /(cashuA[A-Za-z0-9_-]{0,10000}={0,3})/gi; +export const getMatchSimpleEmail = () => /^[^\s]{1,64}@[^\s]+\.[^\s]{2,}$/; // read more https://www.regular-expressions.info/unicode.html#category export function stripInvisibleChar(str?: string) { diff --git a/src/helpers/relay.ts b/src/helpers/relay.ts index 5286f5579..43d70af21 100644 --- a/src/helpers/relay.ts +++ b/src/helpers/relay.ts @@ -53,8 +53,8 @@ export function safeRelayUrl(relayUrl: string | URL) { return null; } } -export function safeRelayUrls(urls: string[]): string[] { - return urls.map(safeRelayUrl).filter(Boolean) as string[]; +export function safeRelayUrls(urls: Iterable): string[] { + return Array.from(urls).map(safeRelayUrl).filter(Boolean) as string[]; } export function splitNostrFilterByPubkeys( diff --git a/src/services/account.ts b/src/services/account.ts index 5aaa516d9..45e6a83b6 100644 --- a/src/services/account.ts +++ b/src/services/account.ts @@ -1,5 +1,6 @@ import { PersistentSubject } from "../classes/subject"; import db from "./db"; +import { NostrConnectClient } from "./nostr-connect"; import { AppSettings } from "./settings/migrations"; type CommonAccount = { @@ -102,6 +103,15 @@ class AccountService { db.put("accounts", account); } + addFromNostrConnect(client: NostrConnectClient) { + this.addAccount({ + type: "nostr-connect", + signerRelays: client.relays, + clientSecretKey: client.secretKey, + pubkey: client.pubkey, + readonly: false, + }); + } removeAccount(pubkey: string) { this.accounts.next(this.accounts.value.filter((acc) => acc.pubkey !== pubkey)); diff --git a/src/services/nostr-connect.ts b/src/services/nostr-connect.ts index d70d1b2fc..1cfc16fa3 100644 --- a/src/services/nostr-connect.ts +++ b/src/services/nostr-connect.ts @@ -69,8 +69,6 @@ export class NostrConnectClient { secretKey: string; publicKey: string; - onAuthURL = new Subject(undefined, false); - supportedMethods: NostrConnectMethod[] | undefined; constructor(pubkey: string, relays: string[], secretKey?: string, provider?: string) { @@ -103,6 +101,14 @@ export class NostrConnectClient { this.sub.close(); } + handleAuthURL(url: string) { + const popup = window.open( + url, + "auth", + "width=400,height=600,resizable=no,status=no,location=no,toolbar=no,menubar=no", + ); + } + private requests = new Map>(); async handleEvent(event: NostrEvent) { if (this.provider && event.pubkey !== this.provider) return; @@ -118,8 +124,13 @@ export class NostrConnectClient { const p = this.requests.get(response.id); if (!p) return; if (response.error) { - if (response.result === "auth_url") this.onAuthURL.next(response.error); - else p.reject(response); + if (response.result === "auth_url") { + try { + await this.handleAuthURL(response.error); + } catch (e) { + p.reject(e); + } + } else p.reject(response); } else if (response.result) { this.log(response.id, response.result); p.resolve(response.result); diff --git a/src/views/signin/address/create.tsx b/src/views/signin/address/create.tsx index c826d7c9c..c649a0f13 100644 --- a/src/views/signin/address/create.tsx +++ b/src/views/signin/address/create.tsx @@ -27,6 +27,7 @@ import dnsIdentityService from "../../../services/dns-identity"; import { useUserMetadata } from "../../../hooks/use-user-metadata"; import nostrConnectService from "../../../services/nostr-connect"; import accountService from "../../../services/account"; +import { safeRelayUrls } from "../../../helpers/relay"; function ProviderCard({ onClick, provider }: { onClick: () => void; provider: NostrEvent }) { const metadata = JSON.parse(provider.content) as Kind0ParsedContent; @@ -66,7 +67,7 @@ export default function LoginNostrAddressCreate() { const [name, setName] = useState(""); const providers = useNip05Providers(); const [selected, setSelected] = useState(); - const metadata = useUserMetadata(selected?.pubkey); + const userMetadata = useUserMetadata(selected?.pubkey); const createAccount: React.FormEventHandler = async (e) => { e.preventDefault(); @@ -74,31 +75,25 @@ export default function LoginNostrAddressCreate() { try { setLoading("Creating..."); - if (!metadata) throw new Error("Cant verify provider"); + const providerMetadata = JSON.parse(selected.content) as Kind0ParsedContent; + const metadata: Kind0ParsedContent = { ...userMetadata, ...providerMetadata }; if (!metadata.nip05) throw new Error("Provider missing nip05 address"); const nip05 = await dnsIdentityService.fetchIdentity(metadata.nip05); if (!nip05 || nip05.pubkey !== selected.pubkey) throw new Error("Invalid provider"); if (nip05.name !== "_") throw new Error("Provider dose not own the domain"); if (!nip05.hasNip46) throw new Error("Provider dose not support NIP-46"); + const relays = safeRelayUrls(nip05.nip46Relays || nip05.relays); + if (relays.length === 0) throw new Error("Cant find providers relays"); - const client = nostrConnectService.createClient("", nip05.nip46Relays || nip05.relays, undefined, nip05.pubkey); - client.onAuthURL.subscribe((url) => { - window.open(url, "auth", "width=400,height=600,resizable=no,status=no,location=no,toolbar=no,menubar=no"); - }); - - const newPubkey = await client.createAccount(name, nip05.domain); + const client = nostrConnectService.createClient("", relays, undefined, nip05.pubkey); + const createPromise = client.createAccount(name, nip05.domain); + await createPromise; await client.connect(); nostrConnectService.saveClient(client); - accountService.addAccount({ - type: "nostr-connect", - signerRelays: client.relays, - clientSecretKey: client.secretKey, - pubkey: client.pubkey, - readonly: false, - }); - accountService.switchAccount(client.pubkey!); + accountService.addFromNostrConnect(client); + accountService.switchAccount(client.pubkey); } catch (e) { if (e instanceof Error) toast({ description: e.message, status: "error" }); } diff --git a/src/views/signin/address/index.tsx b/src/views/signin/address/index.tsx index 60bf65e67..651d3960f 100644 --- a/src/views/signin/address/index.tsx +++ b/src/views/signin/address/index.tsx @@ -9,29 +9,27 @@ import nostrConnectService from "../../../services/nostr-connect"; import accountService from "../../../services/account"; import { COMMON_CONTACT_RELAY } from "../../../const"; import { safeRelayUrls } from "../../../helpers/relay"; +import { getMatchSimpleEmail } from "../../../helpers/regexp"; export default function LoginNostrAddressView() { const navigate = useNavigate(); const toast = useToast(); - const [provider, setProvider] = useState(""); const [address, setAddress] = useState(""); - const userSpecifiedDomain = address.includes("@"); - const fullAddress = userSpecifiedDomain ? address || undefined : provider ? address + "@" + provider : undefined; - - const [rootNip05, setRootNip05] = useState(); - const [nip05, setNip05] = useState(); + const [nip05, setNip05] = useState(); + const [rootNip05, setRootNip05] = useState(); useDebounce( async () => { - if (!fullAddress) return setNip05(undefined); - let [name, domain] = fullAddress.split("@"); - if (!name || !domain || !domain.includes(".")) return setNip05(undefined); - setNip05(await dnsIdentityService.fetchIdentity(fullAddress)); - setRootNip05(await dnsIdentityService.fetchIdentity(`_@${domain}`)); + if (!address) return setNip05(undefined); + if (!getMatchSimpleEmail().test(address)) return setNip05(undefined); + let [name, domain] = address.split("@"); + if (!name || !domain) return setNip05(undefined); + setNip05((await dnsIdentityService.fetchIdentity(address)) ?? null); + setRootNip05((await dnsIdentityService.fetchIdentity(`_@${domain}`)) ?? null); }, 300, - [fullAddress], + [address], ); const [loading, setLoading] = useState(); @@ -44,20 +42,11 @@ export default function LoginNostrAddressView() { setLoading("Connecting..."); const relays = safeRelayUrls(nip05.nip46Relays || rootNip05?.nip46Relays || rootNip05?.relays || nip05.relays); const client = nostrConnectService.fromHostedBunker(nip05.pubkey, relays, rootNip05?.pubkey); - client.onAuthURL.subscribe((url) => { - window.open(url, "auth", "width=400,height=600,resizable=no,status=no,location=no,toolbar=no,menubar=no"); - }); await client.connect(); nostrConnectService.saveClient(client); - accountService.addAccount({ - type: "nostr-connect", - signerRelays: client.relays, - clientSecretKey: client.secretKey, - pubkey: client.pubkey!, - readonly: false, - }); - accountService.switchAccount(client.pubkey!); + accountService.addFromNostrConnect(client); + accountService.switchAccount(client.pubkey); } else { accountService.addAccount({ type: "pubkey", @@ -90,7 +79,7 @@ export default function LoginNostrAddressView() { return ( - {fullAddress} + {address} Found provider @@ -99,22 +88,24 @@ export default function LoginNostrAddressView() { } else return ( - {fullAddress} + {address} Read-only ); - } else { + } else if (nip05 === null) { return ( - {fullAddress} + {address} Cant find identity ); } + + return null; }; return ( @@ -138,19 +129,18 @@ export default function LoginNostrAddressView() { {renderStatus()} )} - - - {nip05 ? ( - - ) : ( - )} + ); diff --git a/src/views/signin/nostr-connect.tsx b/src/views/signin/nostr-connect.tsx index 02fd158c0..5cc5246f0 100644 --- a/src/views/signin/nostr-connect.tsx +++ b/src/views/signin/nostr-connect.tsx @@ -29,13 +29,7 @@ export default function LoginNostrConnectView() { } else throw new Error("Unknown format"); nostrConnectService.saveClient(client); - accountService.addAccount({ - type: "nostr-connect", - signerRelays: client.relays, - clientSecretKey: client.secretKey, - pubkey: client.pubkey, - readonly: false, - }); + accountService.addFromNostrConnect(client); accountService.switchAccount(client.pubkey); } catch (e) { if (e instanceof Error) toast({ status: "error", description: e.message });