@solvapay/auth 1.0.0-preview.9 → 1.0.1-preview.2
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/LICENSE.md +21 -0
- package/README.md +149 -33
- package/dist/adapter-Lx7F0MH7.d.cts +64 -0
- package/dist/adapter-Lx7F0MH7.d.ts +64 -0
- package/dist/{chunk-B4VVTQWS.js → chunk-IOLE5BMD.js} +6 -8
- package/dist/index.cjs +129 -8
- package/dist/index.d.cts +68 -1
- package/dist/index.d.ts +68 -1
- package/dist/index.js +122 -1
- package/dist/mock.d.cts +1 -1
- package/dist/mock.d.ts +1 -1
- package/dist/next-utils.cjs +6 -8
- package/dist/next-utils.d.cts +91 -16
- package/dist/next-utils.d.ts +91 -16
- package/dist/next-utils.js +1 -1
- package/dist/supabase.d.cts +1 -1
- package/dist/supabase.d.ts +1 -1
- package/package.json +6 -5
- package/dist/adapter-4HTpALyc.d.cts +0 -32
- package/dist/adapter-4HTpALyc.d.ts +0 -32
package/LICENSE.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 SolvaPay Inc.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -21,25 +21,27 @@ pnpm add jose
|
|
|
21
21
|
Extract user ID from Supabase JWT tokens:
|
|
22
22
|
|
|
23
23
|
```ts
|
|
24
|
-
import { SupabaseAuthAdapter } from '@solvapay/auth/supabase'
|
|
24
|
+
import { SupabaseAuthAdapter } from '@solvapay/auth/supabase'
|
|
25
25
|
|
|
26
26
|
const auth = new SupabaseAuthAdapter({
|
|
27
|
-
jwtSecret: process.env.SUPABASE_JWT_SECRET
|
|
28
|
-
})
|
|
27
|
+
jwtSecret: process.env.SUPABASE_JWT_SECRET!,
|
|
28
|
+
})
|
|
29
29
|
|
|
30
30
|
// In your API route
|
|
31
31
|
export async function POST(request: Request) {
|
|
32
|
-
const userId = await auth.getUserIdFromRequest(request)
|
|
33
|
-
|
|
32
|
+
const userId = await auth.getUserIdFromRequest(request)
|
|
33
|
+
|
|
34
34
|
if (!userId) {
|
|
35
|
-
return new Response('Unauthorized', { status: 401 })
|
|
35
|
+
return new Response('Unauthorized', { status: 401 })
|
|
36
36
|
}
|
|
37
|
-
|
|
38
|
-
// Use userId as
|
|
39
|
-
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
37
|
+
|
|
38
|
+
// Use userId as cache key and externalRef
|
|
39
|
+
// The ensureCustomer method returns the SolvaPay backend customer reference
|
|
40
|
+
const solvaPay = createSolvaPay({ apiKey: process.env.SOLVAPAY_SECRET_KEY! })
|
|
41
|
+
const ensuredCustomerRef = await solvaPay.ensureCustomer(userId, userId)
|
|
42
|
+
const customer = await solvaPay.getCustomer({ customerRef: ensuredCustomerRef })
|
|
43
|
+
|
|
44
|
+
return Response.json({ userId, customer })
|
|
43
45
|
}
|
|
44
46
|
```
|
|
45
47
|
|
|
@@ -48,14 +50,14 @@ export async function POST(request: Request) {
|
|
|
48
50
|
For testing and examples:
|
|
49
51
|
|
|
50
52
|
```ts
|
|
51
|
-
import { MockAuthAdapter } from '@solvapay/auth/mock'
|
|
53
|
+
import { MockAuthAdapter } from '@solvapay/auth/mock'
|
|
52
54
|
|
|
53
|
-
const auth = new MockAuthAdapter()
|
|
55
|
+
const auth = new MockAuthAdapter()
|
|
54
56
|
|
|
55
57
|
// Set header: x-mock-user-id: user_123
|
|
56
58
|
// Or set env: MOCK_USER_ID=user_123
|
|
57
59
|
|
|
58
|
-
const userId = await auth.getUserIdFromRequest(request)
|
|
60
|
+
const userId = await auth.getUserIdFromRequest(request)
|
|
59
61
|
```
|
|
60
62
|
|
|
61
63
|
### Integration with SolvaPay Server SDK
|
|
@@ -63,27 +65,102 @@ const userId = await auth.getUserIdFromRequest(request);
|
|
|
63
65
|
Use auth adapters with the `getCustomerRef` option:
|
|
64
66
|
|
|
65
67
|
```ts
|
|
66
|
-
import { createSolvaPay } from '@solvapay/server'
|
|
67
|
-
import { SupabaseAuthAdapter } from '@solvapay/auth/supabase'
|
|
68
|
+
import { createSolvaPay } from '@solvapay/server'
|
|
69
|
+
import { SupabaseAuthAdapter } from '@solvapay/auth/supabase'
|
|
68
70
|
|
|
69
71
|
const auth = new SupabaseAuthAdapter({
|
|
70
|
-
jwtSecret: process.env.SUPABASE_JWT_SECRET
|
|
71
|
-
})
|
|
72
|
+
jwtSecret: process.env.SUPABASE_JWT_SECRET!,
|
|
73
|
+
})
|
|
72
74
|
|
|
73
|
-
const solvaPay = createSolvaPay({ apiKey: process.env.SOLVAPAY_SECRET_KEY! })
|
|
75
|
+
const solvaPay = createSolvaPay({ apiKey: process.env.SOLVAPAY_SECRET_KEY! })
|
|
74
76
|
|
|
75
77
|
// Use with Next.js adapter
|
|
76
|
-
export const POST = solvaPay.payable({
|
|
77
|
-
async
|
|
78
|
-
return { result: 'success' }
|
|
78
|
+
export const POST = solvaPay.payable({ product: 'my-api' }).next(
|
|
79
|
+
async args => {
|
|
80
|
+
return { result: 'success' }
|
|
79
81
|
},
|
|
80
82
|
{
|
|
81
|
-
getCustomerRef: async
|
|
82
|
-
const userId = await auth.getUserIdFromRequest(req)
|
|
83
|
-
return userId ?? 'anonymous'
|
|
84
|
-
}
|
|
83
|
+
getCustomerRef: async req => {
|
|
84
|
+
const userId = await auth.getUserIdFromRequest(req)
|
|
85
|
+
return userId ?? 'anonymous'
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
)
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Next.js Route Utilities
|
|
92
|
+
|
|
93
|
+
The package also provides helper functions for extracting user information in Next.js API routes:
|
|
94
|
+
|
|
95
|
+
### getUserIdFromRequest
|
|
96
|
+
|
|
97
|
+
Extract user ID from request headers (commonly set by middleware):
|
|
98
|
+
|
|
99
|
+
```ts
|
|
100
|
+
import { getUserIdFromRequest } from '@solvapay/auth'
|
|
101
|
+
|
|
102
|
+
export async function GET(request: Request) {
|
|
103
|
+
const userId = getUserIdFromRequest(request)
|
|
104
|
+
|
|
105
|
+
if (!userId) {
|
|
106
|
+
return new Response('Unauthorized', { status: 401 })
|
|
85
107
|
}
|
|
86
|
-
|
|
108
|
+
|
|
109
|
+
// Use userId...
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### requireUserId
|
|
114
|
+
|
|
115
|
+
Require user ID or return error response:
|
|
116
|
+
|
|
117
|
+
```ts
|
|
118
|
+
import { requireUserId } from '@solvapay/auth'
|
|
119
|
+
|
|
120
|
+
export async function GET(request: Request) {
|
|
121
|
+
const userIdOrError = requireUserId(request)
|
|
122
|
+
|
|
123
|
+
if (userIdOrError instanceof Response) {
|
|
124
|
+
return userIdOrError // Returns 401 error
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// userIdOrError is now a string
|
|
128
|
+
const userId = userIdOrError
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### getUserEmailFromRequest
|
|
133
|
+
|
|
134
|
+
Extract email from Supabase JWT token in Authorization header:
|
|
135
|
+
|
|
136
|
+
```ts
|
|
137
|
+
import { getUserEmailFromRequest } from '@solvapay/auth'
|
|
138
|
+
|
|
139
|
+
export async function GET(request: Request) {
|
|
140
|
+
const email = await getUserEmailFromRequest(request)
|
|
141
|
+
// Returns email string or null
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### getUserNameFromRequest
|
|
146
|
+
|
|
147
|
+
Extract name from Supabase JWT token in Authorization header:
|
|
148
|
+
|
|
149
|
+
```ts
|
|
150
|
+
import { getUserNameFromRequest } from '@solvapay/auth'
|
|
151
|
+
|
|
152
|
+
export async function GET(request: Request) {
|
|
153
|
+
const name = await getUserNameFromRequest(request)
|
|
154
|
+
// Returns name string or null
|
|
155
|
+
}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
**Note:** `getUserEmailFromRequest` and `getUserNameFromRequest` require the `SUPABASE_JWT_SECRET` environment variable to be set, or you can pass it as an option:
|
|
159
|
+
|
|
160
|
+
```ts
|
|
161
|
+
const email = await getUserEmailFromRequest(request, {
|
|
162
|
+
jwtSecret: process.env.SUPABASE_JWT_SECRET!,
|
|
163
|
+
})
|
|
87
164
|
```
|
|
88
165
|
|
|
89
166
|
## API Reference
|
|
@@ -92,7 +169,7 @@ export const POST = solvaPay.payable({ agent: 'my-api' }).next(
|
|
|
92
169
|
|
|
93
170
|
```ts
|
|
94
171
|
interface AuthAdapter {
|
|
95
|
-
getUserIdFromRequest(req: Request | RequestLike): Promise<string | null
|
|
172
|
+
getUserIdFromRequest(req: Request | RequestLike): Promise<string | null>
|
|
96
173
|
}
|
|
97
174
|
```
|
|
98
175
|
|
|
@@ -100,12 +177,13 @@ interface AuthAdapter {
|
|
|
100
177
|
|
|
101
178
|
```ts
|
|
102
179
|
class SupabaseAuthAdapter implements AuthAdapter {
|
|
103
|
-
constructor(config: { jwtSecret: string })
|
|
104
|
-
getUserIdFromRequest(req: Request | RequestLike): Promise<string | null
|
|
180
|
+
constructor(config: { jwtSecret: string })
|
|
181
|
+
getUserIdFromRequest(req: Request | RequestLike): Promise<string | null>
|
|
105
182
|
}
|
|
106
183
|
```
|
|
107
184
|
|
|
108
185
|
**Configuration:**
|
|
186
|
+
|
|
109
187
|
- `jwtSecret`: Supabase JWT secret from dashboard (Settings → API → JWT Secret)
|
|
110
188
|
|
|
111
189
|
**Returns:** User ID from `sub` claim or `null` if missing/invalid
|
|
@@ -114,11 +192,12 @@ class SupabaseAuthAdapter implements AuthAdapter {
|
|
|
114
192
|
|
|
115
193
|
```ts
|
|
116
194
|
class MockAuthAdapter implements AuthAdapter {
|
|
117
|
-
getUserIdFromRequest(req: Request | RequestLike): Promise<string | null
|
|
195
|
+
getUserIdFromRequest(req: Request | RequestLike): Promise<string | null>
|
|
118
196
|
}
|
|
119
197
|
```
|
|
120
198
|
|
|
121
199
|
**Behavior:**
|
|
200
|
+
|
|
122
201
|
1. Checks `x-mock-user-id` header
|
|
123
202
|
2. Falls back to `MOCK_USER_ID` environment variable
|
|
124
203
|
3. Returns `null` if neither is found
|
|
@@ -130,5 +209,42 @@ class MockAuthAdapter implements AuthAdapter {
|
|
|
130
209
|
|
|
131
210
|
## Edge Runtime Support
|
|
132
211
|
|
|
133
|
-
Both adapters work in Edge runtimes (Vercel Edge Functions, Cloudflare Workers, etc.). SupabaseAuthAdapter
|
|
212
|
+
Both adapters and Next.js utilities work in Edge runtimes (Vercel Edge Functions, Cloudflare Workers, etc.). SupabaseAuthAdapter and JWT utilities use dynamic imports for `jose` to ensure Edge compatibility.
|
|
213
|
+
|
|
214
|
+
## Next.js Integration
|
|
215
|
+
|
|
216
|
+
These utilities work seamlessly with Next.js authentication middleware.
|
|
217
|
+
In Next.js 16, this middleware lives in `proxy.ts` and typically sets the
|
|
218
|
+
`x-user-id` header after authentication:
|
|
219
|
+
|
|
220
|
+
```ts
|
|
221
|
+
// proxy.ts
|
|
222
|
+
import { NextResponse } from 'next/server'
|
|
223
|
+
import type { NextRequest } from 'next/server'
|
|
224
|
+
|
|
225
|
+
export async function proxy(request: NextRequest) {
|
|
226
|
+
// Extract user ID from your auth system (Supabase, Auth0, etc.)
|
|
227
|
+
const userId = await getUserIdFromAuth(request)
|
|
228
|
+
|
|
229
|
+
// Clone the request and add user ID header
|
|
230
|
+
const requestHeaders = new Headers(request.headers)
|
|
231
|
+
requestHeaders.set('x-user-id', userId)
|
|
232
|
+
|
|
233
|
+
return NextResponse.next({
|
|
234
|
+
request: {
|
|
235
|
+
headers: requestHeaders,
|
|
236
|
+
},
|
|
237
|
+
})
|
|
238
|
+
}
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
Then in your API routes, use the utilities:
|
|
134
242
|
|
|
243
|
+
```ts
|
|
244
|
+
import { requireUserId } from '@solvapay/auth'
|
|
245
|
+
|
|
246
|
+
export async function GET(request: Request) {
|
|
247
|
+
const userId = requireUserId(request)
|
|
248
|
+
// userId is guaranteed to exist or an error response is returned
|
|
249
|
+
}
|
|
250
|
+
```
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auth Adapter Interface
|
|
3
|
+
*
|
|
4
|
+
* Defines the contract for authentication adapters that extract user IDs from requests.
|
|
5
|
+
* Implementations should never throw - return null if authentication fails or is missing.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Request-like object that has headers
|
|
9
|
+
*/
|
|
10
|
+
interface RequestLike {
|
|
11
|
+
headers: Headers | {
|
|
12
|
+
get(name: string): string | null;
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Authentication adapter interface for extracting user IDs from requests.
|
|
17
|
+
*
|
|
18
|
+
* This interface defines the contract for authentication adapters that can
|
|
19
|
+
* extract user IDs from various authentication systems (Supabase, custom JWT,
|
|
20
|
+
* session-based auth, etc.). Adapters should never throw - return null if
|
|
21
|
+
* authentication fails or is missing.
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```typescript
|
|
25
|
+
* import type { AuthAdapter } from '@solvapay/auth';
|
|
26
|
+
*
|
|
27
|
+
* // Custom adapter implementation
|
|
28
|
+
* const myAdapter: AuthAdapter = {
|
|
29
|
+
* async getUserIdFromRequest(req) {
|
|
30
|
+
* // Extract user ID from request (JWT, session, etc.)
|
|
31
|
+
* const token = req.headers.get('authorization')?.replace('Bearer ', '');
|
|
32
|
+
* if (!token) return null;
|
|
33
|
+
*
|
|
34
|
+
* // Validate and extract user ID
|
|
35
|
+
* const payload = await verifyToken(token);
|
|
36
|
+
* return payload.userId || null;
|
|
37
|
+
* }
|
|
38
|
+
* };
|
|
39
|
+
* ```
|
|
40
|
+
*
|
|
41
|
+
* @see SupabaseAuthAdapter (from `@solvapay/auth/supabase`) for Supabase implementation
|
|
42
|
+
* @see {@link MockAuthAdapter} for testing implementation
|
|
43
|
+
* @since 1.0.0
|
|
44
|
+
*/
|
|
45
|
+
interface AuthAdapter {
|
|
46
|
+
/**
|
|
47
|
+
* Extract the authenticated user ID from a request.
|
|
48
|
+
*
|
|
49
|
+
* This method should:
|
|
50
|
+
* - Never throw exceptions (return null on failure)
|
|
51
|
+
* - Handle missing/invalid authentication gracefully
|
|
52
|
+
* - Work with both Request objects and objects with headers
|
|
53
|
+
*
|
|
54
|
+
* @param req - Request object or object with headers
|
|
55
|
+
* @returns The user ID string if authenticated, null otherwise
|
|
56
|
+
*
|
|
57
|
+
* @remarks
|
|
58
|
+
* This method should never throw. If authentication fails or is missing,
|
|
59
|
+
* return null and let the caller decide how to handle unauthenticated requests.
|
|
60
|
+
*/
|
|
61
|
+
getUserIdFromRequest(req: Request | RequestLike): Promise<string | null>;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export type { AuthAdapter as A, RequestLike as R };
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auth Adapter Interface
|
|
3
|
+
*
|
|
4
|
+
* Defines the contract for authentication adapters that extract user IDs from requests.
|
|
5
|
+
* Implementations should never throw - return null if authentication fails or is missing.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Request-like object that has headers
|
|
9
|
+
*/
|
|
10
|
+
interface RequestLike {
|
|
11
|
+
headers: Headers | {
|
|
12
|
+
get(name: string): string | null;
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Authentication adapter interface for extracting user IDs from requests.
|
|
17
|
+
*
|
|
18
|
+
* This interface defines the contract for authentication adapters that can
|
|
19
|
+
* extract user IDs from various authentication systems (Supabase, custom JWT,
|
|
20
|
+
* session-based auth, etc.). Adapters should never throw - return null if
|
|
21
|
+
* authentication fails or is missing.
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```typescript
|
|
25
|
+
* import type { AuthAdapter } from '@solvapay/auth';
|
|
26
|
+
*
|
|
27
|
+
* // Custom adapter implementation
|
|
28
|
+
* const myAdapter: AuthAdapter = {
|
|
29
|
+
* async getUserIdFromRequest(req) {
|
|
30
|
+
* // Extract user ID from request (JWT, session, etc.)
|
|
31
|
+
* const token = req.headers.get('authorization')?.replace('Bearer ', '');
|
|
32
|
+
* if (!token) return null;
|
|
33
|
+
*
|
|
34
|
+
* // Validate and extract user ID
|
|
35
|
+
* const payload = await verifyToken(token);
|
|
36
|
+
* return payload.userId || null;
|
|
37
|
+
* }
|
|
38
|
+
* };
|
|
39
|
+
* ```
|
|
40
|
+
*
|
|
41
|
+
* @see SupabaseAuthAdapter (from `@solvapay/auth/supabase`) for Supabase implementation
|
|
42
|
+
* @see {@link MockAuthAdapter} for testing implementation
|
|
43
|
+
* @since 1.0.0
|
|
44
|
+
*/
|
|
45
|
+
interface AuthAdapter {
|
|
46
|
+
/**
|
|
47
|
+
* Extract the authenticated user ID from a request.
|
|
48
|
+
*
|
|
49
|
+
* This method should:
|
|
50
|
+
* - Never throw exceptions (return null on failure)
|
|
51
|
+
* - Handle missing/invalid authentication gracefully
|
|
52
|
+
* - Work with both Request objects and objects with headers
|
|
53
|
+
*
|
|
54
|
+
* @param req - Request object or object with headers
|
|
55
|
+
* @returns The user ID string if authenticated, null otherwise
|
|
56
|
+
*
|
|
57
|
+
* @remarks
|
|
58
|
+
* This method should never throw. If authentication fails or is missing,
|
|
59
|
+
* return null and let the caller decide how to handle unauthenticated requests.
|
|
60
|
+
*/
|
|
61
|
+
getUserIdFromRequest(req: Request | RequestLike): Promise<string | null>;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export type { AuthAdapter as A, RequestLike as R };
|
|
@@ -46,7 +46,8 @@ async function getUserNameFromRequest(request, options) {
|
|
|
46
46
|
const { payload } = await jwtVerify(token, secret, {
|
|
47
47
|
algorithms: ["HS256"]
|
|
48
48
|
});
|
|
49
|
-
const
|
|
49
|
+
const payloadWithMetadata = payload;
|
|
50
|
+
const name = payloadWithMetadata.user_metadata?.full_name || payloadWithMetadata.user_metadata?.name || payloadWithMetadata.name || null;
|
|
50
51
|
return name ? String(name) : null;
|
|
51
52
|
} catch {
|
|
52
53
|
return null;
|
|
@@ -58,13 +59,10 @@ function requireUserId(request, options) {
|
|
|
58
59
|
const errorDetails = options?.errorDetails || "User ID not found. Ensure middleware is configured.";
|
|
59
60
|
const userId = request.headers.get(headerName);
|
|
60
61
|
if (!userId) {
|
|
61
|
-
return new Response(
|
|
62
|
-
|
|
63
|
-
{
|
|
64
|
-
|
|
65
|
-
headers: { "Content-Type": "application/json" }
|
|
66
|
-
}
|
|
67
|
-
);
|
|
62
|
+
return new Response(JSON.stringify({ error: errorMessage, details: errorDetails }), {
|
|
63
|
+
status: 401,
|
|
64
|
+
headers: { "Content-Type": "application/json" }
|
|
65
|
+
});
|
|
68
66
|
}
|
|
69
67
|
return userId;
|
|
70
68
|
}
|
package/dist/index.cjs
CHANGED
|
@@ -31,6 +31,8 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
31
31
|
var index_exports = {};
|
|
32
32
|
__export(index_exports, {
|
|
33
33
|
MockAuthAdapter: () => MockAuthAdapter,
|
|
34
|
+
SolvapayAuthAdapter: () => SolvapayAuthAdapter,
|
|
35
|
+
SolvapayOAuthClient: () => SolvapayOAuthClient,
|
|
34
36
|
getUserEmailFromRequest: () => getUserEmailFromRequest,
|
|
35
37
|
getUserIdFromRequest: () => getUserIdFromRequest,
|
|
36
38
|
getUserNameFromRequest: () => getUserNameFromRequest,
|
|
@@ -53,6 +55,125 @@ var MockAuthAdapter = class {
|
|
|
53
55
|
}
|
|
54
56
|
};
|
|
55
57
|
|
|
58
|
+
// src/solvapay.ts
|
|
59
|
+
var SolvapayAuthAdapter = class {
|
|
60
|
+
apiBaseUrl;
|
|
61
|
+
constructor(config) {
|
|
62
|
+
this.apiBaseUrl = config.apiBaseUrl.replace(/\/$/, "");
|
|
63
|
+
}
|
|
64
|
+
async getUserIdFromRequest(req) {
|
|
65
|
+
try {
|
|
66
|
+
const headers = "headers" in req ? req.headers : req;
|
|
67
|
+
const authHeader = headers.get("authorization");
|
|
68
|
+
if (!authHeader || !authHeader.startsWith("Bearer ")) {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
const token = authHeader.slice(7);
|
|
72
|
+
if (!token) {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
const response = await fetch(`${this.apiBaseUrl}/v1/customer/auth/userinfo`, {
|
|
76
|
+
method: "GET",
|
|
77
|
+
headers: {
|
|
78
|
+
Authorization: `Bearer ${token}`,
|
|
79
|
+
"Content-Type": "application/json"
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
if (!response.ok) {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
const userInfo = await response.json();
|
|
86
|
+
return userInfo.sub || userInfo.id || userInfo.customerRef || null;
|
|
87
|
+
} catch {
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
var SolvapayOAuthClient = class {
|
|
93
|
+
constructor(config) {
|
|
94
|
+
this.config = config;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Get the URL to redirect the user to for login
|
|
98
|
+
*/
|
|
99
|
+
getAuthorizationUrl(options = {}) {
|
|
100
|
+
const params = new URLSearchParams({
|
|
101
|
+
response_type: "code",
|
|
102
|
+
client_id: this.config.clientId,
|
|
103
|
+
redirect_uri: this.config.redirectUri
|
|
104
|
+
});
|
|
105
|
+
if (options.state) params.append("state", options.state);
|
|
106
|
+
if (options.scope) params.append("scope", options.scope);
|
|
107
|
+
return `${this.config.authUrl}?${params.toString()}`;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Exchange an authorization code for an access token
|
|
111
|
+
*/
|
|
112
|
+
async exchangeCodeForToken(code) {
|
|
113
|
+
const body = new URLSearchParams({
|
|
114
|
+
grant_type: "authorization_code",
|
|
115
|
+
code,
|
|
116
|
+
client_id: this.config.clientId,
|
|
117
|
+
redirect_uri: this.config.redirectUri
|
|
118
|
+
});
|
|
119
|
+
if (this.config.clientSecret) {
|
|
120
|
+
body.append("client_secret", this.config.clientSecret);
|
|
121
|
+
}
|
|
122
|
+
const response = await fetch(this.config.tokenUrl, {
|
|
123
|
+
method: "POST",
|
|
124
|
+
headers: {
|
|
125
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
126
|
+
Accept: "application/json"
|
|
127
|
+
},
|
|
128
|
+
body: body.toString()
|
|
129
|
+
});
|
|
130
|
+
if (!response.ok) {
|
|
131
|
+
const text = await response.text();
|
|
132
|
+
throw new Error(`Failed to exchange code: ${response.status} ${text}`);
|
|
133
|
+
}
|
|
134
|
+
return response.json();
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Revoke a token (Sign Out)
|
|
138
|
+
*/
|
|
139
|
+
async revokeToken(token) {
|
|
140
|
+
const revokeUrl = this.config.tokenUrl.replace("/token", "/revoke");
|
|
141
|
+
const body = new URLSearchParams({
|
|
142
|
+
token,
|
|
143
|
+
client_id: this.config.clientId
|
|
144
|
+
});
|
|
145
|
+
if (this.config.clientSecret) {
|
|
146
|
+
body.append("client_secret", this.config.clientSecret);
|
|
147
|
+
}
|
|
148
|
+
await fetch(revokeUrl, {
|
|
149
|
+
method: "POST",
|
|
150
|
+
headers: {
|
|
151
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
152
|
+
},
|
|
153
|
+
body: body.toString()
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Get user info using access token
|
|
158
|
+
*/
|
|
159
|
+
async getUserInfo(accessToken, userInfoUrl) {
|
|
160
|
+
if (!userInfoUrl) {
|
|
161
|
+
throw new Error("userInfoUrl is required");
|
|
162
|
+
}
|
|
163
|
+
const response = await fetch(userInfoUrl, {
|
|
164
|
+
method: "GET",
|
|
165
|
+
headers: {
|
|
166
|
+
Authorization: `Bearer ${accessToken}`,
|
|
167
|
+
Accept: "application/json"
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
if (!response.ok) {
|
|
171
|
+
throw new Error(`Failed to fetch user info: ${response.status}`);
|
|
172
|
+
}
|
|
173
|
+
return response.json();
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
|
|
56
177
|
// src/next-utils.ts
|
|
57
178
|
function getUserIdFromRequest(request, options) {
|
|
58
179
|
const headerName = options?.headerName || "x-user-id";
|
|
@@ -101,7 +222,8 @@ async function getUserNameFromRequest(request, options) {
|
|
|
101
222
|
const { payload } = await jwtVerify(token, secret, {
|
|
102
223
|
algorithms: ["HS256"]
|
|
103
224
|
});
|
|
104
|
-
const
|
|
225
|
+
const payloadWithMetadata = payload;
|
|
226
|
+
const name = payloadWithMetadata.user_metadata?.full_name || payloadWithMetadata.user_metadata?.name || payloadWithMetadata.name || null;
|
|
105
227
|
return name ? String(name) : null;
|
|
106
228
|
} catch {
|
|
107
229
|
return null;
|
|
@@ -113,19 +235,18 @@ function requireUserId(request, options) {
|
|
|
113
235
|
const errorDetails = options?.errorDetails || "User ID not found. Ensure middleware is configured.";
|
|
114
236
|
const userId = request.headers.get(headerName);
|
|
115
237
|
if (!userId) {
|
|
116
|
-
return new Response(
|
|
117
|
-
|
|
118
|
-
{
|
|
119
|
-
|
|
120
|
-
headers: { "Content-Type": "application/json" }
|
|
121
|
-
}
|
|
122
|
-
);
|
|
238
|
+
return new Response(JSON.stringify({ error: errorMessage, details: errorDetails }), {
|
|
239
|
+
status: 401,
|
|
240
|
+
headers: { "Content-Type": "application/json" }
|
|
241
|
+
});
|
|
123
242
|
}
|
|
124
243
|
return userId;
|
|
125
244
|
}
|
|
126
245
|
// Annotate the CommonJS export names for ESM import in node:
|
|
127
246
|
0 && (module.exports = {
|
|
128
247
|
MockAuthAdapter,
|
|
248
|
+
SolvapayAuthAdapter,
|
|
249
|
+
SolvapayOAuthClient,
|
|
129
250
|
getUserEmailFromRequest,
|
|
130
251
|
getUserIdFromRequest,
|
|
131
252
|
getUserNameFromRequest,
|
package/dist/index.d.cts
CHANGED
|
@@ -1,3 +1,70 @@
|
|
|
1
|
-
|
|
1
|
+
import { A as AuthAdapter, R as RequestLike } from './adapter-Lx7F0MH7.cjs';
|
|
2
2
|
export { MockAuthAdapter, MockAuthAdapter as MockAuthAdapterType } from './mock.cjs';
|
|
3
3
|
export { getUserEmailFromRequest, getUserIdFromRequest, getUserNameFromRequest, requireUserId } from './next-utils.cjs';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* SolvaPay Auth Adapter
|
|
7
|
+
*
|
|
8
|
+
* Validates SolvaPay OAuth tokens by calling the userinfo endpoint.
|
|
9
|
+
* Suitable for validating opaque tokens issued by SolvaPay's hosted OAuth service.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
interface SolvapayAuthAdapterConfig {
|
|
13
|
+
/**
|
|
14
|
+
* SolvaPay API Base URL (e.g. https://api.solvapay.com)
|
|
15
|
+
*/
|
|
16
|
+
apiBaseUrl: string;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* SolvaPay authentication adapter
|
|
20
|
+
*
|
|
21
|
+
* Validates tokens by calling the SolvaPay userinfo endpoint.
|
|
22
|
+
* This is required for opaque tokens which cannot be verified offline.
|
|
23
|
+
*/
|
|
24
|
+
declare class SolvapayAuthAdapter implements AuthAdapter {
|
|
25
|
+
private apiBaseUrl;
|
|
26
|
+
constructor(config: SolvapayAuthAdapterConfig);
|
|
27
|
+
getUserIdFromRequest(req: Request | RequestLike): Promise<string | null>;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* SolvaPay OAuth Client Helpers
|
|
31
|
+
*/
|
|
32
|
+
interface SolvapayOAuthConfig {
|
|
33
|
+
clientId: string;
|
|
34
|
+
clientSecret?: string;
|
|
35
|
+
authUrl: string;
|
|
36
|
+
tokenUrl: string;
|
|
37
|
+
redirectUri: string;
|
|
38
|
+
}
|
|
39
|
+
interface TokenResponse {
|
|
40
|
+
access_token: string;
|
|
41
|
+
token_type: string;
|
|
42
|
+
expires_in: number;
|
|
43
|
+
refresh_token?: string;
|
|
44
|
+
scope?: string;
|
|
45
|
+
}
|
|
46
|
+
declare class SolvapayOAuthClient {
|
|
47
|
+
private config;
|
|
48
|
+
constructor(config: SolvapayOAuthConfig);
|
|
49
|
+
/**
|
|
50
|
+
* Get the URL to redirect the user to for login
|
|
51
|
+
*/
|
|
52
|
+
getAuthorizationUrl(options?: {
|
|
53
|
+
state?: string;
|
|
54
|
+
scope?: string;
|
|
55
|
+
}): string;
|
|
56
|
+
/**
|
|
57
|
+
* Exchange an authorization code for an access token
|
|
58
|
+
*/
|
|
59
|
+
exchangeCodeForToken(code: string): Promise<TokenResponse>;
|
|
60
|
+
/**
|
|
61
|
+
* Revoke a token (Sign Out)
|
|
62
|
+
*/
|
|
63
|
+
revokeToken(token: string): Promise<void>;
|
|
64
|
+
/**
|
|
65
|
+
* Get user info using access token
|
|
66
|
+
*/
|
|
67
|
+
getUserInfo(accessToken: string, userInfoUrl?: string): Promise<Record<string, unknown>>;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export { AuthAdapter, RequestLike, SolvapayAuthAdapter, type SolvapayAuthAdapterConfig, SolvapayOAuthClient, type SolvapayOAuthConfig, type TokenResponse };
|