@githat/nextjs 0.2.1 → 0.2.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/README.md +580 -45
- package/dist/githat.css +41 -0
- package/dist/index.d.mts +213 -2
- package/dist/index.d.ts +213 -2
- package/dist/index.js +778 -99
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +772 -98
- package/dist/index.mjs.map +1 -1
- package/dist/middleware.d.mts +73 -4
- package/dist/middleware.d.ts +73 -4
- package/dist/middleware.js +96 -12
- package/dist/middleware.js.map +1 -1
- package/dist/middleware.mjs +85 -13
- package/dist/middleware.mjs.map +1 -1
- package/dist/proxy.d.mts +78 -0
- package/dist/proxy.d.ts +78 -0
- package/dist/proxy.js +138 -0
- package/dist/proxy.js.map +1 -0
- package/dist/proxy.mjs +101 -0
- package/dist/proxy.mjs.map +1 -0
- package/dist/server.d.mts +164 -0
- package/dist/server.d.ts +164 -0
- package/dist/server.js +203 -0
- package/dist/server.js.map +1 -0
- package/dist/server.mjs +163 -0
- package/dist/server.mjs.map +1 -0
- package/package.json +22 -2
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@ GitHat is your backend. This SDK connects your app to `api.githat.io` with pre-b
|
|
|
11
11
|
[](https://www.typescriptlang.org/)
|
|
12
12
|
[](./LICENSE)
|
|
13
13
|
|
|
14
|
-
[Quick Start](#quick-start) · [Components](#components) · [Hooks](#hooks) · [Middleware](#middleware) · [Docs](https://githat.io/docs)
|
|
14
|
+
[Quick Start](#quick-start) · [Components](#components) · [Hooks](#hooks) · [Middleware](#middleware) · [Server-Side](#server-side-authentication) · [Docs](https://githat.io/docs)
|
|
15
15
|
|
|
16
16
|
</div>
|
|
17
17
|
|
|
@@ -19,9 +19,11 @@ GitHat is your backend. This SDK connects your app to `api.githat.io` with pre-b
|
|
|
19
19
|
|
|
20
20
|
## Features
|
|
21
21
|
|
|
22
|
-
- **Pre-built UI Components** — Sign-in, sign-up,
|
|
23
|
-
- **React Hooks** — `useAuth()` and `
|
|
22
|
+
- **Pre-built UI Components** — Sign-in, sign-up, password reset, email verification — all themed and ready to go
|
|
23
|
+
- **React Hooks** — `useAuth()`, `useGitHat()`, and `useData()` for full control over auth state and API calls
|
|
24
24
|
- **Next.js Middleware** — Protect routes at the edge with a single line of config
|
|
25
|
+
- **Server-Side Auth** — Verify tokens, wrap API routes, and inject auth headers
|
|
26
|
+
- **Customer Data API** — Store and query app data in GitHat's managed DynamoDB
|
|
25
27
|
- **Multi-Tenant** — Organizations, team invitations, and role-based access — managed by GitHat's backend
|
|
26
28
|
- **MCP & Agent Verification** — Verify MCP servers and AI agents on-chain
|
|
27
29
|
- **Dark Theme Included** — Import `@githat/nextjs/styles` for a polished dark UI out of the box
|
|
@@ -99,6 +101,10 @@ export default function SignUpPage() {
|
|
|
99
101
|
|
|
100
102
|
### 5. Protect Routes with Middleware
|
|
101
103
|
|
|
104
|
+
<!-- tabs:start -->
|
|
105
|
+
|
|
106
|
+
#### **Next.js 14-15 (middleware.ts)**
|
|
107
|
+
|
|
102
108
|
```ts
|
|
103
109
|
// middleware.ts (project root)
|
|
104
110
|
import { authMiddleware } from '@githat/nextjs/middleware';
|
|
@@ -112,6 +118,23 @@ export const config = {
|
|
|
112
118
|
};
|
|
113
119
|
```
|
|
114
120
|
|
|
121
|
+
#### **Next.js 16+ (proxy.ts)**
|
|
122
|
+
|
|
123
|
+
```ts
|
|
124
|
+
// proxy.ts (project root)
|
|
125
|
+
import { authProxy } from '@githat/nextjs/proxy';
|
|
126
|
+
|
|
127
|
+
export const proxy = authProxy({
|
|
128
|
+
publicRoutes: ['/', '/sign-in', '/sign-up', '/forgot-password'],
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
export const config = {
|
|
132
|
+
matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],
|
|
133
|
+
};
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
<!-- tabs:end -->
|
|
137
|
+
|
|
115
138
|
### 6. Use Auth State Anywhere
|
|
116
139
|
|
|
117
140
|
```tsx
|
|
@@ -142,32 +165,91 @@ That's it. You now have a fully authenticated app with sign-in, sign-up, route p
|
|
|
142
165
|
|
|
143
166
|
### Provider
|
|
144
167
|
|
|
145
|
-
| Component | Description | Key Props
|
|
146
|
-
|
|
168
|
+
| Component | Description | Key Props |
|
|
169
|
+
|------------------|------------------------------------------|-----------------------------------------------------------|
|
|
147
170
|
| `GitHatProvider` | Wraps your app and provides auth context | `config` (required) — see [Configuration](#configuration) |
|
|
148
171
|
|
|
149
172
|
### Authentication Forms
|
|
150
173
|
|
|
151
|
-
| Component
|
|
152
|
-
|
|
153
|
-
| `SignInForm`
|
|
154
|
-
| `SignUpForm`
|
|
174
|
+
| Component | Description | Props |
|
|
175
|
+
|----------------------|--------------------------------------|-----------------------------------------------------|
|
|
176
|
+
| `SignInForm` | Email + password sign-in form | `onSuccess?`, `signUpUrl?`, `forgotPasswordUrl?` |
|
|
177
|
+
| `SignUpForm` | Name + email + password sign-up form | `onSuccess?`, `signInUrl?` |
|
|
178
|
+
| `ForgotPasswordForm` | Request password reset email | `onSuccess?`, `onError?`, `signInUrl?` |
|
|
179
|
+
| `ResetPasswordForm` | Reset password with token | `token`, `onSuccess?`, `onError?`, `signInUrl?`, `minPasswordLength?` |
|
|
180
|
+
| `VerifyEmailStatus` | Auto-verify email from URL token | `token`, `onSuccess?`, `onError?`, `signInUrl?`, `redirectDelay?` |
|
|
181
|
+
| `ChangePasswordForm` | Change password (authenticated) | `onSuccess?`, `onError?`, `minPasswordLength?` |
|
|
155
182
|
|
|
156
183
|
### Navigation
|
|
157
184
|
|
|
158
|
-
| Component | Description
|
|
159
|
-
|
|
160
|
-
| `SignInButton` | Link/button to your sign-in page
|
|
161
|
-
| `SignUpButton` | Link/button to your sign-up page
|
|
162
|
-
| `UserButton` | Avatar dropdown with user info and sign-out
|
|
163
|
-
| `OrgSwitcher` | Dropdown to switch between organizations
|
|
185
|
+
| Component | Description | Props |
|
|
186
|
+
|----------------|---------------------------------------------|------------------------------------|
|
|
187
|
+
| `SignInButton` | Link/button to your sign-in page | `className?`, `children?`, `href?` |
|
|
188
|
+
| `SignUpButton` | Link/button to your sign-up page | `className?`, `children?`, `href?` |
|
|
189
|
+
| `UserButton` | Avatar dropdown with user info and sign-out | — (uses context) |
|
|
190
|
+
| `OrgSwitcher` | Dropdown to switch between organizations | — (uses context) |
|
|
164
191
|
|
|
165
192
|
### Protection & Verification
|
|
166
193
|
|
|
167
|
-
| Component | Description | Props
|
|
168
|
-
|
|
169
|
-
| `ProtectedRoute` | Redirects unauthenticated users to sign-in | `children`, `fallback?`
|
|
170
|
-
| `VerifiedBadge` | Displays MCP/agent verification status | `type: 'mcp' \| 'agent'`, `identifier`, `label?`
|
|
194
|
+
| Component | Description | Props |
|
|
195
|
+
|------------------|--------------------------------------------|--------------------------------------------------|
|
|
196
|
+
| `ProtectedRoute` | Redirects unauthenticated users to sign-in | `children`, `fallback?` |
|
|
197
|
+
| `VerifiedBadge` | Displays MCP/agent verification status | `type: 'mcp' \| 'agent'`, `identifier`, `label?` |
|
|
198
|
+
|
|
199
|
+
### Example: Password Reset Flow
|
|
200
|
+
|
|
201
|
+
```tsx
|
|
202
|
+
// app/forgot-password/page.tsx
|
|
203
|
+
import { ForgotPasswordForm } from '@githat/nextjs';
|
|
204
|
+
|
|
205
|
+
export default function ForgotPasswordPage() {
|
|
206
|
+
return (
|
|
207
|
+
<div style={{ maxWidth: 400, margin: '80px auto' }}>
|
|
208
|
+
<ForgotPasswordForm signInUrl="/sign-in" />
|
|
209
|
+
</div>
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
```tsx
|
|
215
|
+
// app/reset-password/page.tsx
|
|
216
|
+
'use client';
|
|
217
|
+
|
|
218
|
+
import { ResetPasswordForm } from '@githat/nextjs';
|
|
219
|
+
import { useSearchParams } from 'next/navigation';
|
|
220
|
+
|
|
221
|
+
export default function ResetPasswordPage() {
|
|
222
|
+
const searchParams = useSearchParams();
|
|
223
|
+
const token = searchParams.get('token') || '';
|
|
224
|
+
|
|
225
|
+
return (
|
|
226
|
+
<div style={{ maxWidth: 400, margin: '80px auto' }}>
|
|
227
|
+
<ResetPasswordForm token={token} signInUrl="/sign-in" />
|
|
228
|
+
</div>
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
### Example: Email Verification
|
|
234
|
+
|
|
235
|
+
```tsx
|
|
236
|
+
// app/verify-email/page.tsx
|
|
237
|
+
'use client';
|
|
238
|
+
|
|
239
|
+
import { VerifyEmailStatus } from '@githat/nextjs';
|
|
240
|
+
import { useSearchParams } from 'next/navigation';
|
|
241
|
+
|
|
242
|
+
export default function VerifyEmailPage() {
|
|
243
|
+
const searchParams = useSearchParams();
|
|
244
|
+
const token = searchParams.get('token') || '';
|
|
245
|
+
|
|
246
|
+
return (
|
|
247
|
+
<div style={{ maxWidth: 400, margin: '80px auto' }}>
|
|
248
|
+
<VerifyEmailStatus token={token} signInUrl="/sign-in" />
|
|
249
|
+
</div>
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
```
|
|
171
253
|
|
|
172
254
|
### Example: Protected Dashboard
|
|
173
255
|
|
|
@@ -256,14 +338,30 @@ export function CustomLogin() {
|
|
|
256
338
|
|
|
257
339
|
### `useGitHat()`
|
|
258
340
|
|
|
259
|
-
Access the GitHat API client for authenticated requests, org management, and
|
|
341
|
+
Access the GitHat API client for authenticated requests, org management, password reset, email verification, and more.
|
|
260
342
|
|
|
261
343
|
```tsx
|
|
262
344
|
const {
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
345
|
+
// API Client
|
|
346
|
+
fetch, // <T>(path: string, init?: RequestInit) => Promise<T>
|
|
347
|
+
getUserOrgs, // () => Promise<{ orgs: GitHatOrg[] }>
|
|
348
|
+
|
|
349
|
+
// MCP & Agent Verification
|
|
350
|
+
verifyMCP, // (domain: string) => Promise<{ verified: boolean }>
|
|
351
|
+
verifyAgent, // (wallet: string) => Promise<{ verified: boolean }>
|
|
352
|
+
|
|
353
|
+
// Organization Metadata
|
|
354
|
+
getOrgMetadata, // () => Promise<OrgMetadata>
|
|
355
|
+
updateOrgMetadata, // (updates: OrgMetadata) => Promise<OrgMetadata>
|
|
356
|
+
|
|
357
|
+
// Password Management
|
|
358
|
+
forgotPassword, // (email: string) => Promise<{ success: boolean }>
|
|
359
|
+
resetPassword, // (token: string, newPassword: string) => Promise<{ success: boolean }>
|
|
360
|
+
changePassword, // (currentPassword: string, newPassword: string) => Promise<{ success: boolean }>
|
|
361
|
+
|
|
362
|
+
// Email Verification
|
|
363
|
+
verifyEmail, // (token: string) => Promise<{ success: boolean }>
|
|
364
|
+
resendVerificationEmail, // (email: string) => Promise<{ success: boolean }>
|
|
267
365
|
} = useGitHat();
|
|
268
366
|
```
|
|
269
367
|
|
|
@@ -303,6 +401,143 @@ const { fetch } = useGitHat();
|
|
|
303
401
|
const apps = await fetch<{ apps: App[] }>('/user/apps');
|
|
304
402
|
```
|
|
305
403
|
|
|
404
|
+
#### Example: Organization metadata
|
|
405
|
+
|
|
406
|
+
```tsx
|
|
407
|
+
const { getOrgMetadata, updateOrgMetadata } = useGitHat();
|
|
408
|
+
|
|
409
|
+
// Read metadata
|
|
410
|
+
const meta = await getOrgMetadata();
|
|
411
|
+
console.log(meta.stripeAccountId);
|
|
412
|
+
|
|
413
|
+
// Update metadata (requires admin or owner role)
|
|
414
|
+
await updateOrgMetadata({ stripeAccountId: 'acct_xxx', features: ['pos'] });
|
|
415
|
+
|
|
416
|
+
// Delete a key by setting it to null
|
|
417
|
+
await updateOrgMetadata({ oldKey: null });
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
#### Example: Password reset flow (programmatic)
|
|
421
|
+
|
|
422
|
+
```tsx
|
|
423
|
+
const { forgotPassword, resetPassword, changePassword } = useGitHat();
|
|
424
|
+
|
|
425
|
+
// Request reset email
|
|
426
|
+
await forgotPassword('user@example.com');
|
|
427
|
+
|
|
428
|
+
// Reset with token (from email link)
|
|
429
|
+
await resetPassword(token, 'NewSecurePassword123!');
|
|
430
|
+
|
|
431
|
+
// Change password (authenticated user)
|
|
432
|
+
await changePassword('currentPassword', 'newSecurePassword123!');
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
#### Example: Email verification (programmatic)
|
|
436
|
+
|
|
437
|
+
```tsx
|
|
438
|
+
const { verifyEmail, resendVerificationEmail } = useGitHat();
|
|
439
|
+
|
|
440
|
+
// Verify with token from email link
|
|
441
|
+
await verifyEmail(token);
|
|
442
|
+
|
|
443
|
+
// Resend verification email
|
|
444
|
+
await resendVerificationEmail('user@example.com');
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
### `useData()`
|
|
448
|
+
|
|
449
|
+
Access GitHat's Customer Data API for storing app data in managed DynamoDB. Must be used within a `<GitHatProvider>`.
|
|
450
|
+
|
|
451
|
+
```tsx
|
|
452
|
+
const {
|
|
453
|
+
put, // <T>(collection: string, data: T) => Promise<PutResult<T>>
|
|
454
|
+
get, // <T>(collection: string, id: string) => Promise<T | null>
|
|
455
|
+
query, // <T>(collection: string, options?: QueryOptions) => Promise<QueryResult<T>>
|
|
456
|
+
remove, // (collection: string, id: string) => Promise<DeleteResult>
|
|
457
|
+
batch, // (collection: string, operations: BatchOperation[]) => Promise<BatchResult>
|
|
458
|
+
} = useData();
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
#### Example: CRUD operations
|
|
462
|
+
|
|
463
|
+
```tsx
|
|
464
|
+
'use client';
|
|
465
|
+
|
|
466
|
+
import { useData } from '@githat/nextjs';
|
|
467
|
+
|
|
468
|
+
interface Order {
|
|
469
|
+
id: string;
|
|
470
|
+
amount: number;
|
|
471
|
+
status: 'pending' | 'completed' | 'cancelled';
|
|
472
|
+
createdAt: string;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
export function OrderManager() {
|
|
476
|
+
const { put, get, query, remove, batch } = useData();
|
|
477
|
+
|
|
478
|
+
// Create or update an item
|
|
479
|
+
const createOrder = async () => {
|
|
480
|
+
const result = await put<Order>('orders', {
|
|
481
|
+
id: 'order_123',
|
|
482
|
+
amount: 99.99,
|
|
483
|
+
status: 'pending',
|
|
484
|
+
createdAt: new Date().toISOString(),
|
|
485
|
+
});
|
|
486
|
+
console.log(result.created ? 'Created' : 'Updated');
|
|
487
|
+
};
|
|
488
|
+
|
|
489
|
+
// Get a single item
|
|
490
|
+
const getOrder = async () => {
|
|
491
|
+
const order = await get<Order>('orders', 'order_123');
|
|
492
|
+
if (order) {
|
|
493
|
+
console.log(`Order: $${order.amount}`);
|
|
494
|
+
}
|
|
495
|
+
};
|
|
496
|
+
|
|
497
|
+
// Query with filters and pagination
|
|
498
|
+
const getPendingOrders = async () => {
|
|
499
|
+
const { items, nextCursor } = await query<Order>('orders', {
|
|
500
|
+
filter: { status: 'pending' },
|
|
501
|
+
limit: 20,
|
|
502
|
+
});
|
|
503
|
+
console.log(`Found ${items.length} pending orders`);
|
|
504
|
+
};
|
|
505
|
+
|
|
506
|
+
// Delete an item
|
|
507
|
+
const deleteOrder = async () => {
|
|
508
|
+
await remove('orders', 'order_123');
|
|
509
|
+
};
|
|
510
|
+
|
|
511
|
+
// Batch operations (max 100 per request)
|
|
512
|
+
const bulkUpdate = async () => {
|
|
513
|
+
await batch('orders', [
|
|
514
|
+
{ type: 'put', id: 'order_1', data: { amount: 50, status: 'completed' } },
|
|
515
|
+
{ type: 'put', id: 'order_2', data: { amount: 75, status: 'pending' } },
|
|
516
|
+
{ type: 'delete', id: 'order_3' },
|
|
517
|
+
]);
|
|
518
|
+
};
|
|
519
|
+
|
|
520
|
+
return (
|
|
521
|
+
<div>
|
|
522
|
+
<button onClick={createOrder}>Create Order</button>
|
|
523
|
+
<button onClick={getOrder}>Get Order</button>
|
|
524
|
+
<button onClick={getPendingOrders}>List Pending</button>
|
|
525
|
+
<button onClick={deleteOrder}>Delete Order</button>
|
|
526
|
+
<button onClick={bulkUpdate}>Bulk Update</button>
|
|
527
|
+
</div>
|
|
528
|
+
);
|
|
529
|
+
}
|
|
530
|
+
```
|
|
531
|
+
|
|
532
|
+
#### Data API Tier Limits
|
|
533
|
+
|
|
534
|
+
| Tier | Max Items |
|
|
535
|
+
|------------|-----------|
|
|
536
|
+
| Free | 1,000 |
|
|
537
|
+
| Basic | 10,000 |
|
|
538
|
+
| Pro | 100,000 |
|
|
539
|
+
| Enterprise | Unlimited |
|
|
540
|
+
|
|
306
541
|
---
|
|
307
542
|
|
|
308
543
|
## Middleware
|
|
@@ -310,13 +545,13 @@ const apps = await fetch<{ apps: App[] }>('/user/apps');
|
|
|
310
545
|
The `authMiddleware` function protects your Next.js routes at the edge. Unauthenticated users are redirected to your sign-in page with a `redirect_url` query parameter for post-login redirect.
|
|
311
546
|
|
|
312
547
|
```ts
|
|
313
|
-
// middleware.ts
|
|
548
|
+
// middleware.ts (Next.js 14-15)
|
|
314
549
|
import { authMiddleware } from '@githat/nextjs/middleware';
|
|
315
550
|
|
|
316
551
|
export default authMiddleware({
|
|
317
552
|
publicRoutes: ['/', '/sign-in', '/sign-up', '/pricing'],
|
|
318
|
-
signInUrl: '/sign-in',
|
|
319
|
-
|
|
553
|
+
signInUrl: '/sign-in',
|
|
554
|
+
injectHeaders: true, // Optional: inject x-githat-* headers
|
|
320
555
|
});
|
|
321
556
|
|
|
322
557
|
export const config = {
|
|
@@ -326,13 +561,193 @@ export const config = {
|
|
|
326
561
|
|
|
327
562
|
### Middleware Options
|
|
328
563
|
|
|
329
|
-
| Option
|
|
330
|
-
|
|
331
|
-
| `publicRoutes`
|
|
332
|
-
| `signInUrl`
|
|
333
|
-
| `
|
|
564
|
+
| Option | Type | Default | Description |
|
|
565
|
+
|--------------------|------------|-------------------------|------------------------------------------------------|
|
|
566
|
+
| `publicRoutes` | `string[]` | `['/']` | Routes accessible without authentication |
|
|
567
|
+
| `signInUrl` | `string` | `'/sign-in'` | Where to redirect unauthenticated users |
|
|
568
|
+
| `tokenCookie` | `string` | `'githat_access'` | Cookie name for httpOnly access token |
|
|
569
|
+
| `legacyTokenCookie`| `string` | `'githat_access_token'` | Legacy cookie name (localStorage bridge) |
|
|
570
|
+
| `injectHeaders` | `boolean` | `false` | Inject `x-githat-*` headers into requests |
|
|
571
|
+
| `secretKey` | `string` | — | Secret key for local JWT verification (recommended) |
|
|
572
|
+
|
|
573
|
+
### Header Injection
|
|
574
|
+
|
|
575
|
+
When `injectHeaders: true`, the middleware decodes the JWT and adds these headers to downstream requests:
|
|
576
|
+
|
|
577
|
+
| Header | Value |
|
|
578
|
+
|---------------------|----------------------------|
|
|
579
|
+
| `x-githat-user-id` | User's unique ID |
|
|
580
|
+
| `x-githat-email` | User's email address |
|
|
581
|
+
| `x-githat-org-id` | Current organization ID |
|
|
582
|
+
| `x-githat-org-slug` | Current organization slug |
|
|
583
|
+
| `x-githat-role` | User's role (owner/admin/member) |
|
|
584
|
+
|
|
585
|
+
This allows API routes to access user info without re-verifying the token.
|
|
334
586
|
|
|
335
|
-
>
|
|
587
|
+
> **⚠️ Important: API routes are NOT protected by the middleware/proxy.** Both `authMiddleware` and `authProxy` automatically skip all `/api` routes (`pathname.startsWith('/api')` → pass-through). This is by design — API routes should use `withAuth()` or `getAuth()` from `@githat/nextjs/server` to verify tokens. See [Server-Side Authentication](#server-side-authentication) below.
|
|
588
|
+
|
|
589
|
+
---
|
|
590
|
+
|
|
591
|
+
## Next.js 16+ Proxy
|
|
592
|
+
|
|
593
|
+
Next.js 16 renamed `middleware.ts` to `proxy.ts` and the export from `export default middleware` to `export const proxy`. Use `authProxy` for Next.js 16+:
|
|
594
|
+
|
|
595
|
+
```ts
|
|
596
|
+
// proxy.ts (Next.js 16+)
|
|
597
|
+
import { authProxy } from '@githat/nextjs/proxy';
|
|
598
|
+
|
|
599
|
+
export const proxy = authProxy({
|
|
600
|
+
publicRoutes: ['/', '/about', '/pricing', '/sign-in', '/sign-up'],
|
|
601
|
+
signInUrl: '/sign-in',
|
|
602
|
+
injectHeaders: true,
|
|
603
|
+
secretKey: process.env.GITHAT_SECRET_KEY, // Recommended
|
|
604
|
+
});
|
|
605
|
+
|
|
606
|
+
export const config = {
|
|
607
|
+
matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],
|
|
608
|
+
};
|
|
609
|
+
```
|
|
610
|
+
|
|
611
|
+
The `authProxy` function accepts the same options as `authMiddleware`.
|
|
612
|
+
|
|
613
|
+
> **⚠️ Important: API routes are NOT protected by `authProxy` or `authMiddleware`.** Both automatically skip all `/api` routes. You **must** use `withAuth()` or `getAuth()` from `@githat/nextjs/server` to protect your API route handlers. See below.
|
|
614
|
+
|
|
615
|
+
---
|
|
616
|
+
|
|
617
|
+
## Server-Side Authentication
|
|
618
|
+
|
|
619
|
+
The `@githat/nextjs/server` module provides utilities for server-side token verification in API routes and middleware.
|
|
620
|
+
|
|
621
|
+
### Import
|
|
622
|
+
|
|
623
|
+
```ts
|
|
624
|
+
import {
|
|
625
|
+
verifyToken,
|
|
626
|
+
getAuth,
|
|
627
|
+
withAuth,
|
|
628
|
+
getOrgMetadata,
|
|
629
|
+
updateOrgMetadata,
|
|
630
|
+
COOKIE_NAMES,
|
|
631
|
+
type AuthPayload,
|
|
632
|
+
type VerifyOptions,
|
|
633
|
+
} from '@githat/nextjs/server';
|
|
634
|
+
```
|
|
635
|
+
|
|
636
|
+
### `verifyToken(token, options?)`
|
|
637
|
+
|
|
638
|
+
Verify a JWT token and return the decoded auth payload.
|
|
639
|
+
|
|
640
|
+
```ts
|
|
641
|
+
// Local verification (fast, ~1ms) — recommended for production
|
|
642
|
+
const auth = await verifyToken(token, {
|
|
643
|
+
secretKey: process.env.GITHAT_SECRET_KEY,
|
|
644
|
+
});
|
|
645
|
+
|
|
646
|
+
// API-based verification (simpler setup, ~50-100ms)
|
|
647
|
+
const auth = await verifyToken(token);
|
|
648
|
+
```
|
|
649
|
+
|
|
650
|
+
Returns `AuthPayload`:
|
|
651
|
+
|
|
652
|
+
```ts
|
|
653
|
+
interface AuthPayload {
|
|
654
|
+
userId: string;
|
|
655
|
+
email: string;
|
|
656
|
+
orgId: string | null;
|
|
657
|
+
orgSlug: string | null;
|
|
658
|
+
role: 'owner' | 'admin' | 'member' | null;
|
|
659
|
+
tier: 'free' | 'basic' | 'pro' | 'enterprise' | null;
|
|
660
|
+
}
|
|
661
|
+
```
|
|
662
|
+
|
|
663
|
+
### `getAuth(request, options?)`
|
|
664
|
+
|
|
665
|
+
Extract and verify the auth token from a Next.js request. Checks cookies first, then Authorization header.
|
|
666
|
+
|
|
667
|
+
```ts
|
|
668
|
+
// app/api/orders/route.ts
|
|
669
|
+
import { getAuth } from '@githat/nextjs/server';
|
|
670
|
+
|
|
671
|
+
export async function GET(request: Request) {
|
|
672
|
+
const auth = await getAuth(request, {
|
|
673
|
+
secretKey: process.env.GITHAT_SECRET_KEY,
|
|
674
|
+
});
|
|
675
|
+
|
|
676
|
+
if (!auth) {
|
|
677
|
+
return Response.json({ error: 'Unauthorized' }, { status: 401 });
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
// auth.userId, auth.orgId, auth.role available
|
|
681
|
+
return Response.json({ userId: auth.userId });
|
|
682
|
+
}
|
|
683
|
+
```
|
|
684
|
+
|
|
685
|
+
### `withAuth(handler, options?)`
|
|
686
|
+
|
|
687
|
+
Wrap an API route handler with authentication. The handler only runs if the request has a valid token. **This is required for every protected API route** — the middleware/proxy does not protect `/api` routes.
|
|
688
|
+
|
|
689
|
+
```ts
|
|
690
|
+
// app/api/orders/route.ts
|
|
691
|
+
import { withAuth } from '@githat/nextjs/server';
|
|
692
|
+
|
|
693
|
+
export const GET = withAuth(
|
|
694
|
+
async (request, auth) => {
|
|
695
|
+
// auth is guaranteed to be valid here
|
|
696
|
+
const orders = await db.orders.findMany({
|
|
697
|
+
where: { orgId: auth.orgId },
|
|
698
|
+
});
|
|
699
|
+
return Response.json({ orders });
|
|
700
|
+
},
|
|
701
|
+
{ secretKey: process.env.GITHAT_SECRET_KEY }
|
|
702
|
+
);
|
|
703
|
+
```
|
|
704
|
+
|
|
705
|
+
#### Custom unauthorized response
|
|
706
|
+
|
|
707
|
+
```ts
|
|
708
|
+
export const GET = withAuth(
|
|
709
|
+
async (request, auth) => {
|
|
710
|
+
return Response.json({ userId: auth.userId });
|
|
711
|
+
},
|
|
712
|
+
{
|
|
713
|
+
secretKey: process.env.GITHAT_SECRET_KEY,
|
|
714
|
+
onUnauthorized: () => Response.redirect('/sign-in'),
|
|
715
|
+
}
|
|
716
|
+
);
|
|
717
|
+
```
|
|
718
|
+
|
|
719
|
+
### `getOrgMetadata(orgId, options)` / `updateOrgMetadata(orgId, metadata, options)`
|
|
720
|
+
|
|
721
|
+
Server-side org metadata operations.
|
|
722
|
+
|
|
723
|
+
```ts
|
|
724
|
+
import { getOrgMetadata, updateOrgMetadata } from '@githat/nextjs/server';
|
|
725
|
+
|
|
726
|
+
// Get metadata
|
|
727
|
+
const meta = await getOrgMetadata(orgId, {
|
|
728
|
+
token: accessToken,
|
|
729
|
+
apiUrl: 'https://api.githat.io',
|
|
730
|
+
});
|
|
731
|
+
console.log(meta.stripeAccountId);
|
|
732
|
+
|
|
733
|
+
// Update metadata
|
|
734
|
+
await updateOrgMetadata(
|
|
735
|
+
orgId,
|
|
736
|
+
{ stripeAccountId: 'acct_xxx' },
|
|
737
|
+
{ token: accessToken }
|
|
738
|
+
);
|
|
739
|
+
```
|
|
740
|
+
|
|
741
|
+
### `COOKIE_NAMES`
|
|
742
|
+
|
|
743
|
+
Constants for cookie names used by the SDK.
|
|
744
|
+
|
|
745
|
+
```ts
|
|
746
|
+
import { COOKIE_NAMES } from '@githat/nextjs/server';
|
|
747
|
+
|
|
748
|
+
// COOKIE_NAMES.accessToken = 'githat_access'
|
|
749
|
+
// COOKIE_NAMES.refreshToken = 'githat_refresh'
|
|
750
|
+
```
|
|
336
751
|
|
|
337
752
|
---
|
|
338
753
|
|
|
@@ -342,20 +757,36 @@ export const config = {
|
|
|
342
757
|
|
|
343
758
|
Pass this to `<GitHatProvider config={...}>`:
|
|
344
759
|
|
|
345
|
-
| Property
|
|
346
|
-
|
|
347
|
-
| `publishableKey`
|
|
348
|
-
| `apiUrl`
|
|
349
|
-
| `signInUrl`
|
|
350
|
-
| `signUpUrl`
|
|
351
|
-
| `afterSignInUrl`
|
|
352
|
-
| `afterSignOutUrl
|
|
760
|
+
| Property | Type | Required | Default | Description |
|
|
761
|
+
|------------------|--------------------------------|----------|---------------------------|--------------------------------------|
|
|
762
|
+
| `publishableKey` | `string` | Yes | — | Your GitHat publishable key |
|
|
763
|
+
| `apiUrl` | `string` | No | `'https://api.githat.io'` | API base URL |
|
|
764
|
+
| `signInUrl` | `string` | No | `'/sign-in'` | Sign-in page route |
|
|
765
|
+
| `signUpUrl` | `string` | No | `'/sign-up'` | Sign-up page route |
|
|
766
|
+
| `afterSignInUrl` | `string` | No | `'/'` | Redirect after sign-in |
|
|
767
|
+
| `afterSignOutUrl`| `string` | No | `'/'` | Redirect after sign-out |
|
|
768
|
+
| `tokenStorage` | `'localStorage' \| 'cookie'` | No | `'localStorage'` | Token storage mode (see below) |
|
|
769
|
+
|
|
770
|
+
### Token Storage Modes
|
|
771
|
+
|
|
772
|
+
| Mode | Description |
|
|
773
|
+
|----------------|--------------------------------------------------------------------------|
|
|
774
|
+
| `localStorage` | Default. Tokens stored in browser localStorage. Simple client-side setup. |
|
|
775
|
+
| `cookie` | Tokens stored in httpOnly cookies. More secure, XSS-resistant. Better for SSR. |
|
|
776
|
+
|
|
777
|
+
When using `cookie` mode:
|
|
778
|
+
|
|
779
|
+
- Login/refresh automatically set httpOnly cookies
|
|
780
|
+
- SDK reads auth state from cookies server-side
|
|
781
|
+
- Better security for apps with server-side rendering
|
|
782
|
+
- Required for `getAuth()` and `withAuth()` server utilities
|
|
353
783
|
|
|
354
784
|
### Environment Variables
|
|
355
785
|
|
|
356
786
|
```env
|
|
357
787
|
# .env.local
|
|
358
|
-
NEXT_PUBLIC_GITHAT_KEY=
|
|
788
|
+
NEXT_PUBLIC_GITHAT_KEY=pk_live_your_key_here
|
|
789
|
+
GITHAT_SECRET_KEY=sk_live_your_secret_key # For server-side verification
|
|
359
790
|
```
|
|
360
791
|
|
|
361
792
|
### React/Vite Setup
|
|
@@ -375,7 +806,7 @@ ReactDOM.createRoot(document.getElementById('root')!).render(
|
|
|
375
806
|
);
|
|
376
807
|
```
|
|
377
808
|
|
|
378
|
-
> **Note:** The middleware
|
|
809
|
+
> **Note:** The middleware/proxy exports are Next.js-specific. For Vite/CRA apps, use `<ProtectedRoute>` for client-side route protection.
|
|
379
810
|
|
|
380
811
|
---
|
|
381
812
|
|
|
@@ -387,7 +818,7 @@ Import the built-in dark theme:
|
|
|
387
818
|
import '@githat/nextjs/styles';
|
|
388
819
|
```
|
|
389
820
|
|
|
390
|
-
This provides styled defaults for all components (`SignInForm`, `SignUpForm`, `UserButton`, `OrgSwitcher`, etc.). All class names are prefixed with `githat-` to avoid conflicts.
|
|
821
|
+
This provides styled defaults for all components (`SignInForm`, `SignUpForm`, `ForgotPasswordForm`, `ResetPasswordForm`, `VerifyEmailStatus`, `ChangePasswordForm`, `UserButton`, `OrgSwitcher`, etc.). All class names are prefixed with `githat-` to avoid conflicts.
|
|
391
822
|
|
|
392
823
|
### Customization
|
|
393
824
|
|
|
@@ -417,6 +848,7 @@ Every export is fully typed. Import types directly:
|
|
|
417
848
|
|
|
418
849
|
```tsx
|
|
419
850
|
import type {
|
|
851
|
+
// Core types
|
|
420
852
|
GitHatUser,
|
|
421
853
|
GitHatOrg,
|
|
422
854
|
GitHatConfig,
|
|
@@ -425,6 +857,30 @@ import type {
|
|
|
425
857
|
SignUpData,
|
|
426
858
|
SignUpResult,
|
|
427
859
|
GitHatContextValue,
|
|
860
|
+
|
|
861
|
+
// Password & Email
|
|
862
|
+
PasswordResetResult,
|
|
863
|
+
EmailVerificationResult,
|
|
864
|
+
|
|
865
|
+
// Server-side
|
|
866
|
+
AuthPayload,
|
|
867
|
+
VerifyOptions,
|
|
868
|
+
OrgMetadata,
|
|
869
|
+
WithAuthOptions,
|
|
870
|
+
AuthenticatedHandler,
|
|
871
|
+
|
|
872
|
+
// Customer Data API
|
|
873
|
+
DataItem,
|
|
874
|
+
QueryOptions,
|
|
875
|
+
QueryResult,
|
|
876
|
+
PutResult,
|
|
877
|
+
DeleteResult,
|
|
878
|
+
BatchOperation,
|
|
879
|
+
BatchResult,
|
|
880
|
+
|
|
881
|
+
// Middleware/Proxy
|
|
882
|
+
AuthHandlerOptions,
|
|
883
|
+
AuthProxyOptions,
|
|
428
884
|
} from '@githat/nextjs';
|
|
429
885
|
```
|
|
430
886
|
|
|
@@ -447,6 +903,15 @@ interface GitHatOrg {
|
|
|
447
903
|
tier: string;
|
|
448
904
|
}
|
|
449
905
|
|
|
906
|
+
interface AuthPayload {
|
|
907
|
+
userId: string;
|
|
908
|
+
email: string;
|
|
909
|
+
orgId: string | null;
|
|
910
|
+
orgSlug: string | null;
|
|
911
|
+
role: 'owner' | 'admin' | 'member' | null;
|
|
912
|
+
tier: 'free' | 'basic' | 'pro' | 'enterprise' | null;
|
|
913
|
+
}
|
|
914
|
+
|
|
450
915
|
interface SignUpData {
|
|
451
916
|
email: string;
|
|
452
917
|
password: string;
|
|
@@ -470,7 +935,77 @@ Use the companion CLI to scaffold a complete Next.js or React app with GitHat au
|
|
|
470
935
|
npx create-githat-app my-app
|
|
471
936
|
```
|
|
472
937
|
|
|
473
|
-
This generates a full project connected to GitHat's hosted backend — sign-in, sign-up, dashboard, settings, and team management pages. No backend to deploy.
|
|
938
|
+
This generates a full project connected to GitHat's hosted backend — sign-in, sign-up, password reset, email verification, dashboard, settings, and team management pages. No backend to deploy.
|
|
939
|
+
|
|
940
|
+
---
|
|
941
|
+
|
|
942
|
+
## Known Limitations
|
|
943
|
+
|
|
944
|
+
These are things GitHat does **not** provide today. They are not blockers — each has a recommended workaround.
|
|
945
|
+
|
|
946
|
+
### No webhooks / event system
|
|
947
|
+
|
|
948
|
+
GitHat does not send server-side notifications when users register, verify email, or delete accounts. Use `onSuccess` callbacks on form components to call your own API endpoint instead:
|
|
949
|
+
|
|
950
|
+
```tsx
|
|
951
|
+
<SignUpForm
|
|
952
|
+
onSuccess={async (result) => {
|
|
953
|
+
// result: { requiresVerification: boolean, email: string }
|
|
954
|
+
// Call your API to sync user, send welcome email, etc.
|
|
955
|
+
await fetch('/api/on-signup', {
|
|
956
|
+
method: 'POST',
|
|
957
|
+
headers: { 'Content-Type': 'application/json' },
|
|
958
|
+
body: JSON.stringify({ email: result.email }),
|
|
959
|
+
});
|
|
960
|
+
}}
|
|
961
|
+
/>
|
|
962
|
+
|
|
963
|
+
<VerifyEmailStatus
|
|
964
|
+
token={token}
|
|
965
|
+
onSuccess={async () => {
|
|
966
|
+
// Email verified — trigger your post-verification logic
|
|
967
|
+
await fetch('/api/on-email-verified', { method: 'POST' });
|
|
968
|
+
}}
|
|
969
|
+
/>
|
|
970
|
+
```
|
|
971
|
+
|
|
972
|
+
### No user metadata API
|
|
973
|
+
|
|
974
|
+
Org metadata exists (`getOrgMetadata` / `updateOrgMetadata`), but there is no per-user equivalent. Use the [Customer Data API](https://githat.io/docs/data) or your own database:
|
|
975
|
+
|
|
976
|
+
```tsx
|
|
977
|
+
import { useData, useAuth } from '@githat/nextjs';
|
|
978
|
+
|
|
979
|
+
function useUserPreferences() {
|
|
980
|
+
const { user } = useAuth();
|
|
981
|
+
const { put, get } = useData();
|
|
982
|
+
|
|
983
|
+
const getPreferences = async () => {
|
|
984
|
+
if (!user) return null;
|
|
985
|
+
return get('user-preferences', user.id);
|
|
986
|
+
};
|
|
987
|
+
|
|
988
|
+
const updatePreferences = async (prefs: Partial<Preferences>) => {
|
|
989
|
+
if (!user) throw new Error('Not authenticated');
|
|
990
|
+
const current = await getPreferences() || { theme: 'dark', notifications: true };
|
|
991
|
+
return put('user-preferences', { id: user.id, ...current, ...prefs });
|
|
992
|
+
};
|
|
993
|
+
|
|
994
|
+
return { getPreferences, updatePreferences };
|
|
995
|
+
}
|
|
996
|
+
```
|
|
997
|
+
|
|
998
|
+
### No bulk user import
|
|
999
|
+
|
|
1000
|
+
There is no self-service API to import existing users from another auth provider. For beta or new apps, users re-register. For production migrations, contact us for enterprise import.
|
|
1001
|
+
|
|
1002
|
+
### API routes not protected by middleware
|
|
1003
|
+
|
|
1004
|
+
`authMiddleware` and `authProxy` skip `/api` routes by design. Use `withAuth()` or `getAuth()` per-route — see [Server-Side Authentication](#server-side-authentication).
|
|
1005
|
+
|
|
1006
|
+
### Migrating from Clerk?
|
|
1007
|
+
|
|
1008
|
+
See the [Clerk Migration Guide](https://githat.io/docs/clerk-migration) for a step-by-step walkthrough of switching from Clerk to GitHat with zero downtime.
|
|
474
1009
|
|
|
475
1010
|
---
|
|
476
1011
|
|