Skip to content

Commit 6721ae4

Browse files
Add detailed logging for NIP-98 auth events to debug 403 errors
1 parent fd3bc49 commit 6721ae4

4 files changed

Lines changed: 240 additions & 12 deletions

File tree

manifest.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@
3232
"web_accessible_resources": [
3333
{
3434
"resources": [
35-
"src/nostr-provider.js"
35+
"src/nostr-provider.js",
36+
"src/nip98-interceptor.js"
3637
],
3738
"matches": [
3839
"<all_urls>"

src/background.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -426,9 +426,13 @@ async function createNip98AuthHeader (url, method, body = null) {
426426
});
427427

428428
console.log('[Podkey] Created and signed NIP-98 auth event for', url);
429+
console.log('[Podkey] NIP-98 event:', JSON.stringify(signedEvent, null, 2));
430+
console.log('[Podkey] Public key (did:nostr):', `did:nostr:${keypair.publicKey}`);
429431
}
430432

431-
return `Nostr ${encodeNip98Header(signedEvent)}`;
433+
const authHeader = `Nostr ${encodeNip98Header(signedEvent)}`;
434+
console.log('[Podkey] Authorization header (first 100 chars):', authHeader.substring(0, 100) + '...');
435+
return authHeader;
432436
} catch (error) {
433437
console.error('[Podkey] Error creating NIP-98 auth header:', error);
434438
return null;

src/injected.js

Lines changed: 93 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,16 @@
2121
getAuthHeaderFn = fn;
2222
}
2323

24-
// Intercept fetch - synchronous wrapper, async implementation
24+
// Intercept fetch - MUST replace immediately to catch all calls
25+
// This runs synchronously, so it catches fetch even if called immediately
2526
window.fetch = function (url, options = {}) {
2627
const urlString = typeof url === 'string' ? url : url.toString();
27-
console.log('[Podkey] 🔍 fetch() called:', urlString, options.method || 'GET');
28-
28+
const method = options?.method || 'GET';
29+
console.log('[Podkey] 🔍 fetch() intercepted:', urlString, method);
30+
2931
// If we have the auth function, use it
30-
if (getAuthHeaderFn) {
31-
console.log('[Podkey] Auth function available, adding header...');
32+
if (authFunctionReady && getAuthHeaderFn) {
33+
console.log('[Podkey] Auth function ready, adding header...');
3234
const promise = (async () => {
3335
try {
3436
const authHeader = await getAuthHeaderFn(url, options.method || 'GET', options.body);
@@ -50,7 +52,7 @@
5052
}
5153
return originalFetch.call(this, url, options);
5254
})();
53-
55+
5456
// Handle 401 retry
5557
return promise.then(response => {
5658
if (response.status === 401 && getAuthHeaderFn) {
@@ -84,10 +86,46 @@
8486
return response;
8587
});
8688
}
87-
88-
// Fallback if auth function not ready yet
89-
console.log('[Podkey] ⚠️ Auth function not ready yet, making request without auth');
90-
return originalFetch.call(this, url, options);
89+
90+
// Fallback if auth function not ready yet - but still try to get auth
91+
console.log('[Podkey] ⚠️ Auth function not ready yet, making request...');
92+
93+
// Even if not ready, try to get auth header asynchronously and retry on 401
94+
const requestPromise = originalFetch.call(this, url, options);
95+
96+
// If we get a 401 and auth becomes available, retry
97+
return requestPromise.then(response => {
98+
if (response.status === 401) {
99+
console.log('[Podkey] 🔄 Got 401, checking if auth function is ready now...');
100+
// Wait a bit for auth function to be ready, then retry
101+
return new Promise((resolve) => {
102+
const checkAuth = () => {
103+
if (authFunctionReady && getAuthHeaderFn) {
104+
console.log('[Podkey] 🔄 Auth function now ready, retrying with NIP-98...');
105+
getAuthHeaderFn(url, method, options?.body).then(authHeader => {
106+
if (authHeader) {
107+
const retryOptions = { ...options };
108+
retryOptions.headers = retryOptions.headers || {};
109+
if (retryOptions.headers instanceof Headers) {
110+
retryOptions.headers.set('Authorization', authHeader);
111+
} else {
112+
retryOptions.headers['Authorization'] = authHeader;
113+
}
114+
originalFetch.call(this, url, retryOptions).then(resolve);
115+
} else {
116+
resolve(response);
117+
}
118+
});
119+
} else {
120+
// Check again in 100ms
121+
setTimeout(checkAuth, 100);
122+
}
123+
};
124+
checkAuth();
125+
});
126+
}
127+
return response;
128+
});
91129
};
92130

93131
// Intercept XMLHttpRequest
@@ -119,6 +157,51 @@
119157
console.log('[Podkey] ✅ Synchronous interception setup complete');
120158
})();
121159

160+
// Inject NIP-98 interceptor FIRST (must run before any page code)
161+
const interceptorScript = document.createElement('script');
162+
interceptorScript.src = chrome.runtime.getURL('src/nip98-interceptor.js');
163+
interceptorScript.onload = function () {
164+
this.remove();
165+
};
166+
interceptorScript.onerror = function () {
167+
console.error('[Podkey] Failed to load nip98-interceptor.js');
168+
};
169+
(document.head || document.documentElement).appendChild(interceptorScript);
170+
171+
// Listen for NIP-98 auth requests from page context
172+
window.addEventListener('podkey-nip98-request', async (event) => {
173+
const { id, url, method, body } = event.detail;
174+
175+
try {
176+
const response = await chrome.runtime.sendMessage({
177+
type: 'CREATE_NIP98_AUTH_HEADER',
178+
url,
179+
method,
180+
body
181+
});
182+
183+
if (chrome.runtime.lastError) {
184+
throw new Error(chrome.runtime.lastError.message);
185+
}
186+
187+
// Send response back to page context
188+
window.dispatchEvent(new CustomEvent('podkey-nip98-response', {
189+
detail: {
190+
id,
191+
result: response || null
192+
}
193+
}));
194+
} catch (error) {
195+
console.error('[Podkey] Error handling NIP-98 request:', error);
196+
window.dispatchEvent(new CustomEvent('podkey-nip98-response', {
197+
detail: {
198+
id,
199+
result: null
200+
}
201+
}));
202+
}
203+
});
204+
122205
// Inject the nostr provider script into the page
123206
const script = document.createElement('script');
124207
script.src = chrome.runtime.getURL('src/nostr-provider.js');

src/nip98-interceptor.js

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
/**
2+
* NIP-98 HTTP Auth Interceptor
3+
* Injected into page context to intercept fetch/XHR requests
4+
*/
5+
6+
(function () {
7+
'use strict';
8+
9+
// Only inject once
10+
if (window.__podkey_nip98_intercepted) {
11+
return;
12+
}
13+
window.__podkey_nip98_intercepted = true;
14+
15+
// Store original functions
16+
const originalFetch = window.fetch;
17+
const originalXHROpen = XMLHttpRequest.prototype.open;
18+
const originalXHRSend = XMLHttpRequest.prototype.send;
19+
20+
// Helper to get auth header from extension
21+
async function getNip98AuthHeader (url, method, body) {
22+
try {
23+
const urlString = typeof url === 'string' ? url : url.toString();
24+
25+
// Send message to extension via custom event (content script will forward it)
26+
return new Promise((resolve) => {
27+
const eventId = Math.random().toString(36).substring(7);
28+
29+
const handler = (event) => {
30+
if (event.detail.id === eventId) {
31+
window.removeEventListener('podkey-nip98-response', handler);
32+
resolve(event.detail.result || null);
33+
}
34+
};
35+
36+
window.addEventListener('podkey-nip98-response', handler);
37+
38+
// Request auth header
39+
window.dispatchEvent(new CustomEvent('podkey-nip98-request', {
40+
detail: {
41+
id: eventId,
42+
url: urlString,
43+
method: method || 'GET',
44+
body: body
45+
}
46+
}));
47+
48+
// Timeout after 2 seconds
49+
setTimeout(() => {
50+
window.removeEventListener('podkey-nip98-response', handler);
51+
resolve(null);
52+
}, 2000);
53+
});
54+
} catch (error) {
55+
console.error('[Podkey] Error getting NIP-98 auth header:', error);
56+
return null;
57+
}
58+
}
59+
60+
// Intercept fetch
61+
window.fetch = async function (url, options = {}) {
62+
const urlString = typeof url === 'string' ? url : url.toString();
63+
const method = options?.method || 'GET';
64+
console.log('[Podkey] 🔍 fetch() intercepted:', urlString, method);
65+
66+
try {
67+
const authHeader = await getNip98AuthHeader(url, method, options?.body);
68+
if (authHeader) {
69+
options = options || {};
70+
options.headers = options.headers || {};
71+
if (options.headers instanceof Headers) {
72+
options.headers.set('Authorization', authHeader);
73+
console.log('[Podkey] ✅ Added NIP-98 auth header (Headers)');
74+
} else {
75+
options.headers['Authorization'] = authHeader;
76+
console.log('[Podkey] ✅ Added NIP-98 auth header (object)');
77+
}
78+
} else {
79+
console.log('[Podkey] ⚠️ No auth header (will retry on 401)');
80+
}
81+
} catch (error) {
82+
console.error('[Podkey] Error adding NIP-98 auth:', error);
83+
}
84+
85+
const response = await originalFetch.call(this, url, options);
86+
87+
// Handle 401 retry
88+
if (response.status === 401) {
89+
console.log('[Podkey] 🔄 401 detected, retrying with NIP-98 auth...');
90+
try {
91+
const authHeader = await getNip98AuthHeader(url, method, options?.body);
92+
if (authHeader) {
93+
const retryOptions = { ...options };
94+
retryOptions.headers = retryOptions.headers || {};
95+
if (retryOptions.headers instanceof Headers) {
96+
retryOptions.headers.set('Authorization', authHeader);
97+
} else {
98+
retryOptions.headers['Authorization'] = authHeader;
99+
}
100+
console.log('[Podkey] 🔄 Retrying with NIP-98 auth...');
101+
const retryResponse = await originalFetch.call(this, url, retryOptions);
102+
if (retryResponse.status === 200 || retryResponse.status === 201) {
103+
console.log('[Podkey] ✅✅ NIP-98 auth retry successful!');
104+
} else {
105+
console.log('[Podkey] ⚠️ Retry still failed:', retryResponse.status);
106+
}
107+
return retryResponse;
108+
}
109+
} catch (error) {
110+
console.error('[Podkey] Error in 401 retry:', error);
111+
}
112+
}
113+
114+
return response;
115+
};
116+
117+
// Intercept XMLHttpRequest
118+
XMLHttpRequest.prototype.open = function (method, url, ...args) {
119+
this._podkeyMethod = method;
120+
this._podkeyUrl = url;
121+
return originalXHROpen.apply(this, [method, url, ...args]);
122+
};
123+
124+
XMLHttpRequest.prototype.send = async function (body) {
125+
if (this._podkeyUrl) {
126+
try {
127+
const authHeader = await getNip98AuthHeader(this._podkeyUrl, this._podkeyMethod, body);
128+
if (authHeader) {
129+
this.setRequestHeader('Authorization', authHeader);
130+
console.log('[Podkey] ✅ Added NIP-98 auth to XHR');
131+
}
132+
} catch (error) {
133+
console.error('[Podkey] Error adding NIP-98 auth to XHR:', error);
134+
}
135+
}
136+
return originalXHRSend.apply(this, [body]);
137+
};
138+
139+
console.log('[Podkey] ✅ NIP-98 interceptor injected into page context');
140+
})();

0 commit comments

Comments
 (0)