nostr-dev-kit / ndk

Nostr Development Kit with outbox-model support
MIT License
352 stars 95 forks source link

Trying to help you with debugging AUTH on master branch #246

Open jeremyd opened 3 months ago

jeremyd commented 3 months ago

I see you made some changes to "fix AUTH", but for my example, AUTH is broken both on the release, and on master branch. Here is a rough example code of the basics of what I'm attempting here:

// Top level constants
const nip07signer = new NDKNip07Signer();

const ndk = new NDK({
    signer: nip07signer,
    autoConnectUserRelays: false,
    enableOutboxModel: false,
});

// inside UseEffect
const ndkPool = ndk.pool;
            ndkPool.on("flapping", (flapping: NDKRelay) => {
                addToStatus("relay is flapping: " + flapping.url);
            });
            ndkPool.on("relay:auth", (relay: NDKRelay, challenge: string) => {
                addToStatus("auth: " + relay.url);
            });
            ndkPool.on("relay:authed", (relay: NDKRelay) => {
                addToStatus("authed: " + relay.url);
                const kind1Sub = ndk.subscribe({ kinds: [1], limit: relayLimit }, {closeOnEose: false});
                kind1Sub.on("event", (event: NDKEvent) => {
                    if(lookupProfileName(event.pubkey) == event.pubkey) {
                        const profileSub = ndk.subscribe({ kinds: [0], limit: 1, authors: [event.pubkey] }, {closeOnEose: true});
                        profileSub.on("event", (pevent: NDKEvent) => {
                            addProfile(pevent);
                        });
                    }
                    addPost(event);
                });
            });
            ndkPool.on("relay:disconnect", (relay: NDKRelay) => {
                addToStatus("disconnected: " + relay.url);
            });

            ndkPool.on("relay:connect", (relay: NDKRelay) => {
                addToStatus("connect: " + relay.url);

            });

            ndkPool.on("relay:connecting", (relay: NDKRelay) => {
                addToStatus("connecting: " + relay.url);
            });

            ndk.addExplicitRelay(nrelaydata, NDKRelayAuthPolicies.signIn({ndk}), true);

So, here's what I found:

First of all, just want to mention that the following did NOT work, although it seems like it should have. Having the two different policies (one for the pool and one per relay) may just not apply to explicit relays or etc, I don't know but the if statement to select either or, is not working apparently:

ndk.relayAuthDefaultPolicy = NDKRelayAuthPolicies.signIn({ndk});
ndk.addExplicitRelay(nrelaydata);
await ndk.connect();

However, after some digging I managed to get farther by passing a policy and connect=true to addExplicitRelay.

Second of all, the nitty gritty debug. AUTH was still not working, I could see the auth message come through on the relay but the relay itself inside NDK is stuck in "connect" (ie, it connected then doesn't proceed). Here's why:

On line 82 of connectivity.ts this if statement was returning false when it should be true. res when inspected, is not a bool, it is an object. I naievely hotwired this to if (!!res === true) and this got farther down the code path. At this point, the signed event had already arrived at the relay and should be clear to proceed.

The next error happens on line 83 of connectivity.ts it fails saying "No signer available for authentication". This is not the case, it has already signed the AUTH event and sent it off to the relay at this point so clearly it has a signer. This is NIP07 signer. So I hotwired this as well by commenting it out and jumping straight to the if statement on line 85 of connectivity.ts changing it from an else to an if (this._status === NDKRelayStatus.AUTHENTICATING) which returns true.

This allowed NDK to continue and :boom: my code started working. I am receiving reqs, and the pool is now emiting all the statuses I expected to see and subscribing etc.

I hope this helps you figure out what mistakes are happening here! My hot-wiring is obviously not PR ready or anything as I still need to understand more about some of this. I have a feeling that bunkers vs. nip07 and possibly just some bad if's are the culprit here.

jeremyd commented 3 months ago

could be that we're not setting the NDK here when addExplicitRelay is called:

ndk/src/ndk/index.ts line 327

jeremyd commented 3 months ago

Ok, I think I am starting to understand what's going on here. (Or at least guess..)

It appears to me that the first part of the auth handler was meant for NIP07 use.. and it's simply not setting the status to connected and emitting auth. So when i back out my hotwiring, and just add these things, I think this is working also and that the second part of the authHandler, where it asks if res===true, that must be for bunker signing.

jeremyd commented 3 months ago

Like this:

                    if (res instanceof NDKEvent) {
                        this.relay.auth(async (evt: EventTemplate): Promise<VerifiedEvent> => {
                            return res.rawEvent() as VerifiedEvent;
                        });
                            this._status = NDKRelayStatus.CONNECTED;
                            this.ndkRelay.emit("authed");
                    }
jeremyd commented 3 months ago

Turns out I was just confused about all this stuff. I have backed out all my changes to NDK, and things are working. I even went back to latest released version 2.8.2 and it's also working. I learned a LOT about the internals of NDK, so that's a win. I am enjoying these grouped subscriptions and auth features. Thank you.

Here's some example code of what finally seems like a pattern that works for me. I have to delay the subscription until after the auth for it to work. I also have to make sure to close the subscription on disconnect and re-create it. Overall, I'm very happy with this now. PV


const nip07signer = new NDKNip07Signer();

const ndk = new NDK({
    signer: nip07signer,
    autoConnectUserRelays: false,
    enableOutboxModel: false,
});

const ndkPool = ndk.pool;

export default function PostsPage() {

async function grabStuff(nrelaydata: string, auth: boolean = false) {
        var kind1Sub: NDKSubscription

        ndkPool.on("flapping", (flapping: NDKRelay) => {
            addToStatus("relay is flapping: " + flapping.url);
        });
        ndkPool.on("relay:auth", (relay: NDKRelay, challenge: string) => {
            addToStatus("auth: " + relay.url);
        });

        ndkPool.on("relay:authed", (relay: NDKRelay) => {
            addToStatus("authed: " + relay.url);
            wipePosts();
            kind1Sub = ndk.subscribe({ kinds: [1], limit: relayLimit }, {closeOnEose: false, groupable: false});
            kind1Sub.on("event", (event: NDKEvent) => {
                // do profile lookups on the fly
                if(lookupProfileName(event.pubkey) == event.pubkey) {
                    const profileSubAuth = ndk.subscribe({ kinds: [0], authors: [event.pubkey] }, {closeOnEose: true, groupable: true});
                    profileSubAuth.on("event", (pevent: NDKEvent) => {
                        addProfile(pevent);
                    });
                }
                addPost(event);
            });
        });

        ndkPool.on("relay:disconnect", (relay: NDKRelay) => {
            kind1Sub.stop()
            addToStatus("disconnected: " + relay.url);
        });

        ndkPool.on("relay:connect", (relay: NDKRelay) => {
            addToStatus("connected: " + relay.url);
            wipePosts();
            if(!auth) {
                kind1Sub = ndk.subscribe({ kinds: [1], limit: relayLimit }, {closeOnEose: false, groupable: false});
                kind1Sub.on("event", (event: NDKEvent) => {
                    // do profile lookups on the fly
                    if(lookupProfileName(event.pubkey) == event.pubkey) {
                        const profileSubAuth = ndk.subscribe({ kinds: [0], authors: [event.pubkey] }, {closeOnEose: true, groupable: true});
                        profileSubAuth.on("event", (pevent: NDKEvent) => {
                            addProfile(pevent);
                        });
                    }
                    addPost(event);
                });
            }
        });

        ndkPool.on("relay:connecting", (relay: NDKRelay) => {
            //addToStatus("connecting: " + relay.url);
        });

        ndk.addExplicitRelay(nrelaydata, NDKRelayAuthPolicies.signIn({ndk}), true);
    }

useEffect(() => {
        grabStuff(nrelaydata, useAuth == "true");
    }, []);
}    
pablof7z commented 3 months ago

Thanks for all this! I am writing documentation on the docs directory with tutorials and stuff like that

I would absolutely love it if you could spend some time to contribute some docs on what could have helped you understand how all this works; I am very close to NDK's inténtale (I wrote them! 😂) so there's probably a lot of stuff that you can see that I can't because of that

What's your npub btw?

jeremyd commented 3 months ago

Thanks for all this! I am writing documentation on the docs directory with tutorials and stuff like that

I would absolutely love it if you could spend some time to contribute some docs on what could have helped you understand how all this works; I am very close to NDK's inténtale (I wrote them! 😂) so there's probably a lot of stuff that you can see that I can't because of that

What's your npub btw?

No problem, I'd be happy to contribute. Let me know if you have a branch you're currently working on for the docs or just main branch. Some of the things from code snippits in the code comments did not work for me, so I will reproduce and make sure those are addressed. I'm still learning a lot (i filed a separate issue for I can't even seem to turn on debug haha). It looks like I'm going to be rolling with ndk for my project so contribution is the name of my game and I'll keep flowing with the issues, PRs and etc. :) I'll tag you on nostr so you have my npub.

pablof7z commented 3 months ago

I'm writing docs and tutorials on the /docs directory and they are deployed here https://nostr-dev-kit.github.io/ndk

jeremyd commented 3 months ago

Cool, while testing this morning I realized I had *not backed out all changes, and that auth actually is broken in master. I will corral and test these changes and push them up. PR incoming..

jeremyd commented 3 months ago

While this commit does un-stick authentication for a successful authentication. When authentication fails (relay rejects the auth) an error is thrown:

index.js:773 Uncaught (in promise) 
Error: auth-required: invalid auth received
    at Relay.handleNext (index.js:773:23)
    at Relay.runQueue (index.js:712:26)
    at Relay._onmessage (index.js:852:12)

I will need to think about this further, as I would like to be able to detect authentication failure and show a nice message to the user. Any thoughts appreciated. I think this error is coming from nostr-tools..

jeremyd commented 3 months ago

A bonus of catching this error is that it doesn't infinitely keep asking the user to sign these auth requests. It fails once, and then doesn't ask again..