{"id":23970583,"url":"https://github.com/humanjavaenterprises/nostr-dm-magiclink-utils","last_synced_at":"2026-02-11T10:33:10.169Z","repository":{"id":270648174,"uuid":"898134772","full_name":"HumanjavaEnterprises/nostr-dm-magiclink-utils","owner":"HumanjavaEnterprises","description":"🔐 Secure, NIP-compliant magic link authentication for Nostr applications. Features encrypted DMs (NIP-04), multi-relay support, and i18n with RTL languages. Built with TypeScript for type-safe, passwordless authentication flows.","archived":false,"fork":false,"pushed_at":"2025-01-28T23:20:40.000Z","size":483,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-03-27T08:48:25.251Z","etag":null,"topics":["authentication","decentralized-identity","direct-messages","encryption","i18n","magic-links","multi-relay","nip-04","nostr","passwordless-auth","rtl-support","secure-messaging","typescript","web3","zero-config"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/HumanjavaEnterprises.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":["humanjavaenterprises"],"patreon":null,"open_collective":null,"ko_fi":null,"tidelift":null,"community_bridge":null,"liberapay":null,"issuehunt":null,"otechie":null,"custom":["https://getalby.com/p/vveerrgg","https://blockchair.com/bitcoin/address/bc1qw6kasfudkd64sts2q5a0lpm8zgea9txjc4gxj7","https://blockchair.com/litecoin/address/ltc1qhvt82z3308nr2gmpfuhldmu9cw3d943n7lf2we"]}},"created_at":"2024-12-03T21:11:28.000Z","updated_at":"2025-01-28T23:20:43.000Z","dependencies_parsed_at":"2025-01-29T00:24:29.388Z","dependency_job_id":"59bdf156-7e8c-426b-b2ea-6fa87381afd4","html_url":"https://github.com/HumanjavaEnterprises/nostr-dm-magiclink-utils","commit_stats":null,"previous_names":["humanjavaenterprises/nostr-dm-magiclink-utils"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/HumanjavaEnterprises%2Fnostr-dm-magiclink-utils","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/HumanjavaEnterprises%2Fnostr-dm-magiclink-utils/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/HumanjavaEnterprises%2Fnostr-dm-magiclink-utils/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/HumanjavaEnterprises%2Fnostr-dm-magiclink-utils/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/HumanjavaEnterprises","download_url":"https://codeload.github.com/HumanjavaEnterprises/nostr-dm-magiclink-utils/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248758446,"owners_count":21156957,"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","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":["authentication","decentralized-identity","direct-messages","encryption","i18n","magic-links","multi-relay","nip-04","nostr","passwordless-auth","rtl-support","secure-messaging","typescript","web3","zero-config"],"created_at":"2025-01-07T02:07:16.302Z","updated_at":"2026-02-11T10:33:10.163Z","avatar_url":"https://github.com/HumanjavaEnterprises.png","language":"TypeScript","funding_links":["https://github.com/sponsors/humanjavaenterprises","https://getalby.com/p/vveerrgg","https://blockchair.com/bitcoin/address/bc1qw6kasfudkd64sts2q5a0lpm8zgea9txjc4gxj7","https://blockchair.com/litecoin/address/ltc1qhvt82z3308nr2gmpfuhldmu9cw3d943n7lf2we"],"categories":[],"sub_categories":[],"readme":"# nostr-dm-magiclink-utils\n\n[![npm version](https://img.shields.io/npm/v/nostr-dm-magiclink-utils.svg)](https://www.npmjs.com/package/nostr-dm-magiclink-utils)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n[![TypeScript](https://img.shields.io/badge/TypeScript-5.0-blue.svg)](https://www.typescriptlang.org/)\n[![Node.js Version](https://img.shields.io/node/v/nostr-dm-magiclink-utils.svg)](https://nodejs.org)\n[![Test Coverage](https://img.shields.io/codecov/c/github/HumanjavaEnterprises/nostr-dm-magiclink-utils)](https://codecov.io/gh/HumanjavaEnterprises/nostr-dm-magiclink-utils)\n[![Build Status](https://img.shields.io/github/workflow/status/HumanjavaEnterprises/nostr-dm-magiclink-utils/CI)](https://github.com/HumanjavaEnterprises/nostr-dm-magiclink-utils/actions)\n[![Dependencies](https://img.shields.io/librariesio/release/npm/nostr-dm-magiclink-utils)](https://libraries.io/npm/nostr-dm-magiclink-utils)\n[![Code Style: Prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg)](https://github.com/prettier/prettier)\n[![Author](https://img.shields.io/badge/author-vveerrgg-blue.svg)](https://github.com/vveerrgg)\n[![Bundle Size](https://img.shields.io/bundlephobia/minzip/nostr-dm-magiclink-utils)](https://bundlephobia.com/package/nostr-dm-magiclink-utils)\n[![Downloads](https://img.shields.io/npm/dm/nostr-dm-magiclink-utils.svg)](https://www.npmjs.com/package/nostr-dm-magiclink-utils)\n[![Languages](https://img.shields.io/badge/i18n-9_languages-green.svg)](https://github.com/HumanjavaEnterprises/nostr-dm-magiclink-utils#supported-languages)\n[![Security](https://img.shields.io/badge/security-NIP--04-brightgreen.svg)](https://github.com/nostr-protocol/nips/blob/master/04.md)\n\nA comprehensive Nostr utility library for implementing secure, user-friendly authentication via magic links in direct messages. Built with TypeScript and following Nostr Improvement Proposals (NIPs) for maximum compatibility and security.\n\n## Features\n\n- 🔐 **NIP-04 Compliant**: Secure, encrypted direct messages following Nostr standards\n- 🌍 **Rich i18n Support**: 9 languages with RTL support\n- 🔄 **Multi-Relay Support**: Reliable message delivery with automatic failover\n- 🛡️ **Type-Safe**: Full TypeScript support with comprehensive types\n- 📝 **Flexible Templates**: Customizable messages with variable interpolation\n- 🚀 **Modern API**: Promise-based, async/await friendly interface\n- 🎯 **Zero Config**: Sensible defaults with optional deep customization\n\n## Installation\n\n```bash\nnpm install nostr-dm-magiclink-utils\n```\n\n## Quick Start\n\nHere's a complete example showing how to set up and use the magic link service:\n\n```typescript\nimport { createNostrMagicLink, NostrError } from 'nostr-dm-magiclink-utils';\nimport { generatePrivateKey } from 'nostr-tools'; // For demo purposes\n\nasync function setupAuthService() {\n  // Create manager with secure configuration\n  const magicLink = createNostrMagicLink({\n    nostr: {\n      // In production, load from secure environment variable\n      privateKey: process.env.NOSTR_PRIVATE_KEY || generatePrivateKey(),\n      relayUrls: [\n        'wss://relay.damus.io',\n        'wss://relay.nostr.band',\n        'wss://nos.lol'\n      ],\n      // Optional: Configure connection timeouts\n      connectionTimeout: 5000\n    },\n    magicLink: {\n      verifyUrl: 'https://your-app.com/verify',\n      // Async token generation with expiry\n      token: async () =\u003e {\n        const token = await generateSecureToken({\n          expiresIn: '15m',\n          length: 32\n        });\n        return token;\n      },\n      defaultLocale: 'en',\n      // Optional: Custom message templates\n      templates: {\n        en: {\n          subject: 'Login to {{appName}}',\n          body: 'Click this secure link to log in: {{link}}\\nValid for 15 minutes.'\n        }\n      }\n    }\n  });\n\n  return magicLink;\n}\n\n// Example usage in an Express route handler\napp.post('/auth/magic-link', async (req, res) =\u003e {\n  try {\n    const { pubkey } = req.body;\n    \n    if (!pubkey) {\n      return res.status(400).json({ error: 'Missing pubkey' });\n    }\n\n    const magicLink = await setupAuthService();\n    \n    const result = await magicLink.sendMagicLink({\n      recipientPubkey: pubkey,\n      messageOptions: {\n        locale: req.locale, // From i18n middleware\n        variables: {\n          appName: 'YourApp',\n          username: req.body.username\n        }\n      }\n    });\n\n    if (result.success) {\n      res.json({ \n        message: 'Magic link sent successfully',\n        expiresIn: '15 minutes'\n      });\n    }\n  } catch (error) {\n    if (error instanceof NostrError) {\n      // Handle specific Nostr-related errors\n      res.status(400).json({ \n        error: error.message,\n        code: error.code \n      });\n    } else {\n      // Handle unexpected errors\n      res.status(500).json({ \n        error: 'Failed to send magic link' \n      });\n    }\n  }\n});\n```\n\n## Advanced Usage\n\n### Custom Error Handling\n\n```typescript\ntry {\n  const result = await magicLink.sendMagicLink({\n    recipientPubkey: pubkey,\n    messageOptions: { locale: 'en' }\n  });\n  \n  if (!result.success) {\n    switch (result.error.code) {\n      case 'RELAY_CONNECTION_FAILED':\n        // Attempt reconnection or use fallback relay\n        await magicLink.reconnect();\n        break;\n      case 'ENCRYPTION_FAILED':\n        // Log encryption errors for debugging\n        logger.error('Encryption failed:', result.error);\n        break;\n      case 'INVALID_PUBKEY':\n        // Handle invalid recipient public key\n        throw new UserError('Invalid recipient');\n        break;\n    }\n  }\n} catch (error) {\n  // Handle other errors\n}\n```\n\n### Multi-Language Support\n\n```typescript\n// Arabic (RTL) example\nconst result = await magicLink.sendMagicLink({\n  recipientPubkey: pubkey,\n  messageOptions: {\n    locale: 'ar',\n    // Optional: Override default template\n    template: {\n      subject: 'تسجيل الدخول إلى {{appName}}',\n      body: 'انقر فوق هذا الرابط الآمن لتسجيل الدخول: {{link}}'\n    },\n    variables: {\n      appName: 'تطبيقك',\n      username: 'المستخدم'\n    }\n  }\n});\n```\n\n### Custom Token Generation\n\n```typescript\nconst magicLink = createNostrMagicLink({\n  // ... other config\n  magicLink: {\n    verifyUrl: 'https://your-app.com/verify',\n    token: async (recipientPubkey: string) =\u003e {\n      // Generate a secure, short-lived token\n      const token = await generateJWT({\n        sub: recipientPubkey,\n        exp: Math.floor(Date.now() / 1000) + (15 * 60), // 15 minutes\n        jti: crypto.randomUUID(),\n        iss: 'your-app'\n      });\n      \n      // Optional: Store token in database for verification\n      await db.tokens.create({\n        token,\n        pubkey: recipientPubkey,\n        expiresAt: new Date(Date.now() + 15 * 60 * 1000)\n      });\n      \n      return token;\n    }\n  }\n});\n```\n\n### Relay Management\n\n```typescript\nconst magicLink = createNostrMagicLink({\n  nostr: {\n    privateKey: process.env.NOSTR_PRIVATE_KEY,\n    relayUrls: ['wss://relay1.com', 'wss://relay2.com'],\n    // Advanced relay options\n    relayOptions: {\n      retryAttempts: 3,\n      retryDelay: 1000,\n      timeout: 5000,\n      onError: async (error, relay) =\u003e {\n        logger.error(`Relay ${relay} error:`, error);\n        // Optionally switch to backup relay\n        await magicLink.addRelay('wss://backup-relay.com');\n      }\n    }\n  }\n});\n\n// Monitor relay status\nmagicLink.on('relay:connected', (relay) =\u003e {\n  logger.info(`Connected to relay: ${relay}`);\n});\n\nmagicLink.on('relay:disconnected', (relay) =\u003e {\n  logger.warn(`Disconnected from relay: ${relay}`);\n});\n```\n\n## Security Best Practices\n\n1. **Private Key Management**\n   - Never hardcode private keys\n   - Use secure environment variables\n   - Rotate keys periodically\n\n```typescript\n// Load private key securely\nconst privateKey = await loadPrivateKeyFromSecureStore();\nif (!privateKey) {\n  throw new Error('Missing required private key');\n}\n```\n\n2. **Token Security**\n   - Use short expiration times (15-30 minutes)\n   - Include necessary claims (sub, exp, jti)\n   - Store tokens securely for verification\n\n3. **Error Handling**\n   - Never expose internal errors to users\n   - Log errors securely\n   - Implement rate limiting\n\n4. **Relay Security**\n   - Use trusted relays\n   - Implement connection timeouts\n   - Handle connection errors gracefully\n\n## Supported Languages\n\nThe library includes built-in support for:\n- 🇺🇸 English (en)\n- 🇪🇸 Spanish (es)\n- 🇫🇷 French (fr)\n- 🇯🇵 Japanese (ja)\n- 🇰🇷 Korean (ko)\n- 🇨🇳 Chinese (zh)\n- 🇧🇷 Portuguese (pt)\n- 🇷🇺 Russian (ru)\n- 🇸🇦 Arabic (ar) - with RTL support\n\n## Contributing\n\nSee [CONTRIBUTING.md](CONTRIBUTING.md) for development setup and guidelines.\n\n## License\n\nMIT © [vveerrgg](https://github.com/vveerrgg)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhumanjavaenterprises%2Fnostr-dm-magiclink-utils","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhumanjavaenterprises%2Fnostr-dm-magiclink-utils","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhumanjavaenterprises%2Fnostr-dm-magiclink-utils/lists"}