@tuwaio/satellite-siwe-next-auth 0.3.5 → 0.3.6

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
@@ -4,15 +4,15 @@
4
4
  [![License](https://img.shields.io/npm/l/@tuwaio/satellite-siwe-next-auth.svg)](./LICENSE)
5
5
  [![Build Status](https://img.shields.io/github/actions/workflow/status/TuwaIO/satellite-connect/release.yml?branch=main)](https://github.com/TuwaIO/satellite-connect/actions)
6
6
 
7
- A robust connector module for enabling secure Web3 authentication (Sign-In with Ethereum, SIWE) in Next.js App Router using **Iron Session** for state management.
7
+ Robust server-side session authentication adapter mapping cryptographic signatures to the SIWE standard and NextAuth.
8
8
 
9
9
  ---
10
10
 
11
11
  ## 🏛️ What is `@tuwaio/satellite-siwe-next-auth`?
12
12
 
13
- `@tuwaio/satellite-siwe-next-auth` provides a secure, boilerplate-free solution for integrating **Sign-In with Ethereum (SIWE)** authentication into Next.js applications using the **App Router**.
13
+ `@tuwaio/satellite-siwe-next-auth` implements the session authentication logic for the Satellite framework in Next.js App Router environments. It maps user-provided cryptographic signatures directly to the SIWE standard, bridging them to active application sessions.
14
14
 
15
- It replaces the complexity of traditional NextAuth setup by leveraging **Iron Session** for robust, encrypted, server-side session management, ensuring a seamless and fully decentralized authentication experience.
15
+ By bypassing standard client-heavy authentication pipelines, this package utilizes server-encrypted **Iron Session** cookies to verify and enforce session state across API routes and Server Components.
16
16
 
17
17
  Built on top of **Wagmi/Viem** for signature generation and verification.
18
18
 
@@ -46,10 +46,10 @@ pnpm add @tuwaio/satellite-siwe-next-auth siwe iron-session wagmi @wagmi/core vi
46
46
 
47
47
  This package requires two **private** server environment variables for security:
48
48
 
49
- | Variable | Description |
50
- | :--- | :--- |
51
- | `SIWE_SESSION_SECRET` | **Required.** A cryptographically secure secret (minimum 32 characters) used by Iron Session to encrypt the session cookie. |
52
- | `SIWE_SESSION_URL` | **Required.** The full base URL of your application (e.g., `http://localhost:3000` or `https://myapp.com`). Used for SIWE domain verification. |
49
+ | Variable | Description |
50
+ | :-------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------- |
51
+ | `SIWE_SESSION_SECRET` | **Required.** A cryptographically secure secret (minimum 32 characters) used by Iron Session to encrypt the session cookie. |
52
+ | `SIWE_SESSION_URL` | **Required.** The full base URL of your application (e.g., `http://localhost:3000` or `https://myapp.com`). Used for SIWE domain verification. |
53
53
 
54
54
  **Example `.env`:**
55
55
 
@@ -146,14 +146,15 @@ export function SatelliteSiweProvider({ children }: { children: ReactNode }) {
146
146
  return (
147
147
  <SatelliteConnectProvider
148
148
  // Pass the EVM adapter with SIWE integration
149
- adapter={satelliteEVMAdapter(wagmiConfig, wagmiConfig.chains as readonly [Chain, ...Chain[]], siweEnabled ? signInWithSiwe : undefined)}
149
+ adapter={satelliteEVMAdapter(
150
+ wagmiConfig,
151
+ wagmiConfig.chains as readonly [Chain, ...Chain[]],
152
+ siweEnabled ? signInWithSiwe : undefined,
153
+ )}
150
154
  autoConnect={true}
151
155
  >
152
156
  {/* EVMConnectorsWatcher handles disconnections and account changes */}
153
- <EVMConnectorsWatcher
154
- wagmiConfig={wagmiConfig}
155
- siwe={{ isSignedIn, isRejected, enabled: siweEnabled }}
156
- />
157
+ <EVMConnectorsWatcher wagmiConfig={wagmiConfig} siwe={{ isSignedIn, isRejected, enabled: siweEnabled }} />
157
158
  {children}
158
159
  </SatelliteConnectProvider>
159
160
  );
@@ -168,13 +169,13 @@ The `createSiweApiHandler` accepts an optional configuration object to override
168
169
 
169
170
  ### Configuration Parameters (`SiweApiConfig` Type)
170
171
 
171
- | Parameter | Type | Default | Description |
172
- | :--- | :--- | :--- | :--- |
173
- | `session.password` | `string` | `SIWE_SESSION_SECRET` | Overrides the secret key for encryption. |
174
- | `session.cookieName` | `string` | `'satellite_siwe'` | Overrides the name of the session cookie. |
175
- | `session.cookieOptions` | `SiweCookieOptions` | `{ maxAge: 30 days, secure: false (dev) }` | Allows overriding standard cookie settings (e.g., `maxAge`). |
176
- | `options.afterVerify` | `() => Promise<void>` | `undefined` | Hook executed **after** the SIWE signature is cryptographically verified. Ideal for fetching user data. |
177
- | `options.afterLogout` | `() => Promise<void>` | `undefined` | Hook executed **after** the session cookie is destroyed. |
172
+ | Parameter | Type | Default | Description |
173
+ | :---------------------- | :-------------------- | :----------------------------------------- | :------------------------------------------------------------------------------------------------------ |
174
+ | `session.password` | `string` | `SIWE_SESSION_SECRET` | Overrides the secret key for encryption. |
175
+ | `session.cookieName` | `string` | `'satellite_siwe'` | Overrides the name of the session cookie. |
176
+ | `session.cookieOptions` | `SiweCookieOptions` | `{ maxAge: 30 days, secure: false (dev) }` | Allows overriding standard cookie settings (e.g., `maxAge`). |
177
+ | `options.afterVerify` | `() => Promise<void>` | `undefined` | Hook executed **after** the SIWE signature is cryptographically verified. Ideal for fetching user data. |
178
+ | `options.afterLogout` | `() => Promise<void>` | `undefined` | Hook executed **after** the session cookie is destroyed. |
178
179
 
179
180
  ### Example Custom Initialization
180
181
 
@@ -186,21 +187,21 @@ import { createSiweApiHandler } from '@tuwaio/satellite-siwe-next-auth/server';
186
187
  const siweApiHandler = createSiweApiHandler({
187
188
  // Custom Session Settings
188
189
  session: {
189
- cookieName: "my_app_session",
190
+ cookieName: 'my_app_session',
190
191
  cookieOptions: {
191
192
  maxAge: 60 * 60 * 24 * 7, // 7 days
192
- }
193
+ },
193
194
  },
194
195
  // Custom Hooks
195
196
  options: {
196
197
  afterVerify: async () => {
197
198
  // This logic runs on the server side after a valid signature is confirmed.
198
- console.log("User verified, ready to create DB record.");
199
+ console.log('User verified, ready to create DB record.');
199
200
  },
200
201
  afterLogout: () => {
201
- console.log("User session destroyed.");
202
- }
203
- }
202
+ console.log('User session destroyed.');
203
+ },
204
+ },
204
205
  });
205
206
 
206
207
  export const { GET, POST, DELETE } = siweApiHandler;
package/dist/index.d.mts CHANGED
@@ -2,7 +2,6 @@ import { S as SIWESession, a as SiweAuthContextType, b as SiweNextAuthProviderPr
2
2
  export { C as ConfigurableMessageOptions, G as GetSiweMessageOptions, c as Session, d as SiweApiConfig, e as SiweApiHooks, f as SiweCookieOptions, g as SiweSessionData, h as SiweSessionSettings, i as UnconfigurableMessageOptions } from './types-DYvmUmv4.mjs';
3
3
  import { Config } from '@wagmi/core';
4
4
  import * as react from 'react';
5
- import * as react_jsx_runtime from 'react/jsx-runtime';
6
5
  import 'viem';
7
6
  import 'viem/siwe';
8
7
 
@@ -58,6 +57,6 @@ declare const SiweAuthContext: react.Context<SiweAuthContextType | undefined>;
58
57
  * It must be nested inside NextAuth's `<SessionProvider>` and your Wagmi Provider.
59
58
  * * **Note**: This provider requires the server-side NextAuth configuration to be set up.
60
59
  */
61
- declare function SiweNextAuthProvider(props: SiweNextAuthProviderProps): react_jsx_runtime.JSX.Element;
60
+ declare function SiweNextAuthProvider(props: SiweNextAuthProviderProps): react.JSX.Element;
62
61
 
63
62
  export { SIWESession, SiweAuthContext, SiweAuthContextType, SiweNextAuthProvider, SiweNextAuthProviderProps, UseSiweSignatureResult, useInterval, useSiweAuth, useSiweAuthAdapter, useSiweSignature };
package/dist/index.d.ts CHANGED
@@ -2,7 +2,6 @@ import { S as SIWESession, a as SiweAuthContextType, b as SiweNextAuthProviderPr
2
2
  export { C as ConfigurableMessageOptions, G as GetSiweMessageOptions, c as Session, d as SiweApiConfig, e as SiweApiHooks, f as SiweCookieOptions, g as SiweSessionData, h as SiweSessionSettings, i as UnconfigurableMessageOptions } from './types-DYvmUmv4.js';
3
3
  import { Config } from '@wagmi/core';
4
4
  import * as react from 'react';
5
- import * as react_jsx_runtime from 'react/jsx-runtime';
6
5
  import 'viem';
7
6
  import 'viem/siwe';
8
7
 
@@ -58,6 +57,6 @@ declare const SiweAuthContext: react.Context<SiweAuthContextType | undefined>;
58
57
  * It must be nested inside NextAuth's `<SessionProvider>` and your Wagmi Provider.
59
58
  * * **Note**: This provider requires the server-side NextAuth configuration to be set up.
60
59
  */
61
- declare function SiweNextAuthProvider(props: SiweNextAuthProviderProps): react_jsx_runtime.JSX.Element;
60
+ declare function SiweNextAuthProvider(props: SiweNextAuthProviderProps): react.JSX.Element;
62
61
 
63
62
  export { SIWESession, SiweAuthContext, SiweAuthContextType, SiweNextAuthProvider, SiweNextAuthProviderProps, UseSiweSignatureResult, useInterval, useSiweAuth, useSiweAuthAdapter, useSiweSignature };
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- 'use strict';var react=require('react'),core=require('@wagmi/core'),wagmi=require('wagmi'),siwe=require('viem/siwe'),jsxRuntime=require('react/jsx-runtime');function L(t,e){let r=react.useRef(t);react.useEffect(()=>{r.current=t;},[t]),react.useEffect(()=>{if(e!==null&&typeof window<"u"&&window.setInterval){let a=window.setInterval(()=>r.current(),e);return ()=>window.clearInterval(a)}},[e]);}var A=react.createContext(void 0);function de(t){let e=react.useContext(A);if(e===void 0)throw new Error("useSiweAuth must be used within a SiweNextAuthProvider");let r=react.useCallback(async()=>e.signInWithSiwe(t?.onSignIn),[e.signInWithSiwe,t?.onSignIn]),a=react.useCallback(async()=>e.signOutSiwe(t?.onSignOut),[e.signOutSiwe,t?.onSignOut]);return react.useMemo(()=>({...e,signInWithSiwe:r,signOutSiwe:a}),[e,r,a])}async function X(){return crypto.randomUUID().replace(/-/g,"")}function k({wagmiConfig:t}){let{isConnected:e,address:r,chainId:a}=wagmi.useConnection({config:t}),[h,d]=react.useState(false),u=react.useMemo(()=>e&&!!r&&!!a,[e,r,a]);return react.useEffect(()=>{u&&d(false);},[u]),{getSiweSignature:async S=>{d(false);let n=core.getConnection(t);if(!n.isConnected||!n.address||!n.chainId)throw new Error("Connector not connected or connection details are missing from Wagmi snapshot.");try{let o=await X();if(!o)throw new Error("Failed to retrieve CSRF token/nonce.");let c=siwe.createSiweMessage({domain:window.location.host,statement:"Sign in with Ethereum to the application.",uri:window.location.origin,version:"1",...S?S():{},address:n.address,chainId:n.chainId,nonce:o}),w=await core.signMessage(t,{message:c});if(!w)throw d(!0),await core.disconnect(t),new Error("Message signing cancelled by user or failed.");return {message:c,signature:w}}catch(o){await core.disconnect(t),console.error("Error during signature generation:",o);let c=o;throw (c.name==="UserRejectedRequestError"||c.code===4001||/user rejected/i.test(c.message))&&d(true),o}},isReadyToSign:u,isRejected:h}}async function ee(){try{let t=await fetch("/api/siwe/session");if(t.status===401||t.status===404)return {session:void 0,status:"unauthenticated"};if(!t.ok)throw new Error("Failed to fetch session data.");let e=await t.json();return e.isLoggedIn&&e.address&&e.chainId?{session:{address:e.address,chainId:e.chainId},status:"authenticated"}:{session:void 0,status:"unauthenticated"}}catch(t){return console.error("Error fetching session:",t),{session:void 0,status:"unauthenticated"}}}function O({wagmiConfig:t,enabled:e=true,nonceRefetchInterval:r=300*1e3,onSignIn:a,onSignOut:h,getSiweMessageOptions:d}){let[u,p]=react.useState(void 0),[S,n]=react.useState("loading"),{isReadyToSign:o,getSiweSignature:c,isRejected:w}=k({wagmiConfig:t}),{address:y,chainId:v,isConnected:W}=wagmi.useConnection({config:t}),[P,T]=react.useState(false),g=S==="authenticated",R=S==="loading",N=u,F=react.useCallback(async()=>{n("loading");let{session:s,status:i}=await ee();return p(s),n(i),s},[]);L(()=>{g&&F();},r);let m=react.useCallback(async s=>{await fetch("/api/siwe/logout",{method:"POST"}),p(void 0),n("unauthenticated"),h?.(),s?.();},[h]),I=react.useCallback(async s=>{if(!e)throw new Error("SIWE is currently disabled via provider configuration.");n("loading");try{let i=await c(d);if(!i){n("unauthenticated");return}let l=await fetch("/api/siwe/login",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({message:i.message,signature:i.signature})}),f=await l.json();if(!l.ok||f.isLoggedIn!==!0)throw new Error(`Verification error: ${f.message||"Login failed."}`);console.log("SIWE Authentication successful.");let x={address:f.address,chainId:f.chainId};p(x),n("authenticated"),a?.(x),s?.(x);}catch(i){throw await core.disconnect(t),n("unauthenticated"),new Error(`SIWE Sign-In failed: ${i instanceof Error?i.message:"Unknown error"}`)}},[e,c,d,a,t]);return react.useEffect(()=>{if(g&&e){let s=u?.address?.toLowerCase(),i=y?.toLowerCase(),l=u?.chainId,f=v;s&&i&&s!==i||l&&f&&l!==f?(console.log("SIWE: Connector context changed (Address or Chain ID). Initiating re-authentication."),T(true),m()):!W&&(console.log("SIWE: Connector disconnected. Disconnecting session."),m(),h?.());}},[g,y,v,W,u,m,e,h]),react.useEffect(()=>{P&&S==="unauthenticated"&&o&&e&&(console.log("SIWE: State reset detected. Attempting automatic sign-in to establish new session."),T(false),I().catch(s=>{throw new Error(`SIWE Auto Sign-In failed after context change: ${s instanceof Error?s.message:"Unknown error"}`)}));},[P,S,o,I,e]),react.useMemo(()=>({data:N,isReadyToSign:o,isRejected:w,isLoading:R,isSignedIn:g,signInWithSiwe:I,signOutSiwe:m,enabled:e}),[N,o,w,R,g,I,m,e])}function We(t){let e=O(t);return jsxRuntime.jsx(A.Provider,{value:e,children:t.children})}exports.SiweAuthContext=A;exports.SiweNextAuthProvider=We;exports.useInterval=L;exports.useSiweAuth=de;exports.useSiweAuthAdapter=O;exports.useSiweSignature=k;
1
+ 'use strict';var react=require('react'),core=require('@wagmi/core'),wagmi=require('wagmi'),siwe=require('viem/siwe'),jsxRuntime=require('react/jsx-runtime');function M(t,e){let r=react.useRef(t);react.useEffect(()=>{r.current=t;},[t]),react.useEffect(()=>{if(e!==null&&typeof window<"u"&&window.setInterval){let a=window.setInterval(()=>r.current(),e);return ()=>window.clearInterval(a)}},[e]);}var x=react.createContext(void 0);function de(t){let e=react.useContext(x);if(e===void 0)throw new Error("useSiweAuth must be used within a SiweNextAuthProvider");let r=react.useCallback(async()=>e.signInWithSiwe(t?.onSignIn),[e.signInWithSiwe,t?.onSignIn]),a=react.useCallback(async()=>e.signOutSiwe(t?.onSignOut),[e.signOutSiwe,t?.onSignOut]);return react.useMemo(()=>({...e,signInWithSiwe:r,signOutSiwe:a}),[e,r,a])}async function X(){return crypto.randomUUID().replace(/-/g,"")}function D({wagmiConfig:t}){let{isConnected:e,address:r,chainId:a}=wagmi.useConnection({config:t}),[f,d]=react.useState(false),u=react.useMemo(()=>e&&!!r&&!!a,[e,r,a]);return react.useEffect(()=>{u&&d(false);},[u]),{getSiweSignature:async S=>{d(false);let n=core.getConnection(t);if(!n.isConnected||!n.address||!n.chainId)throw new Error("Connector not connected or connection details are missing from Wagmi snapshot.");try{let o=await X();if(!o)throw new Error("Failed to retrieve CSRF token/nonce.");let c=siwe.createSiweMessage({domain:window.location.host,statement:"Sign in with Ethereum to the application.",uri:window.location.origin,version:"1",...S?S():{},address:n.address,chainId:n.chainId,nonce:o}),w=await core.signMessage(t,{message:c});if(!w)throw d(!0),await core.disconnect(t),new Error("Message signing cancelled by user or failed.");return {message:c,signature:w}}catch(o){await core.disconnect(t),console.error("Error during signature generation:",o);let c=o;throw (c.name==="UserRejectedRequestError"||c.code===4001||/user rejected/i.test(c.message))&&d(true),o}},isReadyToSign:u,isRejected:f}}async function ee(){try{let t=await fetch("/api/siwe/session");if(t.status===401||t.status===404)return {session:void 0,status:"unauthenticated"};if(!t.ok)throw new Error("Failed to fetch session data.");let e=await t.json();return e.isLoggedIn&&e.address&&e.chainId?{session:{address:e.address,chainId:e.chainId},status:"authenticated"}:{session:void 0,status:"unauthenticated"}}catch(t){return console.error("Error fetching session:",t),{session:void 0,status:"unauthenticated"}}}function O({wagmiConfig:t,enabled:e=true,nonceRefetchInterval:r=300*1e3,onSignIn:a,onSignOut:f,getSiweMessageOptions:d}){let[u,p]=react.useState(void 0),[S,n]=react.useState("loading"),{isReadyToSign:o,getSiweSignature:c,isRejected:w}=D({wagmiConfig:t}),{address:y,chainId:v,isConnected:W}=wagmi.useConnection({config:t}),[P,T]=react.useState(false),g=S==="authenticated",R=S==="loading",N=u,F=react.useCallback(async()=>{n("loading");let{session:s,status:i}=await ee();return p(s),n(i),s},[]);M(()=>{g&&F();},r);let m=react.useCallback(async s=>{await fetch("/api/siwe/logout",{method:"POST"}),p(void 0),n("unauthenticated"),f?.(),s?.();},[f]),I=react.useCallback(async s=>{if(!e)throw new Error("SIWE is currently disabled via provider configuration.");n("loading");try{let i=await c(d);if(!i){n("unauthenticated");return}let l=await fetch("/api/siwe/login",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({message:i.message,signature:i.signature})}),h=await l.json();if(!l.ok||h.isLoggedIn!==!0)throw new Error(`Verification error: ${h.message||"Authentication failed."}`);console.log("SIWE Authentication successful.");let A={address:h.address,chainId:h.chainId};p(A),n("authenticated"),a?.(A),s?.(A);}catch(i){throw await core.disconnect(t),n("unauthenticated"),new Error(`SIWE Sign-In failed: ${i instanceof Error?i.message:"Unknown error"}`)}},[e,c,d,a,t]);return react.useEffect(()=>{if(g&&e){let s=u?.address?.toLowerCase(),i=y?.toLowerCase(),l=u?.chainId,h=v;s&&i&&s!==i||l&&h&&l!==h?(console.log("SIWE: Connector context changed (Address or Chain ID). Initiating re-authentication."),T(true),m()):!W&&(console.log("SIWE: Connector disconnected. Disconnecting session."),m(),f?.());}},[g,y,v,W,u,m,e,f]),react.useEffect(()=>{P&&S==="unauthenticated"&&o&&e&&(console.log("SIWE: State reset detected. Attempting automatic sign-in to establish new session."),T(false),I().catch(s=>{throw new Error(`SIWE Auto Sign-In failed after context change: ${s instanceof Error?s.message:"Unknown error"}`)}));},[P,S,o,I,e]),react.useMemo(()=>({data:N,isReadyToSign:o,isRejected:w,isLoading:R,isSignedIn:g,signInWithSiwe:I,signOutSiwe:m,enabled:e}),[N,o,w,R,g,I,m,e])}function We(t){let e=O(t);return jsxRuntime.jsx(x.Provider,{value:e,children:t.children})}exports.SiweAuthContext=x;exports.SiweNextAuthProvider=We;exports.useInterval=M;exports.useSiweAuth=de;exports.useSiweAuthAdapter=O;exports.useSiweSignature=D;
package/dist/index.mjs CHANGED
@@ -1 +1 @@
1
- import {createContext,useRef,useEffect,useContext,useCallback,useMemo,useState}from'react';import {getConnection,signMessage,disconnect}from'@wagmi/core';import {useConnection}from'wagmi';import {createSiweMessage}from'viem/siwe';import {jsx}from'react/jsx-runtime';function L(t,e){let r=useRef(t);useEffect(()=>{r.current=t;},[t]),useEffect(()=>{if(e!==null&&typeof window<"u"&&window.setInterval){let a=window.setInterval(()=>r.current(),e);return ()=>window.clearInterval(a)}},[e]);}var A=createContext(void 0);function de(t){let e=useContext(A);if(e===void 0)throw new Error("useSiweAuth must be used within a SiweNextAuthProvider");let r=useCallback(async()=>e.signInWithSiwe(t?.onSignIn),[e.signInWithSiwe,t?.onSignIn]),a=useCallback(async()=>e.signOutSiwe(t?.onSignOut),[e.signOutSiwe,t?.onSignOut]);return useMemo(()=>({...e,signInWithSiwe:r,signOutSiwe:a}),[e,r,a])}async function X(){return crypto.randomUUID().replace(/-/g,"")}function k({wagmiConfig:t}){let{isConnected:e,address:r,chainId:a}=useConnection({config:t}),[h,d]=useState(false),u=useMemo(()=>e&&!!r&&!!a,[e,r,a]);return useEffect(()=>{u&&d(false);},[u]),{getSiweSignature:async S=>{d(false);let n=getConnection(t);if(!n.isConnected||!n.address||!n.chainId)throw new Error("Connector not connected or connection details are missing from Wagmi snapshot.");try{let o=await X();if(!o)throw new Error("Failed to retrieve CSRF token/nonce.");let c=createSiweMessage({domain:window.location.host,statement:"Sign in with Ethereum to the application.",uri:window.location.origin,version:"1",...S?S():{},address:n.address,chainId:n.chainId,nonce:o}),w=await signMessage(t,{message:c});if(!w)throw d(!0),await disconnect(t),new Error("Message signing cancelled by user or failed.");return {message:c,signature:w}}catch(o){await disconnect(t),console.error("Error during signature generation:",o);let c=o;throw (c.name==="UserRejectedRequestError"||c.code===4001||/user rejected/i.test(c.message))&&d(true),o}},isReadyToSign:u,isRejected:h}}async function ee(){try{let t=await fetch("/api/siwe/session");if(t.status===401||t.status===404)return {session:void 0,status:"unauthenticated"};if(!t.ok)throw new Error("Failed to fetch session data.");let e=await t.json();return e.isLoggedIn&&e.address&&e.chainId?{session:{address:e.address,chainId:e.chainId},status:"authenticated"}:{session:void 0,status:"unauthenticated"}}catch(t){return console.error("Error fetching session:",t),{session:void 0,status:"unauthenticated"}}}function O({wagmiConfig:t,enabled:e=true,nonceRefetchInterval:r=300*1e3,onSignIn:a,onSignOut:h,getSiweMessageOptions:d}){let[u,p]=useState(void 0),[S,n]=useState("loading"),{isReadyToSign:o,getSiweSignature:c,isRejected:w}=k({wagmiConfig:t}),{address:y,chainId:v,isConnected:W}=useConnection({config:t}),[P,T]=useState(false),g=S==="authenticated",R=S==="loading",N=u,F=useCallback(async()=>{n("loading");let{session:s,status:i}=await ee();return p(s),n(i),s},[]);L(()=>{g&&F();},r);let m=useCallback(async s=>{await fetch("/api/siwe/logout",{method:"POST"}),p(void 0),n("unauthenticated"),h?.(),s?.();},[h]),I=useCallback(async s=>{if(!e)throw new Error("SIWE is currently disabled via provider configuration.");n("loading");try{let i=await c(d);if(!i){n("unauthenticated");return}let l=await fetch("/api/siwe/login",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({message:i.message,signature:i.signature})}),f=await l.json();if(!l.ok||f.isLoggedIn!==!0)throw new Error(`Verification error: ${f.message||"Login failed."}`);console.log("SIWE Authentication successful.");let x={address:f.address,chainId:f.chainId};p(x),n("authenticated"),a?.(x),s?.(x);}catch(i){throw await disconnect(t),n("unauthenticated"),new Error(`SIWE Sign-In failed: ${i instanceof Error?i.message:"Unknown error"}`)}},[e,c,d,a,t]);return useEffect(()=>{if(g&&e){let s=u?.address?.toLowerCase(),i=y?.toLowerCase(),l=u?.chainId,f=v;s&&i&&s!==i||l&&f&&l!==f?(console.log("SIWE: Connector context changed (Address or Chain ID). Initiating re-authentication."),T(true),m()):!W&&(console.log("SIWE: Connector disconnected. Disconnecting session."),m(),h?.());}},[g,y,v,W,u,m,e,h]),useEffect(()=>{P&&S==="unauthenticated"&&o&&e&&(console.log("SIWE: State reset detected. Attempting automatic sign-in to establish new session."),T(false),I().catch(s=>{throw new Error(`SIWE Auto Sign-In failed after context change: ${s instanceof Error?s.message:"Unknown error"}`)}));},[P,S,o,I,e]),useMemo(()=>({data:N,isReadyToSign:o,isRejected:w,isLoading:R,isSignedIn:g,signInWithSiwe:I,signOutSiwe:m,enabled:e}),[N,o,w,R,g,I,m,e])}function We(t){let e=O(t);return jsx(A.Provider,{value:e,children:t.children})}export{A as SiweAuthContext,We as SiweNextAuthProvider,L as useInterval,de as useSiweAuth,O as useSiweAuthAdapter,k as useSiweSignature};
1
+ import {createContext,useRef,useEffect,useContext,useCallback,useMemo,useState}from'react';import {getConnection,signMessage,disconnect}from'@wagmi/core';import {useConnection}from'wagmi';import {createSiweMessage}from'viem/siwe';import {jsx}from'react/jsx-runtime';function M(t,e){let r=useRef(t);useEffect(()=>{r.current=t;},[t]),useEffect(()=>{if(e!==null&&typeof window<"u"&&window.setInterval){let a=window.setInterval(()=>r.current(),e);return ()=>window.clearInterval(a)}},[e]);}var x=createContext(void 0);function de(t){let e=useContext(x);if(e===void 0)throw new Error("useSiweAuth must be used within a SiweNextAuthProvider");let r=useCallback(async()=>e.signInWithSiwe(t?.onSignIn),[e.signInWithSiwe,t?.onSignIn]),a=useCallback(async()=>e.signOutSiwe(t?.onSignOut),[e.signOutSiwe,t?.onSignOut]);return useMemo(()=>({...e,signInWithSiwe:r,signOutSiwe:a}),[e,r,a])}async function X(){return crypto.randomUUID().replace(/-/g,"")}function D({wagmiConfig:t}){let{isConnected:e,address:r,chainId:a}=useConnection({config:t}),[f,d]=useState(false),u=useMemo(()=>e&&!!r&&!!a,[e,r,a]);return useEffect(()=>{u&&d(false);},[u]),{getSiweSignature:async S=>{d(false);let n=getConnection(t);if(!n.isConnected||!n.address||!n.chainId)throw new Error("Connector not connected or connection details are missing from Wagmi snapshot.");try{let o=await X();if(!o)throw new Error("Failed to retrieve CSRF token/nonce.");let c=createSiweMessage({domain:window.location.host,statement:"Sign in with Ethereum to the application.",uri:window.location.origin,version:"1",...S?S():{},address:n.address,chainId:n.chainId,nonce:o}),w=await signMessage(t,{message:c});if(!w)throw d(!0),await disconnect(t),new Error("Message signing cancelled by user or failed.");return {message:c,signature:w}}catch(o){await disconnect(t),console.error("Error during signature generation:",o);let c=o;throw (c.name==="UserRejectedRequestError"||c.code===4001||/user rejected/i.test(c.message))&&d(true),o}},isReadyToSign:u,isRejected:f}}async function ee(){try{let t=await fetch("/api/siwe/session");if(t.status===401||t.status===404)return {session:void 0,status:"unauthenticated"};if(!t.ok)throw new Error("Failed to fetch session data.");let e=await t.json();return e.isLoggedIn&&e.address&&e.chainId?{session:{address:e.address,chainId:e.chainId},status:"authenticated"}:{session:void 0,status:"unauthenticated"}}catch(t){return console.error("Error fetching session:",t),{session:void 0,status:"unauthenticated"}}}function O({wagmiConfig:t,enabled:e=true,nonceRefetchInterval:r=300*1e3,onSignIn:a,onSignOut:f,getSiweMessageOptions:d}){let[u,p]=useState(void 0),[S,n]=useState("loading"),{isReadyToSign:o,getSiweSignature:c,isRejected:w}=D({wagmiConfig:t}),{address:y,chainId:v,isConnected:W}=useConnection({config:t}),[P,T]=useState(false),g=S==="authenticated",R=S==="loading",N=u,F=useCallback(async()=>{n("loading");let{session:s,status:i}=await ee();return p(s),n(i),s},[]);M(()=>{g&&F();},r);let m=useCallback(async s=>{await fetch("/api/siwe/logout",{method:"POST"}),p(void 0),n("unauthenticated"),f?.(),s?.();},[f]),I=useCallback(async s=>{if(!e)throw new Error("SIWE is currently disabled via provider configuration.");n("loading");try{let i=await c(d);if(!i){n("unauthenticated");return}let l=await fetch("/api/siwe/login",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({message:i.message,signature:i.signature})}),h=await l.json();if(!l.ok||h.isLoggedIn!==!0)throw new Error(`Verification error: ${h.message||"Authentication failed."}`);console.log("SIWE Authentication successful.");let A={address:h.address,chainId:h.chainId};p(A),n("authenticated"),a?.(A),s?.(A);}catch(i){throw await disconnect(t),n("unauthenticated"),new Error(`SIWE Sign-In failed: ${i instanceof Error?i.message:"Unknown error"}`)}},[e,c,d,a,t]);return useEffect(()=>{if(g&&e){let s=u?.address?.toLowerCase(),i=y?.toLowerCase(),l=u?.chainId,h=v;s&&i&&s!==i||l&&h&&l!==h?(console.log("SIWE: Connector context changed (Address or Chain ID). Initiating re-authentication."),T(true),m()):!W&&(console.log("SIWE: Connector disconnected. Disconnecting session."),m(),f?.());}},[g,y,v,W,u,m,e,f]),useEffect(()=>{P&&S==="unauthenticated"&&o&&e&&(console.log("SIWE: State reset detected. Attempting automatic sign-in to establish new session."),T(false),I().catch(s=>{throw new Error(`SIWE Auto Sign-In failed after context change: ${s instanceof Error?s.message:"Unknown error"}`)}));},[P,S,o,I,e]),useMemo(()=>({data:N,isReadyToSign:o,isRejected:w,isLoading:R,isSignedIn:g,signInWithSiwe:I,signOutSiwe:m,enabled:e}),[N,o,w,R,g,I,m,e])}function We(t){let e=O(t);return jsx(x.Provider,{value:e,children:t.children})}export{x as SiweAuthContext,We as SiweNextAuthProvider,M as useInterval,de as useSiweAuth,O as useSiweAuthAdapter,D as useSiweSignature};
@@ -1,2 +1,2 @@
1
- 'use strict';var ironSession=require('iron-session'),server=require('next/server'),viem=require('viem'),siwe=require('viem/siwe');function l(t){let e=t.session||{},c=process.env.SIWE_SESSION_SECRET,a=e.password||c;if(!a||a.length<32)throw new Error("SIWE Error: Iron Session requires a 'password' option (min 32 chars) or SIWE_SESSION_SECRET environment variable to be set.");let S={...{secure:process.env.NODE_ENV==="production",maxAge:300*60,httpOnly:true,sameSite:"lax",path:"/"},...e.cookieOptions};return {password:a,cookieName:e.cookieName||"satellite_siwe",cookieOptions:S}}function C(t){if(!t)return console.warn("SIWE WARN: SIWE_SESSION_URL is not defined. Defaulting domain check to 'localhost'."),"localhost";try{return new URL(t.startsWith("http")?t:`https://${t}`).host}catch(e){return console.error(`SIWE ERROR: Invalid URL provided in SIWE_SESSION_URL: ${t}. Error: ${typeof e=="string"?e:e.message}`),"localhost"}}function _(t={}){let e=t.options||{},c=l(t);async function a(o){let s=new Response;return {session:await ironSession.getIronSession(o,s,c),response:s}}async function f(o){try{let{message:s,signature:d}=await o.json();if(!s||!d)return server.NextResponse.json({message:"Missing message or signature"},{status:400});e.afterNonce&&await e.afterNonce();let n=siwe.parseSiweMessage(s);if(!n||!n.address||!n.chainId)return server.NextResponse.json({message:"Invalid SIWE message format"},{status:400});let r=C(process.env.SIWE_SESSION_URL);if(!siwe.validateSiweMessage({message:n,domain:r}))return server.NextResponse.json({message:"SIWE message validation failed"},{status:401});if(!await viem.verifyMessage({address:n.address,message:s,signature:d}))return server.NextResponse.json({message:"SIWE signature verification failed"},{status:401});e.afterVerify&&await e.afterVerify();let{session:p,response:E}=await a(o);p.address=n.address,p.chainId=n.chainId,p.isLoggedIn=!0,await p.save(),e.afterSession&&await e.afterSession();let m=server.NextResponse.json({isLoggedIn:!0,address:p.address,chainId:p.chainId},{status:200});return E.headers.forEach((w,I)=>{I.toLowerCase()==="set-cookie"&&m.headers.append("Set-Cookie",w);}),m}catch(s){return console.error("SIWE CRITICAL LOGIN ERROR:",s),server.NextResponse.json({message:"Internal Server Error during login"},{status:500})}}async function S(o){let{session:s,response:d}=await a(o);if(o.method==="POST"||o.method==="DELETE"){s.destroy(),e.afterLogout&&await e.afterLogout();let n=server.NextResponse.json({isLoggedIn:false},{status:200});return d.headers.forEach((r,g)=>{g.toLowerCase()==="set-cookie"&&n.headers.append("Set-Cookie",r);}),n}return s.isLoggedIn&&s.address&&s.chainId?server.NextResponse.json({isLoggedIn:true,address:s.address,chainId:s.chainId}):server.NextResponse.json({isLoggedIn:false},{status:401})}let u=async(o,s)=>{let n=(await s.params||{})?.siwe||[],r=n[n.length-1];return r==="login"&&o.method==="POST"?f(o):r==="session"&&o.method==="GET"||r==="logout"&&(o.method==="POST"||o.method==="DELETE")?S(o):Promise.resolve(new Response("Not Found",{status:404}))};return {GET:u,POST:u,DELETE:u}}
1
+ 'use strict';var ironSession=require('iron-session'),server=require('next/server'),viem=require('viem'),siwe=require('viem/siwe');function l(t){let e=t.session||{},c=process.env.SIWE_SESSION_SECRET,a=e.password||c;if(!a||a.length<32)throw new Error("SIWE Error: Iron Session requires a 'password' option (min 32 chars) or SIWE_SESSION_SECRET environment variable to be set.");let S={...{secure:process.env.NODE_ENV==="production",maxAge:300*60,httpOnly:true,sameSite:"lax",path:"/"},...e.cookieOptions};return {password:a,cookieName:e.cookieName||"satellite_siwe",cookieOptions:S}}function L(t){if(!t)return console.warn("SIWE WARN: SIWE_SESSION_URL is not defined. Defaulting domain check to 'localhost'."),"localhost";try{return new URL(t.startsWith("http")?t:`https://${t}`).host}catch(e){return console.error(`SIWE ERROR: Invalid URL provided in SIWE_SESSION_URL: ${t}. Error: ${typeof e=="string"?e:e.message}`),"localhost"}}function _(t={}){let e=t.options||{},c=l(t);async function a(o){let s=new Response;return {session:await ironSession.getIronSession(o,s,c),response:s}}async function f(o){try{let{message:s,signature:d}=await o.json();if(!s||!d)return server.NextResponse.json({message:"Missing message or signature"},{status:400});e.afterNonce&&await e.afterNonce();let n=siwe.parseSiweMessage(s);if(!n||!n.address||!n.chainId)return server.NextResponse.json({message:"Invalid SIWE message format"},{status:400});let r=L(process.env.SIWE_SESSION_URL);if(!siwe.validateSiweMessage({message:n,domain:r}))return server.NextResponse.json({message:"SIWE message validation failed"},{status:401});if(!await viem.verifyMessage({address:n.address,message:s,signature:d}))return server.NextResponse.json({message:"SIWE signature verification failed"},{status:401});e.afterVerify&&await e.afterVerify();let{session:p,response:E}=await a(o);p.address=n.address,p.chainId=n.chainId,p.isLoggedIn=!0,await p.save(),e.afterSession&&await e.afterSession();let m=server.NextResponse.json({isLoggedIn:!0,address:p.address,chainId:p.chainId},{status:200});return E.headers.forEach((I,w)=>{w.toLowerCase()==="set-cookie"&&m.headers.append("Set-Cookie",I);}),m}catch(s){return console.error("SIWE CRITICAL AUTHENTICATION ERROR:",s),server.NextResponse.json({message:"Internal Server Error during session validation"},{status:500})}}async function S(o){let{session:s,response:d}=await a(o);if(o.method==="POST"||o.method==="DELETE"){s.destroy(),e.afterLogout&&await e.afterLogout();let n=server.NextResponse.json({isLoggedIn:false},{status:200});return d.headers.forEach((r,g)=>{g.toLowerCase()==="set-cookie"&&n.headers.append("Set-Cookie",r);}),n}return s.isLoggedIn&&s.address&&s.chainId?server.NextResponse.json({isLoggedIn:true,address:s.address,chainId:s.chainId}):server.NextResponse.json({isLoggedIn:false},{status:401})}let u=async(o,s)=>{let n=(await s.params||{})?.siwe||[],r=n[n.length-1];return r==="login"&&o.method==="POST"?f(o):r==="session"&&o.method==="GET"||r==="logout"&&(o.method==="POST"||o.method==="DELETE")?S(o):Promise.resolve(new Response("Not Found",{status:404}))};return {GET:u,POST:u,DELETE:u}}
2
2
  exports.createSiweApiHandler=_;exports.getSessionOptions=l;
@@ -1,2 +1,2 @@
1
- import {getIronSession}from'iron-session';import {NextResponse}from'next/server';import {verifyMessage}from'viem';import {parseSiweMessage,validateSiweMessage}from'viem/siwe';function l(t){let e=t.session||{},c=process.env.SIWE_SESSION_SECRET,a=e.password||c;if(!a||a.length<32)throw new Error("SIWE Error: Iron Session requires a 'password' option (min 32 chars) or SIWE_SESSION_SECRET environment variable to be set.");let S={...{secure:process.env.NODE_ENV==="production",maxAge:300*60,httpOnly:true,sameSite:"lax",path:"/"},...e.cookieOptions};return {password:a,cookieName:e.cookieName||"satellite_siwe",cookieOptions:S}}function C(t){if(!t)return console.warn("SIWE WARN: SIWE_SESSION_URL is not defined. Defaulting domain check to 'localhost'."),"localhost";try{return new URL(t.startsWith("http")?t:`https://${t}`).host}catch(e){return console.error(`SIWE ERROR: Invalid URL provided in SIWE_SESSION_URL: ${t}. Error: ${typeof e=="string"?e:e.message}`),"localhost"}}function _(t={}){let e=t.options||{},c=l(t);async function a(o){let s=new Response;return {session:await getIronSession(o,s,c),response:s}}async function f(o){try{let{message:s,signature:d}=await o.json();if(!s||!d)return NextResponse.json({message:"Missing message or signature"},{status:400});e.afterNonce&&await e.afterNonce();let n=parseSiweMessage(s);if(!n||!n.address||!n.chainId)return NextResponse.json({message:"Invalid SIWE message format"},{status:400});let r=C(process.env.SIWE_SESSION_URL);if(!validateSiweMessage({message:n,domain:r}))return NextResponse.json({message:"SIWE message validation failed"},{status:401});if(!await verifyMessage({address:n.address,message:s,signature:d}))return NextResponse.json({message:"SIWE signature verification failed"},{status:401});e.afterVerify&&await e.afterVerify();let{session:p,response:E}=await a(o);p.address=n.address,p.chainId=n.chainId,p.isLoggedIn=!0,await p.save(),e.afterSession&&await e.afterSession();let m=NextResponse.json({isLoggedIn:!0,address:p.address,chainId:p.chainId},{status:200});return E.headers.forEach((w,I)=>{I.toLowerCase()==="set-cookie"&&m.headers.append("Set-Cookie",w);}),m}catch(s){return console.error("SIWE CRITICAL LOGIN ERROR:",s),NextResponse.json({message:"Internal Server Error during login"},{status:500})}}async function S(o){let{session:s,response:d}=await a(o);if(o.method==="POST"||o.method==="DELETE"){s.destroy(),e.afterLogout&&await e.afterLogout();let n=NextResponse.json({isLoggedIn:false},{status:200});return d.headers.forEach((r,g)=>{g.toLowerCase()==="set-cookie"&&n.headers.append("Set-Cookie",r);}),n}return s.isLoggedIn&&s.address&&s.chainId?NextResponse.json({isLoggedIn:true,address:s.address,chainId:s.chainId}):NextResponse.json({isLoggedIn:false},{status:401})}let u=async(o,s)=>{let n=(await s.params||{})?.siwe||[],r=n[n.length-1];return r==="login"&&o.method==="POST"?f(o):r==="session"&&o.method==="GET"||r==="logout"&&(o.method==="POST"||o.method==="DELETE")?S(o):Promise.resolve(new Response("Not Found",{status:404}))};return {GET:u,POST:u,DELETE:u}}
1
+ import {getIronSession}from'iron-session';import {NextResponse}from'next/server';import {verifyMessage}from'viem';import {parseSiweMessage,validateSiweMessage}from'viem/siwe';function l(t){let e=t.session||{},c=process.env.SIWE_SESSION_SECRET,a=e.password||c;if(!a||a.length<32)throw new Error("SIWE Error: Iron Session requires a 'password' option (min 32 chars) or SIWE_SESSION_SECRET environment variable to be set.");let S={...{secure:process.env.NODE_ENV==="production",maxAge:300*60,httpOnly:true,sameSite:"lax",path:"/"},...e.cookieOptions};return {password:a,cookieName:e.cookieName||"satellite_siwe",cookieOptions:S}}function L(t){if(!t)return console.warn("SIWE WARN: SIWE_SESSION_URL is not defined. Defaulting domain check to 'localhost'."),"localhost";try{return new URL(t.startsWith("http")?t:`https://${t}`).host}catch(e){return console.error(`SIWE ERROR: Invalid URL provided in SIWE_SESSION_URL: ${t}. Error: ${typeof e=="string"?e:e.message}`),"localhost"}}function _(t={}){let e=t.options||{},c=l(t);async function a(o){let s=new Response;return {session:await getIronSession(o,s,c),response:s}}async function f(o){try{let{message:s,signature:d}=await o.json();if(!s||!d)return NextResponse.json({message:"Missing message or signature"},{status:400});e.afterNonce&&await e.afterNonce();let n=parseSiweMessage(s);if(!n||!n.address||!n.chainId)return NextResponse.json({message:"Invalid SIWE message format"},{status:400});let r=L(process.env.SIWE_SESSION_URL);if(!validateSiweMessage({message:n,domain:r}))return NextResponse.json({message:"SIWE message validation failed"},{status:401});if(!await verifyMessage({address:n.address,message:s,signature:d}))return NextResponse.json({message:"SIWE signature verification failed"},{status:401});e.afterVerify&&await e.afterVerify();let{session:p,response:E}=await a(o);p.address=n.address,p.chainId=n.chainId,p.isLoggedIn=!0,await p.save(),e.afterSession&&await e.afterSession();let m=NextResponse.json({isLoggedIn:!0,address:p.address,chainId:p.chainId},{status:200});return E.headers.forEach((I,w)=>{w.toLowerCase()==="set-cookie"&&m.headers.append("Set-Cookie",I);}),m}catch(s){return console.error("SIWE CRITICAL AUTHENTICATION ERROR:",s),NextResponse.json({message:"Internal Server Error during session validation"},{status:500})}}async function S(o){let{session:s,response:d}=await a(o);if(o.method==="POST"||o.method==="DELETE"){s.destroy(),e.afterLogout&&await e.afterLogout();let n=NextResponse.json({isLoggedIn:false},{status:200});return d.headers.forEach((r,g)=>{g.toLowerCase()==="set-cookie"&&n.headers.append("Set-Cookie",r);}),n}return s.isLoggedIn&&s.address&&s.chainId?NextResponse.json({isLoggedIn:true,address:s.address,chainId:s.chainId}):NextResponse.json({isLoggedIn:false},{status:401})}let u=async(o,s)=>{let n=(await s.params||{})?.siwe||[],r=n[n.length-1];return r==="login"&&o.method==="POST"?f(o):r==="session"&&o.method==="GET"||r==="logout"&&(o.method==="POST"||o.method==="DELETE")?S(o):Promise.resolve(new Response("Not Found",{status:404}))};return {GET:u,POST:u,DELETE:u}}
2
2
  export{_ as createSiweApiHandler,l as getSessionOptions};
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@tuwaio/satellite-siwe-next-auth",
3
- "version": "0.3.5",
3
+ "version": "0.3.6",
4
4
  "private": false,
5
5
  "author": "Oleksandr Tkach",
6
6
  "license": "Apache-2.0",
7
- "description": "A robust connector module for enabling secure Web3 authentication in Next.js by combining NextAuth with the SIWE standard.",
7
+ "description": "Robust server-side session authentication adapter mapping cryptographic signatures to the SIWE standard and NextAuth.",
8
8
  "main": "./dist/index.js",
9
9
  "module": "./dist/index.mjs",
10
10
  "types": "./dist/index.d.ts",
@@ -59,15 +59,15 @@
59
59
  "wagmi": "3.x.x"
60
60
  },
61
61
  "devDependencies": {
62
- "@wagmi/core": "^3.5.0",
63
- "@types/react": "^19.2.15",
64
- "next": "16.2.6",
62
+ "@wagmi/core": "^3.5.1",
63
+ "@types/react": "^19.2.17",
64
+ "next": "16.2.9",
65
65
  "iron-session": "^8.0.4",
66
- "react": "^19.2.6",
66
+ "react": "^19.2.7",
67
67
  "tsup": "^8.5.1",
68
68
  "typescript": "^6.0.3",
69
- "viem": "^2.51.3",
70
- "wagmi": "^3.6.16"
69
+ "viem": "^2.53.1",
70
+ "wagmi": "^3.6.17"
71
71
  },
72
72
  "scripts": {
73
73
  "start": "tsup src/index.ts --watch",