{"id":31786163,"url":"https://github.com/bitkarrot/relay-auth-test","last_synced_at":"2025-10-28T15:38:18.294Z","repository":{"id":308839955,"uuid":"1034280110","full_name":"bitkarrot/relay-auth-test","owner":"bitkarrot","description":"Explore 4 demos for NIP-42 Relay Auth","archived":false,"fork":false,"pushed_at":"2025-09-23T22:53:32.000Z","size":415,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-09-24T00:22:47.064Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://relay-auth-test.vercel.app","language":"Astro","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/bitkarrot.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-08-08T06:22:26.000Z","updated_at":"2025-09-23T22:53:29.000Z","dependencies_parsed_at":"2025-08-08T08:38:36.942Z","dependency_job_id":null,"html_url":"https://github.com/bitkarrot/relay-auth-test","commit_stats":null,"previous_names":["bitkarrot/relay-auth-test"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/bitkarrot/relay-auth-test","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bitkarrot%2Frelay-auth-test","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bitkarrot%2Frelay-auth-test/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bitkarrot%2Frelay-auth-test/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bitkarrot%2Frelay-auth-test/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/bitkarrot","download_url":"https://codeload.github.com/bitkarrot/relay-auth-test/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bitkarrot%2Frelay-auth-test/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":279003889,"owners_count":26083641,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-10T02:00:06.843Z","response_time":62,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-10-10T12:48:29.257Z","updated_at":"2025-10-10T12:48:32.458Z","avatar_url":"https://github.com/bitkarrot.png","language":"Astro","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Nostr NIP-42 AUTH with Kind 30078 Events - Demo Code\n\n4 Demos in this repo - This implementation provides a modular, reusable system for NIP-42 authentication and publishing kind 30078 (Parameterized Replaceable Events) in Astro applications using nostr-tools.\n\n## 🔗 NIP-42 Authentication Flow\n\n```mermaid\nsequenceDiagram\n    participant Client as Client App\u003cbr/\u003e(Astro Page)\n    participant Extension as NIP-07\u003cbr/\u003eExtension\n    participant Relay as Nostr Relay\u003cbr/\u003e(ws://localhost:3334)\n    \n    Note over Client, Relay: Phase 1: Initial Connection \u0026 Challenge\n    \n    Client-\u003e\u003eRelay: 1. Connect WebSocket\n    activate Relay\n    \n    Client-\u003e\u003eRelay: 2. Send REQ (trigger AUTH)\n    Note right of Client: [\"REQ\", \"auth-trigger\", {\"limit\": 1}]\n    \n    Relay-\u003e\u003eClient: 3. AUTH Challenge\n    Note left of Relay: [\"AUTH\", \"\u003crandom-challenge-string\u003e\"]\n    \n    Note over Client, Relay: Phase 2: Authentication Response\n    \n    Client-\u003e\u003eExtension: 4. Get Public Key\n    activate Extension\n    Extension--\u003e\u003eClient: pubkey\n    deactivate Extension\n    \n    Client-\u003e\u003eClient: 5. Create AUTH Event\n    Note right of Client: kind: 22242\u003cbr/\u003etags: [[\"relay\", \"ws://...\"], [\"challenge\", \"...\"]]\u003cbr/\u003econtent: \"\"\n    \n    Client-\u003e\u003eExtension: 6. Sign AUTH Event\n    activate Extension\n    Extension--\u003e\u003eClient: Signed AUTH Event\n    deactivate Extension\n    \n    Client-\u003e\u003eRelay: 7. Send Signed AUTH\n    Note right of Client: [\"AUTH\", signed-auth-event]\n    \n    Relay-\u003e\u003eClient: 8. AUTH Response (OK/FAIL)\n    Note left of Relay: [\"OK\", event-id, true/false, message]\n    \n    alt Authentication Successful\n        Relay--\u003e\u003eClient: ✅ Connection Authenticated\n        Note over Client, Relay: Same connection remains open\n        \n        Note over Client, Relay: Phase 3: Publish Kind 30078 Event\n        \n        Client-\u003e\u003eClient: 9. Create Kind 30078 Event\n        Note right of Client: kind: 30078\u003cbr/\u003etags: [[\"d\", \"unique-tag\"], ...]\u003cbr/\u003econtent: \"event content\"\n        \n        Client-\u003e\u003eExtension: 10. Sign Kind 30078 Event\n        activate Extension\n        Extension--\u003e\u003eClient: Signed Event\n        deactivate Extension\n        \n        Client-\u003e\u003eRelay: 11. Publish Event\n        Note right of Client: [\"EVENT\", signed-30078-event]\n        \n        Relay-\u003e\u003eClient: 12. Publish Response (OK/FAIL)\n        Note left of Relay: [\"OK\", event-id, true/false, message]\n        \n        alt Publish Successful\n            Relay--\u003e\u003eClient: ✅ Event Published\n            Note right of Client: Event ID returned\n        else Publish Failed\n            Relay--\u003e\u003eClient: ❌ Publish Error\n            Note right of Client: Error message provided\n        end\n        \n    else Authentication Failed\n        Relay--\u003e\u003eClient: ❌ AUTH Failed\n        Note right of Client: Connection not authenticated\u003cbr/\u003eCannot publish events\n    end\n    \n    Note over Client, Relay: Phase 4: Connection Management\n    \n    opt Later Operations\n        Client-\u003e\u003eRelay: Additional Events\n        Note right of Client: Can publish more events\u003cbr/\u003eusing same authenticated connection\n    end\n    \n    opt Disconnect\n        Client-\u003e\u003eRelay: Close Connection\n        deactivate Relay\n        Note over Client, Relay: Connection closed\u003cbr/\u003eMust re-authenticate for new session\n    end\n\n\n```\n\n# NIP-42 Authentication Message Details\n\n## 🔄 **Message Flow Breakdown**\n\n### **Phase 1: Connection \u0026 Challenge**\n\n#### 1. WebSocket Connection\n```javascript\n// Client connects to relay\nconst ws = new WebSocket('ws://localhost:3334')\n```\n\n#### 2. Trigger AUTH Challenge\n```json\n[\"REQ\", \"auth-trigger\", {\"limit\": 1}]\n```\n- **Purpose**: Many relays only send AUTH challenges when a restricted operation is attempted\n- **Effect**: Prompts relay to send AUTH challenge if required\n\n#### 3. AUTH Challenge from Relay\n```json\n[\"AUTH\", \"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\"]\n```\n- **Format**: `[\"AUTH\", \"\u003cchallenge-string\u003e\"]`\n- **Challenge**: Random string that must be included in AUTH response\n- **Security**: Prevents replay attacks\n\n### **Phase 2: Authentication Response**\n\n#### 4. NIP-07 Public Key Request\n```javascript\nconst pubkey = await window.nostr.getPublicKey()\n```\n- **Returns**: User's public key (hex format)\n- **Permission**: May trigger extension permission prompt\n\n#### 5. Create AUTH Event (Kind 22242)\n```json\n{\n  \"kind\": 22242,\n  \"created_at\": 1699123456,\n  \"tags\": [\n    [\"relay\", \"ws://localhost:3334\"],\n    [\"challenge\", \"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\"]\n  ],\n  \"content\": \"\",\n  \"pubkey\": \"user-pubkey-hex\"\n}\n```\n- **Kind 22242**: Special event type for NIP-42 authentication\n- **Required Tags**: \n  - `relay`: The relay URL being authenticated to\n  - `challenge`: The exact challenge string received\n- **Content**: Always empty for AUTH events\n\n#### 6. Sign AUTH Event\n```javascript\nconst signedAuthEvent = await window.nostr.signEvent(authEvent)\n```\n- **Adds**: `id` (event hash) and `sig` (signature) fields\n- **Security**: Cryptographically proves ownership of private key\n\n#### 7. Send AUTH Response\n```json\n[\"AUTH\", {\n  \"kind\": 22242,\n  \"created_at\": 1699123456,\n  \"tags\": [[\"relay\", \"ws://localhost:3334\"], [\"challenge\", \"...\"]],\n  \"content\": \"\",\n  \"pubkey\": \"...\",\n  \"id\": \"event-hash\",\n  \"sig\": \"signature\"\n}]\n```\n\n#### 8. Relay AUTH Verification\n```json\n[\"OK\", \"event-id\", true, \"\"]\n```\n- **Format**: `[\"OK\", event-id, success, message]`\n- **Success**: `true` = authenticated, `false` = failed\n- **Message**: Error description if failed\n\n### **Phase 3: Event Publishing**\n\n#### 9. Create Kind 30078 Event\n```json\n{\n  \"kind\": 30078,\n  \"created_at\": 1699123456,\n  \"tags\": [\n    [\"d\", \"unique-identifier\"],\n    [\"t\", \"example\"],\n    [\"client\", \"astro-app\"]\n  ],\n  \"content\": \"Hello, authenticated Nostr!\",\n  \"pubkey\": \"user-pubkey-hex\"\n}\n```\n- **Kind 30078**: Parameterized Replaceable Event\n- **Required Tag**: `[\"d\", \"identifier\"]` - makes event replaceable\n- **Additional Tags**: Custom tags for categorization, client info, etc.\n\n#### 10. Sign Kind 30078 Event\n```javascript\nconst signedEvent = await window.nostr.signEvent(event)\n```\n\n#### 11. Publish Event\n```json\n[\"EVENT\", {\n  \"kind\": 30078,\n  \"created_at\": 1699123456,\n  \"tags\": [[\"d\", \"unique-id\"], [\"t\", \"example\"]],\n  \"content\": \"Hello, authenticated Nostr!\",\n  \"pubkey\": \"...\",\n  \"id\": \"event-hash\",\n  \"sig\": \"signature\"\n}]\n```\n\n#### 12. Publish Confirmation\n```json\n[\"OK\", \"event-id\", true, \"\"]\n```\n\n## 🔐 **Security Features**\n\n### **Challenge-Response Authentication**\n- **Unique Challenge**: Each AUTH attempt gets a unique challenge\n- **Replay Protection**: Old AUTH events cannot be reused\n- **Time Sensitivity**: AUTH events typically have short validity windows\n\n### **Cryptographic Verification**\n- **Digital Signatures**: All events are cryptographically signed\n- **Public Key Verification**: Relay verifies signature matches claimed pubkey\n- **Event Integrity**: Event ID is hash of event content, preventing tampering\n\n### **Connection Security**\n- **Persistent Auth**: Authentication persists for the WebSocket connection lifetime\n- **Per-Connection**: Each new connection requires fresh authentication\n- **Selective Access**: Relays can require AUTH for specific operations only\n\n## ⚠️ **Critical Implementation Notes**\n\n### **Connection Persistence**\n```javascript\n// ✅ CORRECT: Use same connection for AUTH and publishing\nconst relay = await pool.ensureRelay(url)\nawait authenticate(relay)  // AUTH on this connection\nawait publishEvent(relay)  // Publish on SAME connection\n\n// ❌ WRONG: New connection loses authentication\nconst relay1 = await pool.ensureRelay(url)\nawait authenticate(relay1)\nrelay1.close()\n\nconst relay2 = await pool.ensureRelay(url)  // New connection!\nawait publishEvent(relay2)  // Will fail - not authenticated\n```\n\n### **Challenge Handling**\n```javascript\n// ✅ CORRECT: Use exact challenge from relay\nrelay.on('auth', (challenge) =\u003e {\n  const authEvent = {\n    tags: [['challenge', challenge]]  // Exact challenge\n  }\n})\n\n// ❌ WRONG: Modified or old challenge\nconst authEvent = {\n  tags: [['challenge', 'old-challenge']]  // Will be rejected\n}\n```\n\n### **Event Structure**\n```javascript\n// ✅ CORRECT: Proper kind 30078 with d-tag\nconst event = {\n  kind: 30078,\n  tags: [['d', 'unique-id']]  // Required for parameterized replaceable\n}\n\n// ❌ WRONG: Missing d-tag\nconst event = {\n  kind: 30078,\n  tags: [['t', 'topic']]  // Missing required d-tag\n}\n```\n\n## 🎯 **Implementation Tips**\n\n1. **Always handle AUTH challenges immediately**\n2. **Keep the same WebSocket connection alive**\n3. **Implement proper timeout handling**\n4. **Validate all event structures before signing**\n5. **Handle extension permission requests gracefully**\n6. **Provide clear error messages to users**\n7. **Test with different relay implementations**\n\n\n## 🚀 Features\n\n- **NIP-42 Authentication**: Complete relay authentication flow\n- **Kind 30078 Events**: Publish parameterized replaceable events after auth\n- **NIP-07 Integration**: Works with browser extension signers (Alby, nos2x, etc.)\n- **Persistent Connection**: Maintains same connection for AUTH and event publishing\n- **Modular Design**: Reusable across multiple Astro pages\n- **Environment Configuration**: Configurable relay URLs via environment variables\n- **TypeScript Support**: Full type safety and IntelliSense\n- **Error Handling**: Comprehensive error handling and user feedback\n\n## 📁 Project Structure\n\n```\nsrc/\n├── lib/\n│   └── nostr-auth.ts              # Core authentication service\n├── composables/\n│   └── useNostrAuth.ts            # Reusable composable with UI helpers\n├── pages/\n│   ├── nostr-auth-example.astro   # Complete example page\n│   └── simple-nostr-example.astro # Simple usage example\n└── env.d.ts                       # Environment type definitions\n```\n\n## ⚙️ Installation\n\n1. **Install dependencies**:\n```bash\nnpm install nostr-tools\nnpm install -D @types/node typescript\n```\n\n2. **Set up environment variables**:\n```bash\n# .env\nHIVETALK_RELAYS=ws://localhost:3334\n```\n\n3. **Configure Astro** (astro.config.mjs):\n```js\nimport { defineConfig } from 'astro/config';\n\nexport default defineConfig({\n  vite: {\n    define: {\n      'process.env.HIVETALK_RELAYS': JSON.stringify(process.env.HIVETALK_RELAYS || 'ws://localhost:3334')\n    }\n  }\n});\n```\n\n## 🔧 Core Components\n\n### NostrAuthService (`src/lib/nostr-auth.ts`)\n\nThe main authentication service that handles:\n- NIP-42 authentication flow\n- WebSocket connection management\n- Event signing and publishing\n- Error handling and timeouts\n\n**Key Methods**:\n- `authenticate()`: Perform NIP-42 AUTH with relay\n- `publishKind30078Event()`: Publish parameterized replaceable events\n- `disconnect()`: Clean up connections\n- `getAuthStatus()`: Check authentication state\n\n### UseNostrAuth Composable (`src/composables/useNostrAuth.ts`)\n\nA higher-level composable that provides:\n- State management with reactive updates\n- UI helper utilities\n- Simplified API for common operations\n- Event subscription system\n\n**Key Features**:\n- `subscribe()`: Listen to auth state changes\n- `createUIManager()`: Automatic DOM updates\n- Environment variable integration\n- Error state management\n\n## 📄 Kind 30078 Events\n\nKind 30078 events are Parameterized Replaceable Events that:\n- Require a \"d\" tag as unique identifier\n- Can be updated/replaced by publishing new events with same d-tag\n- Support additional custom tags\n- Must be published on authenticated connections (if relay requires AUTH)\n\n**Event Structure**:\n```json\n{\n  \"kind\": 30078,\n  \"created_at\": 1699123456,\n  \"tags\": [\n    [\"d\", \"unique-identifier\"],\n    [\"t\", \"custom-tag\"],\n    [\"client\", \"my-app\"]\n  ],\n  \"content\": \"Event content here\",\n  \"pubkey\": \"...\",\n  \"id\": \"...\",\n  \"sig\": \"...\"\n}\n```\n\n## 🛠️ Configuration Options\n\n### NostrAuthService Options\n```typescript\ninterface AuthConfig {\n  relayUrl: string    // WebSocket relay URL\n  timeout?: number    // Operation timeout in ms (default: 10000)\n}\n```\n\n### UseNostrAuth Options\n```typescript\ninterface UseNostrAuthOptions {\n  relayUrl?: string     // Override relay URL\n  timeout?: number      // Operation timeout\n  autoConnect?: boolean // Auto-authenticate when extension available\n}\n```\n\n## 🔒 Security Considerations\n\n1. **Extension Security**: Always verify NIP-07 extension availability\n2. **Connection Persistence**: Maintain same connection for AUTH and publishing\n3. **Event Validation**: Validate all event data before signing\n4. **Error Handling**: Never expose sensitive information in error messages\n5. **Timeout Management**: Use appropriate timeouts to prevent hanging\n\n## 🐛 Troubleshooting\n\n### Common Issues\n\n**\"NIP-07 extension not found\"**\n- Install a Nostr extension (Alby, nos2x, etc.)\n- Refresh the page after installation\n\n**\"Authentication timeout\"**\n- Check relay URL and availability\n- Increase timeout in configuration\n- Verify relay supports NIP-42\n\n**\"Event publish failed\"**\n- Ensure authentication completed successfully\n- Check d-tag uniqueness for replaceable events\n- Verify relay accepts kind 30078 events\n\n**\"Connection lost\"**\n- Don't disconnect between AUTH and publishing\n- Handle WebSocket connection errors gracefully\n- Implement reconnection logic if needed\n\n### Debugging Tips\n\n1. **Enable Console Logging**: All operations log detailed information\n2. **Check Network Tab**: Verify WebSocket messages in browser dev tools\n3. **Test Relay**: Use a simple WebSocket client to test relay connectivity\n4. **Validate Environment**: Ensure HIVETALK_RELAYS is properly set\n\n## 📚 References\n\n- [NIP-42: Authentication of clients to relays](https://github.com/nostr-protocol/nips/blob/master/42.md)\n- [NIP-01: Basic protocol flow](https://github.com/nostr-protocol/nips/blob/master/01.md)\n- [NIP-07: Browser extension interface](https://github.com/nostr-protocol/nips/blob/master/07.md)\n- [Nostr Tools Documentation](https://github.com/nbd-wtf/nostr-tools)\n\n## 🤝 Contributing\n\nThis implementation is designed to be modular and extensible. Feel free to:\n- Add support for additional event kinds\n- Implement reconnection logic\n- Add more sophisticated error handling\n- Create additional UI components\n\n## 📄 License\n\nThis code is provided as-is for educational and development purposes. Please review and test thoroughly before using in production applications.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbitkarrot%2Frelay-auth-test","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbitkarrot%2Frelay-auth-test","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbitkarrot%2Frelay-auth-test/lists"}