finish Auth0 signup flow

This commit is contained in:
hzrd149 2024-02-02 10:27:51 +00:00
parent e8e3dc0fac
commit 006971409c
9 changed files with 72 additions and 65 deletions

View File

@ -0,0 +1,5 @@
---
"nostrudel": minor
---
Add support for OAuth signup flow

View File

@ -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<string>, filter: NostrRequestFilter) {
const map: RelayQueryMap = {};
for (const relay of relays) map[relay] = filter;
for (const relay of safeRelayUrls(relays)) map[relay] = filter;
return map;
}

View File

@ -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) {

View File

@ -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>): string[] {
return Array.from(urls).map(safeRelayUrl).filter(Boolean) as string[];
}
export function splitNostrFilterByPubkeys(

View File

@ -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));

View File

@ -69,8 +69,6 @@ export class NostrConnectClient {
secretKey: string;
publicKey: string;
onAuthURL = new Subject<string>(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<string, Deferred<any>>();
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);

View File

@ -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<NostrEvent>();
const metadata = useUserMetadata(selected?.pubkey);
const userMetadata = useUserMetadata(selected?.pubkey);
const createAccount: React.FormEventHandler<HTMLDivElement> = 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" });
}

View File

@ -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<DnsIdentity>();
const [nip05, setNip05] = useState<DnsIdentity>();
const [nip05, setNip05] = useState<DnsIdentity | null>();
const [rootNip05, setRootNip05] = useState<DnsIdentity | null>();
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<string | undefined>();
@ -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 (
<Card {...cardProps}>
<Image w="7" h="7" src={`//${nip05.domain}/favicon.ico`} />
<Text fontWeight="bold">{fullAddress}</Text>
<Text fontWeight="bold">{address}</Text>
<Text color="green.500" ml="auto">
Found provider <CheckIcon boxSize={5} />
</Text>
@ -99,22 +88,24 @@ export default function LoginNostrAddressView() {
} else
return (
<Card {...cardProps}>
<Text fontWeight="bold">{fullAddress}</Text>
<Text fontWeight="bold">{address}</Text>
<Text color="yellow.500" ml="auto">
Read-only
</Text>
</Card>
);
} else {
} else if (nip05 === null) {
return (
<Card {...cardProps}>
<Text fontWeight="bold">{fullAddress}</Text>
<Text fontWeight="bold">{address}</Text>
<Text color="red.500" ml="auto">
Cant find identity
</Text>
</Card>
);
}
return null;
};
return (
@ -138,19 +129,18 @@ export default function LoginNostrAddressView() {
{renderStatus()}
</>
)}
<Flex justifyContent="space-between" gap="2" mt="2">
<Button variant="link" onClick={() => navigate("../")}>
<Flex gap="2" mt="2">
<Button variant="link" onClick={() => navigate("../")} mr="auto">
Back
</Button>
{nip05 ? (
<Button colorScheme="primary" ml="auto" type="submit" isLoading={!!loading} isDisabled={!nip05}>
Connect
</Button>
) : (
<Button colorScheme="primary" ml="auto" as={RouterLink} to="/signin/address/create">
{!loading && (
<Button colorScheme="primary" as={RouterLink} to="/signin/address/create" variant="link" p="2">
Find Provider
</Button>
)}
<Button colorScheme="primary" type="submit" isLoading={!!loading} isDisabled={!nip05}>
Connect
</Button>
</Flex>
</Flex>
);

View File

@ -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 });