@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 +39 -15
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,33 +1,57 @@
|
|
|
1
1
|
# @micro-cms/crypto-auth-node
|
|
2
2
|
|
|
3
|
-
A Micro-CMS
|
|
3
|
+
A backend Micro-CMS module for verifying crypto wallet signatures (Solana and EVM) and issuing JWT tokens.
|
|
4
4
|
|
|
5
5
|
## Features
|
|
6
|
-
- **
|
|
7
|
-
- **
|
|
8
|
-
- **
|
|
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
|
-
##
|
|
16
|
+
## Usage
|
|
11
17
|
|
|
12
|
-
###
|
|
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:
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
-
|
|
38
|
+
cms.start().then(() => {
|
|
39
|
+
bindExpressRoutes({ app, cms });
|
|
40
|
+
});
|
|
28
41
|
```
|
|
29
42
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
- `POST /api/auth/crypto/
|
|
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
|
|
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
|
|
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
|
|
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
|
package/dist/index.mjs.map
CHANGED
|
@@ -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
|
|
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":"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","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"]}
|