@micro-cms/crypto-auth-node 1.0.32 → 1.0.34

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,33 +1,57 @@
1
1
  # @micro-cms/crypto-auth-node
2
2
 
3
- A Micro-CMS backend module for verifying crypto signatures and managing nonces for wallet-based authentication.
3
+ A backend Micro-CMS module for verifying crypto wallet signatures (Solana and EVM) and issuing JWT tokens.
4
4
 
5
5
  ## Features
6
- - **Capabilities**: Provides `authentication` and `route-provider` abilities.
7
- - **Multichain**: Verifies Solana and EVM (Ethereum/Base) signatures.
8
- - **Provisioning**: Supports `onVerified` hooks for user database provisioning.
6
+ - **Multi-Chain Verification**: Supports Solana (ed25519) and EVM (personal_sign) signature verification.
7
+ - **Nonce Management**: Generates and manages short-lived nonces to prevent replay attacks.
8
+ - **JWT Issuance**: Issues signed JWT tokens upon successful verification.
9
+ - **Composable**: Integrates seamlessly with `@micro-cms/core` and `@micro-cms/express-adapter`.
10
+
11
+ ## Installation
12
+ ```bash
13
+ pnpm add @micro-cms/crypto-auth-node
14
+ ```
9
15
 
10
- ## Integration
16
+ ## Usage
11
17
 
12
- ### Initialization
18
+ ### Integrating with Micro-CMS App
13
19
  ```typescript
14
20
  import { createApp } from '@micro-cms/core';
21
+ import { bindExpressRoutes } from '@micro-cms/express-adapter';
15
22
  import CryptoAuthModule from '@micro-cms/crypto-auth-node';
23
+ import express from 'express';
16
24
 
25
+ const app = express();
17
26
  const cms = createApp();
18
27
 
19
28
  cms.use(CryptoAuthModule, {
20
- jwtSecret: 'your-secret',
21
- onVerified: async ({ address, network, req }) => {
22
- // Look up or create user in DB
23
- return { id: 1, email: `${address}@${network}.com` };
29
+ jwtSecret: process.env.JWT_SECRET,
30
+ jwtExpiresIn: '24h',
31
+ // Optional hook to provision or find users in your DB
32
+ onVerified: async ({ address, network, token, req }) => {
33
+ // Return user object to be included in the response
34
+ return { id: 1, address, network };
24
35
  }
25
36
  });
26
37
 
27
- await cms.start();
38
+ cms.start().then(() => {
39
+ bindExpressRoutes({ app, cms });
40
+ });
28
41
  ```
29
42
 
30
- ### Endpoints (Automatic)
31
- The module provides the following endpoints via the `route-provider` capability:
32
- - `POST /api/auth/crypto/nonce`: Generates a unique nonce for signing.
33
- - `POST /api/auth/crypto/verify`: Verifies the signature and returns a JWT.
43
+ ## Provided Endpoints
44
+ When bound to an Express app, this module provides:
45
+
46
+ - `POST /api/auth/crypto/nonce`: Generates a nonce for a wallet address.
47
+ - Body: `{ "address": "..." }`
48
+ - `POST /api/auth/crypto/verify`: Verifies a signature and returns a token.
49
+ - Body: `{ "address": "...", "signature": "...", "network": "solana" | "evm" }`
50
+
51
+ ## Configuration Options
52
+
53
+ | Option | Type | Default | Description |
54
+ | :--- | :--- | :--- | :--- |
55
+ | `jwtSecret` | `string` | 'fallback-secret' | Secret used to sign JWT tokens |
56
+ | `jwtExpiresIn` | `string` | '24h' | JWT expiration time (e.g., '1h', '7d') |
57
+ | `onVerified` | `Function` | - | Async hook called after successful verification |
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- "use strict";var j=Object.create;var g=Object.defineProperty;var C=Object.getOwnPropertyDescriptor;var M=Object.getOwnPropertyNames;var x=Object.getPrototypeOf,E=Object.prototype.hasOwnProperty;var k=(n,e)=>{for(var r in e)g(n,r,{get:e[r],enumerable:!0})},h=(n,e,r,o)=>{if(e&&typeof e=="object"||typeof e=="function")for(let t of M(e))!E.call(n,t)&&t!==r&&g(n,t,{get:()=>e[t],enumerable:!(o=C(e,t))||o.enumerable});return n};var p=(n,e,r)=>(r=n!=null?j(x(n)):{},h(e||!n||!n.__esModule?g(r,"default",{value:n,enumerable:!0}):r,n)),P=n=>h(g({},"__esModule",{value:!0}),n);var b={};k(b,{default:()=>V});module.exports=P(b);var d=p(require("bs58")),v=require("viem"),w=p(require("jsonwebtoken")),f=class{constructor(e){this.options=e;this.nonces=new Map}generateNonce(e){let r=Math.floor(Math.random()*1e6).toString();return this.nonces.set(e.toLowerCase(),r),r}async verifySolana(e,r){let o=this.nonces.get(e.toLowerCase());if(!o)return null;try{let t=new TextEncoder().encode(`Sign this message to authenticate: ${o}`),l=d.default.decode(r),i=d.default.decode(e);if(await this.verifySolanaSignature(t,l,i))return this.nonces.delete(e.toLowerCase()),this.issueToken(e,"solana")}catch(t){console.error("Solana verification error:",t)}return null}async verifySolanaSignature(e,r,o){let{sign:t}=await import("tweetnacl");return t.detached.verify(e,r,o)}async verifyEVM(e,r){let o=this.nonces.get(e.toLowerCase());if(!o)return null;try{let t=`Sign this message to authenticate: ${o}`;if(await(0,v.verifyMessage)({address:e,message:t,signature:r}))return this.nonces.delete(e.toLowerCase()),this.issueToken(e,"evm")}catch(t){console.error("EVM verification error:",t)}return null}issueToken(e,r){let o={expiresIn:this.options.jwtExpiresIn||"24h"};return w.default.sign({address:e,network:r},this.options.jwtSecret,o)}};var V={manifest:{name:"@micro-cms/crypto-auth-node",version:"1.0.0",provides:["authentication","route-provider"],requires:[]},async load(n){let{jwtSecret:e,jwtExpiresIn:r,onVerified:o}=n.config;e||console.warn("[@micro-cms/crypto-auth-node] jwtSecret not provided in config.");let t=new f({jwtSecret:e||"fallback-secret",jwtExpiresIn:r});n.runtime.register("authentication",{verifySolana:(i,s)=>t.verifySolana(i,s),verifyEVM:(i,s)=>t.verifyEVM(i,s),generateNonce:i=>t.generateNonce(i)});let l=[{method:"POST",path:"/api/auth/crypto/nonce",handler:async(i,s)=>{let{address:a}=i.body;if(!a)return s.status(400).json({error:"Address required"});let c=t.generateNonce(a);s.json({nonce:c})}},{method:"POST",path:"/api/auth/crypto/verify",handler:async(i,s)=>{let{address:a,signature:c,network:y}=i.body;if(!a||!c||!y)return s.status(400).json({error:"Address, signature and network required"});let u=null;if(y==="solana"?u=await t.verifySolana(a,c):u=await t.verifyEVM(a,c),u){let m=null;if(o)try{m=await o({address:a,network:y,token:u,req:i})}catch(S){console.error("[@micro-cms/crypto-auth-node] onVerified hook failed:",S.message)}s.json({token:u,user:m})}else s.status(401).json({error:"Verification failed"})}}];n.runtime.register("route-provider",{getRoutes:()=>l})}};
1
+ "use strict";var j=Object.create;var f=Object.defineProperty;var M=Object.getOwnPropertyDescriptor;var E=Object.getOwnPropertyNames;var x=Object.getPrototypeOf,C=Object.prototype.hasOwnProperty;var V=(r,e)=>{for(var n in e)f(r,n,{get:e[n],enumerable:!0})},p=(r,e,n,o)=>{if(e&&typeof e=="object"||typeof e=="function")for(let t of E(e))!C.call(r,t)&&t!==n&&f(r,t,{get:()=>e[t],enumerable:!(o=M(e,t))||o.enumerable});return r};var y=(r,e,n)=>(n=r!=null?j(x(r)):{},p(e||!r||!r.__esModule?f(n,"default",{value:r,enumerable:!0}):n,r)),k=r=>p(f({},"__esModule",{value:!0}),r);var b={};V(b,{default:()=>P});module.exports=k(b);var m=y(require("bs58")),v=require("viem"),w=y(require("jsonwebtoken")),d=class{constructor(e){this.options=e;this.nonces=new Map}generateNonce(e){let n=Math.floor(Math.random()*1e6).toString();return this.nonces.set(e,n),n}async verifySolana(e,n){let o=this.nonces.get(e);if(!o)return console.warn("[@micro-cms/crypto-auth-node] No nonce found for address:",e),null;try{let t=new TextEncoder().encode(`Sign this message to authenticate: ${o}`),s=m.default.decode(n),i=m.default.decode(e);if(await this.verifySolanaSignature(t,s,i))return this.nonces.delete(e),this.issueToken(e,"solana")}catch(t){console.error("Solana verification error:",t)}return null}async verifySolanaSignature(e,n,o){try{let t=await import("tweetnacl"),s=t.sign||t.default?.sign;if(!s||!s.detached)throw new Error("tweetnacl sign.detached not found");return s.detached.verify(e,n,o)}catch(t){return console.error("[@micro-cms/crypto-auth-node] Internal verification error:",t.message),!1}}async verifyEVM(e,n){let o=e.toLowerCase(),t=this.nonces.get(o);if(!t)return console.warn("[@micro-cms/crypto-auth-node] No nonce found for EVM address:",o),null;try{let s=`Sign this message to authenticate: ${t}`;if(await(0,v.verifyMessage)({address:e,message:s,signature:n}))return this.nonces.delete(o),this.issueToken(e,"evm")}catch(s){console.error("EVM verification error:",s)}return null}issueToken(e,n){let o={expiresIn:this.options.jwtExpiresIn||"24h"};return w.default.sign({address:e,network:n},this.options.jwtSecret,o)}};var P={manifest:{name:"@micro-cms/crypto-auth-node",version:"1.0.0",provides:["authentication","route-provider"],requires:[]},async load(r){let{jwtSecret:e,jwtExpiresIn:n,onVerified:o}=r.config;e||console.warn("[@micro-cms/crypto-auth-node] jwtSecret not provided in config.");let t=new d({jwtSecret:e||"fallback-secret",jwtExpiresIn:n});r.runtime.register("authentication",{verifySolana:(i,a)=>t.verifySolana(i,a),verifyEVM:(i,a)=>t.verifyEVM(i,a),generateNonce:i=>t.generateNonce(i)});let s=[{method:"POST",path:"/api/auth/crypto/nonce",handler:async(i,a)=>{let{address:c}=i.body;if(!c)return a.status(400).json({error:"Address required"});let u=t.generateNonce(c);a.json({nonce:u})}},{method:"POST",path:"/api/auth/crypto/verify",handler:async(i,a)=>{let{address:c,signature:u,network:g}=i.body;if(!c||!u||!g)return a.status(400).json({error:"Address, signature and network required"});let l=null;if(g==="solana"?l=await t.verifySolana(c,u):l=await t.verifyEVM(c,u),l){let h=null;if(o)try{h=await o({address:c,network:g,token:l,req:i})}catch(S){console.error("[@micro-cms/crypto-auth-node] onVerified hook failed:",S.message)}a.json({token:l,user:h})}else a.status(401).json({error:"Verification failed"})}}];r.runtime.register("route-provider",{getRoutes:()=>s})}};
2
2
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/auth.service.ts"],"sourcesContent":["import { CmsContext, CmsModule, RouteProvider, RouteDefinition } from '@micro-cms/types';\nimport { CryptoAuthService } from './auth.service';\n\nexport default {\n manifest: {\n name: '@micro-cms/crypto-auth-node',\n version: '1.0.0',\n provides: ['authentication', 'route-provider'],\n requires: []\n },\n\n async load(context: CmsContext) {\n const { jwtSecret, jwtExpiresIn, onVerified } = context.config;\n \n if (!jwtSecret) {\n console.warn('[@micro-cms/crypto-auth-node] jwtSecret not provided in config.');\n }\n\n const authService = new CryptoAuthService({ \n jwtSecret: jwtSecret || 'fallback-secret',\n jwtExpiresIn \n });\n\n context.runtime.register('authentication', {\n verifySolana: (address: string, signature: string) => authService.verifySolana(address, signature),\n verifyEVM: (address: string, signature: string) => authService.verifyEVM(address, signature),\n generateNonce: (address: string) => authService.generateNonce(address)\n });\n\n const routes: RouteDefinition[] = [\n {\n method: 'POST',\n path: '/api/auth/crypto/nonce',\n handler: async (req: any, res: any) => {\n const { address } = req.body;\n if (!address) return res.status(400).json({ error: 'Address required' });\n const nonce = authService.generateNonce(address);\n res.json({ nonce });\n }\n },\n {\n method: 'POST',\n path: '/api/auth/crypto/verify',\n handler: async (req: any, res: any) => {\n const { address, signature, network } = req.body;\n if (!address || !signature || !network) {\n return res.status(400).json({ error: 'Address, signature and network required' });\n }\n\n let token: string | null = null;\n if (network === 'solana') {\n token = await authService.verifySolana(address, signature);\n } else {\n token = await authService.verifyEVM(address, signature);\n }\n\n if (token) {\n // Optional hook to provision user in DB\n let user = null;\n if (onVerified) {\n try {\n user = await onVerified({ address, network, token, req });\n } catch (e: any) {\n console.error('[@micro-cms/crypto-auth-node] onVerified hook failed:', e.message);\n }\n }\n res.json({ token, user });\n } else {\n res.status(401).json({ error: 'Verification failed' });\n }\n }\n }\n ];\n\n context.runtime.register('route-provider', {\n getRoutes: () => routes\n } as RouteProvider);\n }\n} as CmsModule;\n","import { PublicKey } from '@solana/web3.js';\nimport bs58 from 'bs58';\nimport { verifyMessage } from 'viem';\nimport jwt from 'jsonwebtoken';\n\nexport interface CryptoAuthOptions {\n jwtSecret: string;\n jwtExpiresIn?: string;\n}\n\nexport class CryptoAuthService {\n private nonces = new Map<string, string>();\n\n constructor(private options: CryptoAuthOptions) {}\n\n generateNonce(address: string): string {\n const nonce = Math.floor(Math.random() * 1000000).toString();\n this.nonces.set(address.toLowerCase(), nonce);\n return nonce;\n }\n\n async verifySolana(address: string, signature: string): Promise<string | null> {\n const nonce = this.nonces.get(address.toLowerCase());\n if (!nonce) return null;\n\n try {\n const message = new TextEncoder().encode(`Sign this message to authenticate: ${nonce}`);\n const signatureUint8 = bs58.decode(signature);\n const publicKeyUint8 = bs58.decode(address);\n \n const verified = await this.verifySolanaSignature(message, signatureUint8, publicKeyUint8);\n \n if (verified) {\n this.nonces.delete(address.toLowerCase());\n return this.issueToken(address, 'solana');\n }\n } catch (e) {\n console.error('Solana verification error:', e);\n }\n return null;\n }\n\n private async verifySolanaSignature(message: Uint8Array, signature: Uint8Array, publicKey: Uint8Array): Promise<boolean> {\n // In a real implementation, use nacl or @solana/web3.js if it provides it directly\n // For now, let's assume we use tweetnacl via @solana/web3.js dependency or similar\n const { sign } = await import('tweetnacl');\n return sign.detached.verify(message, signature, publicKey);\n }\n\n async verifyEVM(address: string, signature: string): Promise<string | null> {\n const nonce = this.nonces.get(address.toLowerCase());\n if (!nonce) return null;\n\n try {\n const message = `Sign this message to authenticate: ${nonce}`;\n const verified = await verifyMessage({\n address: address as `0x${string}`,\n message,\n signature: signature as `0x${string}`,\n });\n\n if (verified) {\n this.nonces.delete(address.toLowerCase());\n return this.issueToken(address, 'evm');\n }\n } catch (e) {\n console.error('EVM verification error:', e);\n }\n return null;\n }\n\n private issueToken(address: string, network: string): string {\n const options: any = { \n expiresIn: this.options.jwtExpiresIn || '24h' \n };\n return jwt.sign(\n { address, network },\n this.options.jwtSecret,\n options\n );\n }\n}\n"],"mappings":"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","names":["index_exports","__export","index_default","__toCommonJS","import_bs58","import_viem","import_jsonwebtoken","CryptoAuthService","options","address","nonce","signature","message","signatureUint8","bs58","publicKeyUint8","e","publicKey","sign","network","jwt","index_default","context","jwtSecret","jwtExpiresIn","onVerified","authService","CryptoAuthService","address","signature","routes","req","res","nonce","network","token","user","e"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/auth.service.ts"],"sourcesContent":["import { CmsContext, CmsModule, RouteProvider, RouteDefinition } from '@micro-cms/types';\nimport { CryptoAuthService } from './auth.service';\n\nexport default {\n manifest: {\n name: '@micro-cms/crypto-auth-node',\n version: '1.0.0',\n provides: ['authentication', 'route-provider'],\n requires: []\n },\n\n async load(context: CmsContext) {\n const { jwtSecret, jwtExpiresIn, onVerified } = context.config;\n \n if (!jwtSecret) {\n console.warn('[@micro-cms/crypto-auth-node] jwtSecret not provided in config.');\n }\n\n const authService = new CryptoAuthService({ \n jwtSecret: jwtSecret || 'fallback-secret',\n jwtExpiresIn \n });\n\n context.runtime.register('authentication', {\n verifySolana: (address: string, signature: string) => authService.verifySolana(address, signature),\n verifyEVM: (address: string, signature: string) => authService.verifyEVM(address, signature),\n generateNonce: (address: string) => authService.generateNonce(address)\n });\n\n const routes: RouteDefinition[] = [\n {\n method: 'POST',\n path: '/api/auth/crypto/nonce',\n handler: async (req: any, res: any) => {\n const { address } = req.body;\n if (!address) return res.status(400).json({ error: 'Address required' });\n const nonce = authService.generateNonce(address);\n res.json({ nonce });\n }\n },\n {\n method: 'POST',\n path: '/api/auth/crypto/verify',\n handler: async (req: any, res: any) => {\n const { address, signature, network } = req.body;\n if (!address || !signature || !network) {\n return res.status(400).json({ error: 'Address, signature and network required' });\n }\n\n let token: string | null = null;\n if (network === 'solana') {\n token = await authService.verifySolana(address, signature);\n } else {\n token = await authService.verifyEVM(address, signature);\n }\n\n if (token) {\n // Optional hook to provision user in DB\n let user = null;\n if (onVerified) {\n try {\n user = await onVerified({ address, network, token, req });\n } catch (e: any) {\n console.error('[@micro-cms/crypto-auth-node] onVerified hook failed:', e.message);\n }\n }\n res.json({ token, user });\n } else {\n res.status(401).json({ error: 'Verification failed' });\n }\n }\n }\n ];\n\n context.runtime.register('route-provider', {\n getRoutes: () => routes\n } as RouteProvider);\n }\n} as CmsModule;\n","import { PublicKey } from '@solana/web3.js';\nimport bs58 from 'bs58';\nimport { verifyMessage } from 'viem';\nimport jwt from 'jsonwebtoken';\n\nexport interface CryptoAuthOptions {\n jwtSecret: string;\n jwtExpiresIn?: string;\n}\n\nexport class CryptoAuthService {\n private nonces = new Map<string, string>();\n\n constructor(private options: CryptoAuthOptions) {}\n\n generateNonce(address: string): string {\n const nonce = Math.floor(Math.random() * 1000000).toString();\n // Use raw address for map key to handle case-sensitive Solana addresses\n this.nonces.set(address, nonce);\n return nonce;\n }\n\n async verifySolana(address: string, signature: string): Promise<string | null> {\n const nonce = this.nonces.get(address);\n if (!nonce) {\n console.warn('[@micro-cms/crypto-auth-node] No nonce found for address:', address);\n return null;\n }\n\n try {\n const message = new TextEncoder().encode(`Sign this message to authenticate: ${nonce}`);\n const signatureUint8 = bs58.decode(signature);\n const publicKeyUint8 = bs58.decode(address);\n \n const verified = await this.verifySolanaSignature(message, signatureUint8, publicKeyUint8);\n \n if (verified) {\n this.nonces.delete(address);\n return this.issueToken(address, 'solana');\n }\n } catch (e) {\n console.error('Solana verification error:', e);\n }\n return null;\n }\n\n private async verifySolanaSignature(message: Uint8Array, signature: Uint8Array, publicKey: Uint8Array): Promise<boolean> {\n try {\n const nacl = await import('tweetnacl');\n // Handle both ES module and CommonJS styles\n const sign = nacl.sign || (nacl as any).default?.sign;\n \n if (!sign || !sign.detached) {\n throw new Error('tweetnacl sign.detached not found');\n }\n \n return sign.detached.verify(message, signature, publicKey);\n } catch (e: any) {\n console.error('[@micro-cms/crypto-auth-node] Internal verification error:', e.message);\n return false;\n }\n }\n\n async verifyEVM(address: string, signature: string): Promise<string | null> {\n const addrKey = address.toLowerCase();\n const nonce = this.nonces.get(addrKey);\n if (!nonce) {\n console.warn('[@micro-cms/crypto-auth-node] No nonce found for EVM address:', addrKey);\n return null;\n }\n\n try {\n const message = `Sign this message to authenticate: ${nonce}`;\n const verified = await verifyMessage({\n address: address as `0x${string}`,\n message,\n signature: signature as `0x${string}`,\n });\n\n if (verified) {\n this.nonces.delete(addrKey);\n return this.issueToken(address, 'evm');\n }\n } catch (e) {\n console.error('EVM verification error:', e);\n }\n return null;\n }\n\n private issueToken(address: string, network: string): string {\n const options: any = { \n expiresIn: this.options.jwtExpiresIn || '24h' \n };\n return jwt.sign(\n { address, network },\n this.options.jwtSecret,\n options\n );\n }\n}\n"],"mappings":"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","names":["index_exports","__export","index_default","__toCommonJS","import_bs58","import_viem","import_jsonwebtoken","CryptoAuthService","options","address","nonce","signature","message","signatureUint8","bs58","publicKeyUint8","e","publicKey","nacl","sign","addrKey","network","jwt","index_default","context","jwtSecret","jwtExpiresIn","onVerified","authService","CryptoAuthService","address","signature","routes","req","res","nonce","network","token","user","e"]}
package/dist/index.mjs CHANGED
@@ -1,2 +1,2 @@
1
- import p from"bs58";import{verifyMessage as m}from"viem";import h from"jsonwebtoken";var g=class{constructor(e){this.options=e;this.nonces=new Map}generateNonce(e){let r=Math.floor(Math.random()*1e6).toString();return this.nonces.set(e.toLowerCase(),r),r}async verifySolana(e,r){let n=this.nonces.get(e.toLowerCase());if(!n)return null;try{let t=new TextEncoder().encode(`Sign this message to authenticate: ${n}`),l=p.decode(r),o=p.decode(e);if(await this.verifySolanaSignature(t,l,o))return this.nonces.delete(e.toLowerCase()),this.issueToken(e,"solana")}catch(t){console.error("Solana verification error:",t)}return null}async verifySolanaSignature(e,r,n){let{sign:t}=await import("tweetnacl");return t.detached.verify(e,r,n)}async verifyEVM(e,r){let n=this.nonces.get(e.toLowerCase());if(!n)return null;try{let t=`Sign this message to authenticate: ${n}`;if(await m({address:e,message:t,signature:r}))return this.nonces.delete(e.toLowerCase()),this.issueToken(e,"evm")}catch(t){console.error("EVM verification error:",t)}return null}issueToken(e,r){let n={expiresIn:this.options.jwtExpiresIn||"24h"};return h.sign({address:e,network:r},this.options.jwtSecret,n)}};var M={manifest:{name:"@micro-cms/crypto-auth-node",version:"1.0.0",provides:["authentication","route-provider"],requires:[]},async load(u){let{jwtSecret:e,jwtExpiresIn:r,onVerified:n}=u.config;e||console.warn("[@micro-cms/crypto-auth-node] jwtSecret not provided in config.");let t=new g({jwtSecret:e||"fallback-secret",jwtExpiresIn:r});u.runtime.register("authentication",{verifySolana:(o,i)=>t.verifySolana(o,i),verifyEVM:(o,i)=>t.verifyEVM(o,i),generateNonce:o=>t.generateNonce(o)});let l=[{method:"POST",path:"/api/auth/crypto/nonce",handler:async(o,i)=>{let{address:s}=o.body;if(!s)return i.status(400).json({error:"Address required"});let a=t.generateNonce(s);i.json({nonce:a})}},{method:"POST",path:"/api/auth/crypto/verify",handler:async(o,i)=>{let{address:s,signature:a,network:f}=o.body;if(!s||!a||!f)return i.status(400).json({error:"Address, signature and network required"});let c=null;if(f==="solana"?c=await t.verifySolana(s,a):c=await t.verifyEVM(s,a),c){let y=null;if(n)try{y=await n({address:s,network:f,token:c,req:o})}catch(d){console.error("[@micro-cms/crypto-auth-node] onVerified hook failed:",d.message)}i.json({token:c,user:y})}else i.status(401).json({error:"Verification failed"})}}];u.runtime.register("route-provider",{getRoutes:()=>l})}};export{M as default};
1
+ import y from"bs58";import{verifyMessage as h}from"viem";import p from"jsonwebtoken";var f=class{constructor(e){this.options=e;this.nonces=new Map}generateNonce(e){let o=Math.floor(Math.random()*1e6).toString();return this.nonces.set(e,o),o}async verifySolana(e,o){let n=this.nonces.get(e);if(!n)return console.warn("[@micro-cms/crypto-auth-node] No nonce found for address:",e),null;try{let t=new TextEncoder().encode(`Sign this message to authenticate: ${n}`),i=y.decode(o),r=y.decode(e);if(await this.verifySolanaSignature(t,i,r))return this.nonces.delete(e),this.issueToken(e,"solana")}catch(t){console.error("Solana verification error:",t)}return null}async verifySolanaSignature(e,o,n){try{let t=await import("tweetnacl"),i=t.sign||t.default?.sign;if(!i||!i.detached)throw new Error("tweetnacl sign.detached not found");return i.detached.verify(e,o,n)}catch(t){return console.error("[@micro-cms/crypto-auth-node] Internal verification error:",t.message),!1}}async verifyEVM(e,o){let n=e.toLowerCase(),t=this.nonces.get(n);if(!t)return console.warn("[@micro-cms/crypto-auth-node] No nonce found for EVM address:",n),null;try{let i=`Sign this message to authenticate: ${t}`;if(await h({address:e,message:i,signature:o}))return this.nonces.delete(n),this.issueToken(e,"evm")}catch(i){console.error("EVM verification error:",i)}return null}issueToken(e,o){let n={expiresIn:this.options.jwtExpiresIn||"24h"};return p.sign({address:e,network:o},this.options.jwtSecret,n)}};var E={manifest:{name:"@micro-cms/crypto-auth-node",version:"1.0.0",provides:["authentication","route-provider"],requires:[]},async load(l){let{jwtSecret:e,jwtExpiresIn:o,onVerified:n}=l.config;e||console.warn("[@micro-cms/crypto-auth-node] jwtSecret not provided in config.");let t=new f({jwtSecret:e||"fallback-secret",jwtExpiresIn:o});l.runtime.register("authentication",{verifySolana:(r,s)=>t.verifySolana(r,s),verifyEVM:(r,s)=>t.verifyEVM(r,s),generateNonce:r=>t.generateNonce(r)});let i=[{method:"POST",path:"/api/auth/crypto/nonce",handler:async(r,s)=>{let{address:a}=r.body;if(!a)return s.status(400).json({error:"Address required"});let c=t.generateNonce(a);s.json({nonce:c})}},{method:"POST",path:"/api/auth/crypto/verify",handler:async(r,s)=>{let{address:a,signature:c,network:d}=r.body;if(!a||!c||!d)return s.status(400).json({error:"Address, signature and network required"});let u=null;if(d==="solana"?u=await t.verifySolana(a,c):u=await t.verifyEVM(a,c),u){let g=null;if(n)try{g=await n({address:a,network:d,token:u,req:r})}catch(m){console.error("[@micro-cms/crypto-auth-node] onVerified hook failed:",m.message)}s.json({token:u,user:g})}else s.status(401).json({error:"Verification failed"})}}];l.runtime.register("route-provider",{getRoutes:()=>i})}};export{E as default};
2
2
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/auth.service.ts","../src/index.ts"],"sourcesContent":["import { PublicKey } from '@solana/web3.js';\nimport bs58 from 'bs58';\nimport { verifyMessage } from 'viem';\nimport jwt from 'jsonwebtoken';\n\nexport interface CryptoAuthOptions {\n jwtSecret: string;\n jwtExpiresIn?: string;\n}\n\nexport class CryptoAuthService {\n private nonces = new Map<string, string>();\n\n constructor(private options: CryptoAuthOptions) {}\n\n generateNonce(address: string): string {\n const nonce = Math.floor(Math.random() * 1000000).toString();\n this.nonces.set(address.toLowerCase(), nonce);\n return nonce;\n }\n\n async verifySolana(address: string, signature: string): Promise<string | null> {\n const nonce = this.nonces.get(address.toLowerCase());\n if (!nonce) return null;\n\n try {\n const message = new TextEncoder().encode(`Sign this message to authenticate: ${nonce}`);\n const signatureUint8 = bs58.decode(signature);\n const publicKeyUint8 = bs58.decode(address);\n \n const verified = await this.verifySolanaSignature(message, signatureUint8, publicKeyUint8);\n \n if (verified) {\n this.nonces.delete(address.toLowerCase());\n return this.issueToken(address, 'solana');\n }\n } catch (e) {\n console.error('Solana verification error:', e);\n }\n return null;\n }\n\n private async verifySolanaSignature(message: Uint8Array, signature: Uint8Array, publicKey: Uint8Array): Promise<boolean> {\n // In a real implementation, use nacl or @solana/web3.js if it provides it directly\n // For now, let's assume we use tweetnacl via @solana/web3.js dependency or similar\n const { sign } = await import('tweetnacl');\n return sign.detached.verify(message, signature, publicKey);\n }\n\n async verifyEVM(address: string, signature: string): Promise<string | null> {\n const nonce = this.nonces.get(address.toLowerCase());\n if (!nonce) return null;\n\n try {\n const message = `Sign this message to authenticate: ${nonce}`;\n const verified = await verifyMessage({\n address: address as `0x${string}`,\n message,\n signature: signature as `0x${string}`,\n });\n\n if (verified) {\n this.nonces.delete(address.toLowerCase());\n return this.issueToken(address, 'evm');\n }\n } catch (e) {\n console.error('EVM verification error:', e);\n }\n return null;\n }\n\n private issueToken(address: string, network: string): string {\n const options: any = { \n expiresIn: this.options.jwtExpiresIn || '24h' \n };\n return jwt.sign(\n { address, network },\n this.options.jwtSecret,\n options\n );\n }\n}\n","import { CmsContext, CmsModule, RouteProvider, RouteDefinition } from '@micro-cms/types';\nimport { CryptoAuthService } from './auth.service';\n\nexport default {\n manifest: {\n name: '@micro-cms/crypto-auth-node',\n version: '1.0.0',\n provides: ['authentication', 'route-provider'],\n requires: []\n },\n\n async load(context: CmsContext) {\n const { jwtSecret, jwtExpiresIn, onVerified } = context.config;\n \n if (!jwtSecret) {\n console.warn('[@micro-cms/crypto-auth-node] jwtSecret not provided in config.');\n }\n\n const authService = new CryptoAuthService({ \n jwtSecret: jwtSecret || 'fallback-secret',\n jwtExpiresIn \n });\n\n context.runtime.register('authentication', {\n verifySolana: (address: string, signature: string) => authService.verifySolana(address, signature),\n verifyEVM: (address: string, signature: string) => authService.verifyEVM(address, signature),\n generateNonce: (address: string) => authService.generateNonce(address)\n });\n\n const routes: RouteDefinition[] = [\n {\n method: 'POST',\n path: '/api/auth/crypto/nonce',\n handler: async (req: any, res: any) => {\n const { address } = req.body;\n if (!address) return res.status(400).json({ error: 'Address required' });\n const nonce = authService.generateNonce(address);\n res.json({ nonce });\n }\n },\n {\n method: 'POST',\n path: '/api/auth/crypto/verify',\n handler: async (req: any, res: any) => {\n const { address, signature, network } = req.body;\n if (!address || !signature || !network) {\n return res.status(400).json({ error: 'Address, signature and network required' });\n }\n\n let token: string | null = null;\n if (network === 'solana') {\n token = await authService.verifySolana(address, signature);\n } else {\n token = await authService.verifyEVM(address, signature);\n }\n\n if (token) {\n // Optional hook to provision user in DB\n let user = null;\n if (onVerified) {\n try {\n user = await onVerified({ address, network, token, req });\n } catch (e: any) {\n console.error('[@micro-cms/crypto-auth-node] onVerified hook failed:', e.message);\n }\n }\n res.json({ token, user });\n } else {\n res.status(401).json({ error: 'Verification failed' });\n }\n }\n }\n ];\n\n context.runtime.register('route-provider', {\n getRoutes: () => routes\n } as RouteProvider);\n }\n} as CmsModule;\n"],"mappings":"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","names":["bs58","verifyMessage","jwt","CryptoAuthService","options","address","nonce","signature","message","signatureUint8","publicKeyUint8","e","publicKey","sign","network","index_default","context","jwtSecret","jwtExpiresIn","onVerified","authService","CryptoAuthService","address","signature","routes","req","res","nonce","network","token","user","e"]}
1
+ {"version":3,"sources":["../src/auth.service.ts","../src/index.ts"],"sourcesContent":["import { PublicKey } from '@solana/web3.js';\nimport bs58 from 'bs58';\nimport { verifyMessage } from 'viem';\nimport jwt from 'jsonwebtoken';\n\nexport interface CryptoAuthOptions {\n jwtSecret: string;\n jwtExpiresIn?: string;\n}\n\nexport class CryptoAuthService {\n private nonces = new Map<string, string>();\n\n constructor(private options: CryptoAuthOptions) {}\n\n generateNonce(address: string): string {\n const nonce = Math.floor(Math.random() * 1000000).toString();\n // Use raw address for map key to handle case-sensitive Solana addresses\n this.nonces.set(address, nonce);\n return nonce;\n }\n\n async verifySolana(address: string, signature: string): Promise<string | null> {\n const nonce = this.nonces.get(address);\n if (!nonce) {\n console.warn('[@micro-cms/crypto-auth-node] No nonce found for address:', address);\n return null;\n }\n\n try {\n const message = new TextEncoder().encode(`Sign this message to authenticate: ${nonce}`);\n const signatureUint8 = bs58.decode(signature);\n const publicKeyUint8 = bs58.decode(address);\n \n const verified = await this.verifySolanaSignature(message, signatureUint8, publicKeyUint8);\n \n if (verified) {\n this.nonces.delete(address);\n return this.issueToken(address, 'solana');\n }\n } catch (e) {\n console.error('Solana verification error:', e);\n }\n return null;\n }\n\n private async verifySolanaSignature(message: Uint8Array, signature: Uint8Array, publicKey: Uint8Array): Promise<boolean> {\n try {\n const nacl = await import('tweetnacl');\n // Handle both ES module and CommonJS styles\n const sign = nacl.sign || (nacl as any).default?.sign;\n \n if (!sign || !sign.detached) {\n throw new Error('tweetnacl sign.detached not found');\n }\n \n return sign.detached.verify(message, signature, publicKey);\n } catch (e: any) {\n console.error('[@micro-cms/crypto-auth-node] Internal verification error:', e.message);\n return false;\n }\n }\n\n async verifyEVM(address: string, signature: string): Promise<string | null> {\n const addrKey = address.toLowerCase();\n const nonce = this.nonces.get(addrKey);\n if (!nonce) {\n console.warn('[@micro-cms/crypto-auth-node] No nonce found for EVM address:', addrKey);\n return null;\n }\n\n try {\n const message = `Sign this message to authenticate: ${nonce}`;\n const verified = await verifyMessage({\n address: address as `0x${string}`,\n message,\n signature: signature as `0x${string}`,\n });\n\n if (verified) {\n this.nonces.delete(addrKey);\n return this.issueToken(address, 'evm');\n }\n } catch (e) {\n console.error('EVM verification error:', e);\n }\n return null;\n }\n\n private issueToken(address: string, network: string): string {\n const options: any = { \n expiresIn: this.options.jwtExpiresIn || '24h' \n };\n return jwt.sign(\n { address, network },\n this.options.jwtSecret,\n options\n );\n }\n}\n","import { CmsContext, CmsModule, RouteProvider, RouteDefinition } from '@micro-cms/types';\nimport { CryptoAuthService } from './auth.service';\n\nexport default {\n manifest: {\n name: '@micro-cms/crypto-auth-node',\n version: '1.0.0',\n provides: ['authentication', 'route-provider'],\n requires: []\n },\n\n async load(context: CmsContext) {\n const { jwtSecret, jwtExpiresIn, onVerified } = context.config;\n \n if (!jwtSecret) {\n console.warn('[@micro-cms/crypto-auth-node] jwtSecret not provided in config.');\n }\n\n const authService = new CryptoAuthService({ \n jwtSecret: jwtSecret || 'fallback-secret',\n jwtExpiresIn \n });\n\n context.runtime.register('authentication', {\n verifySolana: (address: string, signature: string) => authService.verifySolana(address, signature),\n verifyEVM: (address: string, signature: string) => authService.verifyEVM(address, signature),\n generateNonce: (address: string) => authService.generateNonce(address)\n });\n\n const routes: RouteDefinition[] = [\n {\n method: 'POST',\n path: '/api/auth/crypto/nonce',\n handler: async (req: any, res: any) => {\n const { address } = req.body;\n if (!address) return res.status(400).json({ error: 'Address required' });\n const nonce = authService.generateNonce(address);\n res.json({ nonce });\n }\n },\n {\n method: 'POST',\n path: '/api/auth/crypto/verify',\n handler: async (req: any, res: any) => {\n const { address, signature, network } = req.body;\n if (!address || !signature || !network) {\n return res.status(400).json({ error: 'Address, signature and network required' });\n }\n\n let token: string | null = null;\n if (network === 'solana') {\n token = await authService.verifySolana(address, signature);\n } else {\n token = await authService.verifyEVM(address, signature);\n }\n\n if (token) {\n // Optional hook to provision user in DB\n let user = null;\n if (onVerified) {\n try {\n user = await onVerified({ address, network, token, req });\n } catch (e: any) {\n console.error('[@micro-cms/crypto-auth-node] onVerified hook failed:', e.message);\n }\n }\n res.json({ token, user });\n } else {\n res.status(401).json({ error: 'Verification failed' });\n }\n }\n }\n ];\n\n context.runtime.register('route-provider', {\n getRoutes: () => routes\n } as RouteProvider);\n }\n} as CmsModule;\n"],"mappings":"AACA,OAAOA,MAAU,OACjB,OAAS,iBAAAC,MAAqB,OAC9B,OAAOC,MAAS,eAOT,IAAMC,EAAN,KAAwB,CAG7B,YAAoBC,EAA4B,CAA5B,aAAAA,EAFpB,KAAQ,OAAS,IAAI,GAE4B,CAEjD,cAAcC,EAAyB,CACrC,IAAMC,EAAQ,KAAK,MAAM,KAAK,OAAO,EAAI,GAAO,EAAE,SAAS,EAE3D,YAAK,OAAO,IAAID,EAASC,CAAK,EACvBA,CACT,CAEA,MAAM,aAAaD,EAAiBE,EAA2C,CAC7E,IAAMD,EAAQ,KAAK,OAAO,IAAID,CAAO,EACrC,GAAI,CAACC,EACH,eAAQ,KAAK,4DAA6DD,CAAO,EAC1E,KAGT,GAAI,CACF,IAAMG,EAAU,IAAI,YAAY,EAAE,OAAO,sCAAsCF,CAAK,EAAE,EAChFG,EAAiBT,EAAK,OAAOO,CAAS,EACtCG,EAAiBV,EAAK,OAAOK,CAAO,EAI1C,GAFiB,MAAM,KAAK,sBAAsBG,EAASC,EAAgBC,CAAc,EAGvF,YAAK,OAAO,OAAOL,CAAO,EACnB,KAAK,WAAWA,EAAS,QAAQ,CAE5C,OAASM,EAAG,CACV,QAAQ,MAAM,6BAA8BA,CAAC,CAC/C,CACA,OAAO,IACT,CAEA,MAAc,sBAAsBH,EAAqBD,EAAuBK,EAAyC,CACvH,GAAI,CACF,IAAMC,EAAO,KAAM,QAAO,WAAW,EAE/BC,EAAOD,EAAK,MAASA,EAAa,SAAS,KAEjD,GAAI,CAACC,GAAQ,CAACA,EAAK,SACjB,MAAM,IAAI,MAAM,mCAAmC,EAGrD,OAAOA,EAAK,SAAS,OAAON,EAASD,EAAWK,CAAS,CAC3D,OAASD,EAAQ,CACf,eAAQ,MAAM,6DAA8DA,EAAE,OAAO,EAC9E,EACT,CACF,CAEA,MAAM,UAAUN,EAAiBE,EAA2C,CAC1E,IAAMQ,EAAUV,EAAQ,YAAY,EAC9BC,EAAQ,KAAK,OAAO,IAAIS,CAAO,EACrC,GAAI,CAACT,EACH,eAAQ,KAAK,gEAAiES,CAAO,EAC9E,KAGT,GAAI,CACF,IAAMP,EAAU,sCAAsCF,CAAK,GAO3D,GANiB,MAAML,EAAc,CACnC,QAASI,EACT,QAAAG,EACA,UAAWD,CACb,CAAC,EAGC,YAAK,OAAO,OAAOQ,CAAO,EACnB,KAAK,WAAWV,EAAS,KAAK,CAEzC,OAASM,EAAG,CACV,QAAQ,MAAM,0BAA2BA,CAAC,CAC5C,CACA,OAAO,IACT,CAEQ,WAAWN,EAAiBW,EAAyB,CAC3D,IAAMZ,EAAe,CACnB,UAAW,KAAK,QAAQ,cAAgB,KAC1C,EACA,OAAOF,EAAI,KACT,CAAE,QAAAG,EAAS,QAAAW,CAAQ,EACnB,KAAK,QAAQ,UACbZ,CACF,CACF,CACF,EChGA,IAAOa,EAAQ,CACb,SAAU,CACR,KAAM,8BACN,QAAS,QACT,SAAU,CAAC,iBAAkB,gBAAgB,EAC7C,SAAU,CAAC,CACb,EAEA,MAAM,KAAKC,EAAqB,CAC9B,GAAM,CAAE,UAAAC,EAAW,aAAAC,EAAc,WAAAC,CAAW,EAAIH,EAAQ,OAEnDC,GACH,QAAQ,KAAK,iEAAiE,EAGhF,IAAMG,EAAc,IAAIC,EAAkB,CACxC,UAAWJ,GAAa,kBACxB,aAAAC,CACF,CAAC,EAEDF,EAAQ,QAAQ,SAAS,iBAAkB,CACzC,aAAc,CAACM,EAAiBC,IAAsBH,EAAY,aAAaE,EAASC,CAAS,EACjG,UAAW,CAACD,EAAiBC,IAAsBH,EAAY,UAAUE,EAASC,CAAS,EAC3F,cAAgBD,GAAoBF,EAAY,cAAcE,CAAO,CACvE,CAAC,EAED,IAAME,EAA4B,CAChC,CACE,OAAQ,OACR,KAAM,yBACN,QAAS,MAAOC,EAAUC,IAAa,CACrC,GAAM,CAAE,QAAAJ,CAAQ,EAAIG,EAAI,KACxB,GAAI,CAACH,EAAS,OAAOI,EAAI,OAAO,GAAG,EAAE,KAAK,CAAE,MAAO,kBAAmB,CAAC,EACvE,IAAMC,EAAQP,EAAY,cAAcE,CAAO,EAC/CI,EAAI,KAAK,CAAE,MAAAC,CAAM,CAAC,CACpB,CACF,EACA,CACE,OAAQ,OACR,KAAM,0BACN,QAAS,MAAOF,EAAUC,IAAa,CACrC,GAAM,CAAE,QAAAJ,EAAS,UAAAC,EAAW,QAAAK,CAAQ,EAAIH,EAAI,KAC5C,GAAI,CAACH,GAAW,CAACC,GAAa,CAACK,EAC7B,OAAOF,EAAI,OAAO,GAAG,EAAE,KAAK,CAAE,MAAO,yCAA0C,CAAC,EAGlF,IAAIG,EAAuB,KAO3B,GANID,IAAY,SACdC,EAAQ,MAAMT,EAAY,aAAaE,EAASC,CAAS,EAEzDM,EAAQ,MAAMT,EAAY,UAAUE,EAASC,CAAS,EAGpDM,EAAO,CAET,IAAIC,EAAO,KACX,GAAIX,EACF,GAAI,CACFW,EAAO,MAAMX,EAAW,CAAE,QAAAG,EAAS,QAAAM,EAAS,MAAAC,EAAO,IAAAJ,CAAI,CAAC,CAC1D,OAASM,EAAQ,CACf,QAAQ,MAAM,wDAAyDA,EAAE,OAAO,CAClF,CAEFL,EAAI,KAAK,CAAE,MAAAG,EAAO,KAAAC,CAAK,CAAC,CAC1B,MACEJ,EAAI,OAAO,GAAG,EAAE,KAAK,CAAE,MAAO,qBAAsB,CAAC,CAEzD,CACF,CACF,EAEAV,EAAQ,QAAQ,SAAS,iBAAkB,CACzC,UAAW,IAAMQ,CACnB,CAAkB,CACpB,CACF","names":["bs58","verifyMessage","jwt","CryptoAuthService","options","address","nonce","signature","message","signatureUint8","publicKeyUint8","e","publicKey","nacl","sign","addrKey","network","index_default","context","jwtSecret","jwtExpiresIn","onVerified","authService","CryptoAuthService","address","signature","routes","req","res","nonce","network","token","user","e"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@micro-cms/crypto-auth-node",
3
- "version": "1.0.32",
3
+ "version": "1.0.34",
4
4
  "description": "Node.js Crypto Authentication Module for Micro-CMS",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",