Skip to content

Commit d868f16

Browse files
Add detailed LLM-friendly issue template for NIP-98 auto-auth implementation
1 parent 2f35e37 commit d868f16

1 file changed

Lines changed: 311 additions & 0 deletions

File tree

Lines changed: 311 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,311 @@
1+
# Implement Automatic NIP-98 HTTP Authentication
2+
3+
## Overview
4+
5+
Currently, Podkey provides NIP-07 `window.nostr` API for websites to manually create and sign NIP-98 auth events, but it does **not automatically intercept HTTP requests** to add NIP-98 Authorization headers. This issue tracks the implementation of automatic NIP-98 authentication for Solid servers and other Nostr-authenticated HTTP endpoints.
6+
7+
## Current State
8+
9+
- ✅ Extension has `webRequest` permission in `manifest.json`
10+
- ✅ Extension can sign NIP-98 events (kind 27235) via `window.nostr.signEvent()`
11+
- ✅ Auto-sign functionality exists for trusted origins
12+
-**No automatic HTTP request interception**
13+
-**No automatic 401 response detection and retry**
14+
-**No automatic Authorization header injection**
15+
16+
## NIP-98 Specification Reference
17+
18+
**Specification:** https://github.com/nostr-protocol/nips/blob/master/98.md
19+
20+
### Event Structure
21+
22+
```json
23+
{
24+
"kind": 27235,
25+
"content": "",
26+
"tags": [
27+
["u", "https://example.com/api/resource?query=value"],
28+
["method", "GET"]
29+
],
30+
"created_at": 1682327852
31+
}
32+
```
33+
34+
### Authorization Header Format
35+
36+
```
37+
Authorization: Nostr <base64-encoded-signed-event>
38+
```
39+
40+
### For Requests with Body (POST, PUT, etc.)
41+
42+
Include a `payload` tag with SHA-256 hash of the request body:
43+
```json
44+
{
45+
"tags": [
46+
["u", "https://example.com/api/resource"],
47+
["method", "POST"],
48+
["payload", "<sha256-hex-of-body>"]
49+
]
50+
}
51+
```
52+
53+
## Implementation Requirements
54+
55+
### 1. HTTP Request Interception
56+
57+
**Location:** `src/background.js`
58+
59+
**Implementation:**
60+
- Use `chrome.webRequest.onBeforeSendHeaders` to intercept outgoing requests
61+
- Filter requests to origins that:
62+
- Are trusted (via `isTrustedOrigin()`)
63+
- Have auto-sign enabled (via `getAutoSign()`)
64+
- OR are Solid servers (detect via domain patterns or explicit list)
65+
66+
**Considerations:**
67+
- Only intercept requests to origins that have been trusted or are known Solid servers
68+
- Don't intercept requests that already have an `Authorization` header (unless it's a retry)
69+
- Cache signed auth events per request URL+method to avoid re-signing identical requests
70+
71+
### 2. 401 Response Detection and Retry
72+
73+
**Location:** `src/background.js`
74+
75+
**Implementation:**
76+
- Use `chrome.webRequest.onHeadersReceived` to detect 401 responses
77+
- When 401 is detected:
78+
1. Check if origin is trusted
79+
2. Create NIP-98 auth event for the failed request
80+
3. Sign the event using existing `signEvent()` function
81+
4. Retry the original request with `Authorization: Nostr <base64-event>` header
82+
83+
**Retry Logic:**
84+
- Only retry on **401 Unauthorized** responses
85+
- Consider **403 Forbidden** if it's auth-related (optional, may need user preference)
86+
- **Do NOT retry** on other 4xx errors (404, 400, etc.) - these are not auth issues
87+
- Limit retry attempts (max 1 retry per request to avoid loops)
88+
- Track retry state to prevent infinite loops
89+
90+
### 3. NIP-98 Event Creation
91+
92+
**Location:** `src/background.js` (new function)
93+
94+
**Function Signature:**
95+
```javascript
96+
async function createNip98AuthEvent(requestDetails) {
97+
// requestDetails from chrome.webRequest API
98+
// Returns: unsigned event object ready for signing
99+
}
100+
```
101+
102+
**Event Creation:**
103+
```javascript
104+
const event = {
105+
kind: 27235,
106+
content: "",
107+
created_at: Math.floor(Date.now() / 1000),
108+
tags: [
109+
["u", requestDetails.url], // Full URL including query params
110+
["method", requestDetails.method]
111+
]
112+
};
113+
114+
// If request has body, add payload tag
115+
if (requestDetails.requestBody) {
116+
const bodyHash = await hashRequestBody(requestDetails.requestBody);
117+
event.tags.push(["payload", bodyHash]);
118+
}
119+
```
120+
121+
### 4. Request Body Hashing
122+
123+
**Location:** `src/background.js` or `src/crypto.js`
124+
125+
**Implementation:**
126+
- For requests with body (POST, PUT, PATCH), compute SHA-256 hash
127+
- Use existing `@noble/hashes` library (already in dependencies)
128+
- Handle different body formats:
129+
- `FormData` → convert to bytes
130+
- `ArrayBuffer` → use directly
131+
- `Blob` → read as ArrayBuffer
132+
- `string` → encode to UTF-8 bytes
133+
134+
### 5. Base64 Encoding
135+
136+
**Location:** `src/background.js` or utility function
137+
138+
**Implementation:**
139+
- Encode signed event JSON to base64
140+
- Use browser's built-in `btoa()` or `Buffer` (Node.js style)
141+
- Format: `Authorization: Nostr <base64-string>`
142+
143+
### 6. User Preferences
144+
145+
**Location:** `src/storage.js` (may need additions)
146+
147+
**Considerations:**
148+
- Add setting: "Auto-authenticate HTTP requests" (separate from event auto-sign)
149+
- Add setting: "Retry on 403 Forbidden" (optional, default false)
150+
- Add setting: "Solid server domains" (whitelist for auto-auth)
151+
- Respect existing `getAutoSign()` preference
152+
153+
## Code Structure
154+
155+
### New Functions Needed
156+
157+
```javascript
158+
// In src/background.js
159+
160+
/**
161+
* Create NIP-98 authentication event for an HTTP request
162+
*/
163+
async function createNip98AuthEvent(requestDetails) {
164+
// Implementation
165+
}
166+
167+
/**
168+
* Hash request body for NIP-98 payload tag
169+
*/
170+
async function hashRequestBody(requestBody) {
171+
// Implementation using @noble/hashes
172+
}
173+
174+
/**
175+
* Encode signed event to Authorization header value
176+
*/
177+
function encodeNip98Header(signedEvent) {
178+
// Implementation
179+
}
180+
181+
/**
182+
* Handle 401 response - create auth and retry
183+
*/
184+
async function handle401Response(details) {
185+
// Implementation
186+
}
187+
188+
/**
189+
* Intercept requests and add NIP-98 auth if needed
190+
*/
191+
function interceptRequest(details) {
192+
// Implementation
193+
}
194+
```
195+
196+
### WebRequest Listeners
197+
198+
```javascript
199+
// In src/background.js initialization
200+
201+
// Intercept outgoing requests
202+
chrome.webRequest.onBeforeSendHeaders.addListener(
203+
interceptRequest,
204+
{
205+
urls: ["<all_urls>"],
206+
types: ["xmlhttprequest", "main_frame", "sub_frame"]
207+
},
208+
["requestHeaders", "blocking"]
209+
);
210+
211+
// Detect 401 responses
212+
chrome.webRequest.onHeadersReceived.addListener(
213+
handle401Response,
214+
{
215+
urls: ["<all_urls>"],
216+
types: ["xmlhttprequest", "main_frame", "sub_frame"]
217+
},
218+
["responseHeaders"]
219+
);
220+
```
221+
222+
## Edge Cases and Considerations
223+
224+
1. **Request Body Handling:**
225+
- Different formats (FormData, Blob, ArrayBuffer, string)
226+
- Large bodies (consider streaming or size limits)
227+
- Binary data encoding
228+
229+
2. **URL Normalization:**
230+
- Ensure `u` tag matches exactly what server expects
231+
- Include query parameters
232+
- Handle URL encoding/decoding
233+
234+
3. **Timing:**
235+
- `created_at` should be recent (within 60 seconds typically)
236+
- Consider request timing vs. event creation timing
237+
238+
4. **Caching:**
239+
- Cache signed events per (URL, method, bodyHash) tuple
240+
- Invalidate cache after expiration (e.g., 60 seconds)
241+
- Avoid re-signing identical requests
242+
243+
5. **Security:**
244+
- Only auto-auth for trusted origins
245+
- Respect user's auto-sign preference
246+
- Don't leak private keys or expose auth events unnecessarily
247+
248+
6. **Performance:**
249+
- Minimize blocking operations
250+
- Use async/await properly
251+
- Consider request queuing for retries
252+
253+
7. **Error Handling:**
254+
- Handle signing failures gracefully
255+
- Log errors for debugging
256+
- Don't break normal browsing if auth fails
257+
258+
## Testing Requirements
259+
260+
1. **Unit Tests:**
261+
- `createNip98AuthEvent()` with various request types
262+
- `hashRequestBody()` with different body formats
263+
- `encodeNip98Header()` encoding correctness
264+
265+
2. **Integration Tests:**
266+
- Mock Solid server that requires NIP-98 auth
267+
- Test 401 detection and retry flow
268+
- Test auto-auth for trusted origins
269+
- Test that non-trusted origins don't get auto-auth
270+
271+
3. **Manual Testing:**
272+
- Test with real Solid server
273+
- Test with various HTTP methods (GET, POST, PUT, DELETE)
274+
- Test with and without request bodies
275+
- Test retry behavior on 401
276+
- Verify no retry on other 4xx errors
277+
278+
## Acceptance Criteria
279+
280+
- [ ] Extension automatically adds NIP-98 Authorization header to requests from trusted origins
281+
- [ ] Extension detects 401 responses and retries with NIP-98 auth
282+
- [ ] NIP-98 events are correctly formatted per specification
283+
- [ ] Request bodies are hashed and included in `payload` tag when present
284+
- [ ] Only 401 (and optionally 403) responses trigger retry
285+
- [ ] User preferences (auto-sign, trusted origins) are respected
286+
- [ ] No infinite retry loops
287+
- [ ] Works with GET, POST, PUT, DELETE, PATCH methods
288+
- [ ] Handles various request body formats correctly
289+
- [ ] Unit tests pass
290+
- [ ] Integration tests pass
291+
- [ ] Manual testing with real Solid server succeeds
292+
293+
## Related Files
294+
295+
- `src/background.js` - Main implementation location
296+
- `src/crypto.js` - May need body hashing utilities
297+
- `src/storage.js` - User preferences storage
298+
- `manifest.json` - Already has `webRequest` permission ✅
299+
300+
## References
301+
302+
- [NIP-98 Specification](https://github.com/nostr-protocol/nips/blob/master/98.md)
303+
- [Chrome WebRequest API](https://developer.chrome.com/docs/extensions/reference/webRequest/)
304+
- [Solid Project Authentication](https://solidproject.org/TR/oidc)
305+
306+
## Notes
307+
308+
- This feature should be opt-in via user preferences
309+
- Consider adding UI in popup to enable/disable auto-auth
310+
- May want to show notification when auto-auth succeeds/fails
311+
- Consider rate limiting to prevent abuse

0 commit comments

Comments
 (0)