@githat/nextjs 0.3.0 → 0.3.1
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/dist/index.d.mts +48 -2
- package/dist/index.d.ts +48 -2
- package/dist/index.js +134 -68
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +135 -69
- package/dist/index.mjs.map +1 -1
- package/dist/middleware.d.mts +39 -2
- package/dist/middleware.d.ts +39 -2
- package/dist/middleware.js +82 -5
- package/dist/middleware.js.map +1 -1
- package/dist/middleware.mjs +72 -5
- package/dist/middleware.mjs.map +1 -1
- package/dist/server.d.mts +115 -0
- package/dist/server.d.ts +115 -0
- package/dist/server.js +186 -0
- package/dist/server.js.map +1 -0
- package/dist/server.mjs +147 -0
- package/dist/server.mjs.map +1 -0
- package/package.json +9 -1
package/dist/middleware.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/middleware/index.ts"],"sourcesContent":["import { NextResponse } from 'next/server';\nimport type { NextRequest } from 'next/server';\n\ninterface AuthMiddlewareOptions {\n publicRoutes?: string[];\n signInUrl?: string;\n
|
|
1
|
+
{"version":3,"sources":["../src/middleware/index.ts"],"sourcesContent":["import { NextResponse } from 'next/server';\nimport type { NextRequest } from 'next/server';\nimport * as jose from 'jose';\n\ninterface AuthMiddlewareOptions {\n /**\n * Routes that don't require authentication.\n * Supports exact paths ('/') and path prefixes ('/public/*').\n */\n publicRoutes?: string[];\n\n /**\n * URL to redirect to when authentication is required but not present.\n * @default '/sign-in'\n */\n signInUrl?: string;\n\n /**\n * Cookie name for the access token.\n * @default 'githat_access'\n */\n tokenCookie?: string;\n\n /**\n * Legacy localStorage token cookie name (for backward compatibility).\n * @default 'githat_access_token'\n */\n legacyTokenCookie?: string;\n\n /**\n * When true, decode the JWT and inject x-githat-* headers into the request.\n * This allows downstream API routes to access user/org info without re-verifying.\n *\n * Injected headers:\n * - x-githat-user-id: User's unique ID\n * - x-githat-email: User's email address\n * - x-githat-org-id: Current org ID (if any)\n * - x-githat-org-slug: Current org slug (if any)\n * - x-githat-role: User's role in the org (owner/admin/member)\n *\n * @default false\n */\n injectHeaders?: boolean;\n\n /**\n * Secret key for local JWT verification when injectHeaders is true.\n * If not provided, headers are populated from the decoded (unverified) token payload.\n * For production, always provide the secret key for full verification.\n */\n secretKey?: string;\n}\n\n/**\n * Decode a JWT without verifying the signature.\n * Used when secretKey is not provided but we still want header injection.\n */\nfunction decodeJwtPayload(token: string): Record<string, unknown> | null {\n try {\n const parts = token.split('.');\n if (parts.length !== 3) return null;\n const payload = JSON.parse(atob(parts[1]));\n return payload;\n } catch {\n return null;\n }\n}\n\n/**\n * Verify a JWT and return the payload.\n * Uses jose for proper signature verification.\n */\nasync function verifyJwt(\n token: string,\n secretKey: string\n): Promise<Record<string, unknown> | null> {\n try {\n const secret = new TextEncoder().encode(secretKey);\n const { payload } = await jose.jwtVerify(token, secret, {\n algorithms: ['HS256'],\n });\n return payload as Record<string, unknown>;\n } catch {\n return null;\n }\n}\n\nexport function authMiddleware(options: AuthMiddlewareOptions = {}) {\n const {\n publicRoutes = ['/'],\n signInUrl = '/sign-in',\n tokenCookie = 'githat_access',\n legacyTokenCookie = 'githat_access_token',\n injectHeaders = false,\n secretKey,\n } = options;\n\n return async function middleware(request: NextRequest) {\n const { pathname } = request.nextUrl;\n\n // Allow public routes (exact match or prefix with /*)\n const isPublic = publicRoutes.some((route) => {\n if (route.endsWith('/*')) {\n const prefix = route.slice(0, -1); // Remove the *\n return pathname === prefix.slice(0, -1) || pathname.startsWith(prefix);\n }\n return pathname === route;\n });\n\n if (isPublic) {\n return NextResponse.next();\n }\n\n // Allow static files, Next.js internals, and API routes\n if (\n pathname.startsWith('/_next') ||\n pathname.startsWith('/api') ||\n pathname.includes('.')\n ) {\n return NextResponse.next();\n }\n\n // Check for token in cookies (try new httpOnly cookie first, then legacy)\n let token = request.cookies.get(tokenCookie)?.value;\n if (!token) {\n token = request.cookies.get(legacyTokenCookie)?.value;\n }\n\n // No token — redirect to sign-in\n if (!token) {\n const signInUrlObj = new URL(signInUrl, request.url);\n signInUrlObj.searchParams.set('redirect_url', pathname);\n return NextResponse.redirect(signInUrlObj);\n }\n\n // If not injecting headers, just let the request through\n if (!injectHeaders) {\n return NextResponse.next();\n }\n\n // Decode or verify the token to inject headers\n let payload: Record<string, unknown> | null = null;\n\n if (secretKey) {\n // Full verification with secret key\n payload = await verifyJwt(token, secretKey);\n if (!payload) {\n // Token verification failed — redirect to sign-in\n const signInUrlObj = new URL(signInUrl, request.url);\n signInUrlObj.searchParams.set('redirect_url', pathname);\n return NextResponse.redirect(signInUrlObj);\n }\n } else {\n // Decode without verification (less secure, but works without secret)\n payload = decodeJwtPayload(token);\n }\n\n // Create a new response with injected headers\n const response = NextResponse.next();\n\n if (payload) {\n // Inject user/org info as headers\n if (payload.userId) {\n response.headers.set('x-githat-user-id', String(payload.userId));\n }\n if (payload.email) {\n response.headers.set('x-githat-email', String(payload.email));\n }\n if (payload.orgId) {\n response.headers.set('x-githat-org-id', String(payload.orgId));\n }\n if (payload.orgSlug) {\n response.headers.set('x-githat-org-slug', String(payload.orgSlug));\n }\n if (payload.orgRole) {\n response.headers.set('x-githat-role', String(payload.orgRole));\n }\n }\n\n return response;\n };\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @githat/nextjs/server
|
|
3
|
+
*
|
|
4
|
+
* Server-side utilities for token verification in Next.js API routes and middleware.
|
|
5
|
+
* This module runs on the server only — do not import in client components.
|
|
6
|
+
*/
|
|
7
|
+
interface AuthPayload {
|
|
8
|
+
userId: string;
|
|
9
|
+
email: string;
|
|
10
|
+
orgId: string | null;
|
|
11
|
+
orgSlug: string | null;
|
|
12
|
+
role: 'owner' | 'admin' | 'member' | null;
|
|
13
|
+
tier: 'free' | 'basic' | 'pro' | 'enterprise' | null;
|
|
14
|
+
}
|
|
15
|
+
interface VerifyOptions {
|
|
16
|
+
/**
|
|
17
|
+
* Secret key for local JWT verification. If provided, tokens are verified
|
|
18
|
+
* locally without making an API call (~1ms vs ~50-100ms).
|
|
19
|
+
* Must match the JWT_SECRET used by the GitHat backend.
|
|
20
|
+
*/
|
|
21
|
+
secretKey?: string;
|
|
22
|
+
/**
|
|
23
|
+
* API URL for remote token verification. Defaults to https://api.githat.io
|
|
24
|
+
*/
|
|
25
|
+
apiUrl?: string;
|
|
26
|
+
}
|
|
27
|
+
interface OrgMetadata {
|
|
28
|
+
[key: string]: unknown;
|
|
29
|
+
}
|
|
30
|
+
declare const COOKIE_NAMES: {
|
|
31
|
+
readonly accessToken: "githat_access";
|
|
32
|
+
readonly refreshToken: "githat_refresh";
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* Verify a JWT token and return the decoded auth payload.
|
|
36
|
+
*
|
|
37
|
+
* Supports two verification modes:
|
|
38
|
+
* 1. Local verification (recommended for performance): Pass `secretKey` option
|
|
39
|
+
* 2. API-based verification: No secret key needed, calls api.githat.io/auth/verify
|
|
40
|
+
*
|
|
41
|
+
* @example Local verification (fast, ~1ms)
|
|
42
|
+
* ```ts
|
|
43
|
+
* const auth = await verifyToken(token, {
|
|
44
|
+
* secretKey: process.env.GITHAT_SECRET_KEY
|
|
45
|
+
* });
|
|
46
|
+
* ```
|
|
47
|
+
*
|
|
48
|
+
* @example API-based verification (simpler setup)
|
|
49
|
+
* ```ts
|
|
50
|
+
* const auth = await verifyToken(token);
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
declare function verifyToken(token: string, options?: VerifyOptions): Promise<AuthPayload>;
|
|
54
|
+
/**
|
|
55
|
+
* Extract and verify the auth token from a Next.js request.
|
|
56
|
+
* Checks cookies first (for httpOnly cookie mode), then Authorization header.
|
|
57
|
+
*
|
|
58
|
+
* Returns null if no token is found (unauthenticated request).
|
|
59
|
+
* Throws if a token is found but is invalid/expired.
|
|
60
|
+
*
|
|
61
|
+
* @example In a Next.js API route
|
|
62
|
+
* ```ts
|
|
63
|
+
* import { getAuth } from '@githat/nextjs/server';
|
|
64
|
+
*
|
|
65
|
+
* export async function GET(request: Request) {
|
|
66
|
+
* const auth = await getAuth(request, {
|
|
67
|
+
* secretKey: process.env.GITHAT_SECRET_KEY
|
|
68
|
+
* });
|
|
69
|
+
*
|
|
70
|
+
* if (!auth) {
|
|
71
|
+
* return Response.json({ error: 'Unauthorized' }, { status: 401 });
|
|
72
|
+
* }
|
|
73
|
+
*
|
|
74
|
+
* return Response.json({ userId: auth.userId });
|
|
75
|
+
* }
|
|
76
|
+
* ```
|
|
77
|
+
*/
|
|
78
|
+
declare function getAuth(request: Request, options?: VerifyOptions): Promise<AuthPayload | null>;
|
|
79
|
+
/**
|
|
80
|
+
* Get organization metadata from the server.
|
|
81
|
+
*
|
|
82
|
+
* @example
|
|
83
|
+
* ```ts
|
|
84
|
+
* import { getOrgMetadata } from '@githat/nextjs/server';
|
|
85
|
+
*
|
|
86
|
+
* const meta = await getOrgMetadata(orgId, {
|
|
87
|
+
* token: accessToken,
|
|
88
|
+
* apiUrl: 'https://api.githat.io'
|
|
89
|
+
* });
|
|
90
|
+
* console.log(meta.stripeAccountId);
|
|
91
|
+
* ```
|
|
92
|
+
*/
|
|
93
|
+
declare function getOrgMetadata(orgId: string, options: {
|
|
94
|
+
token: string;
|
|
95
|
+
apiUrl?: string;
|
|
96
|
+
}): Promise<OrgMetadata>;
|
|
97
|
+
/**
|
|
98
|
+
* Update organization metadata from the server.
|
|
99
|
+
*
|
|
100
|
+
* @example
|
|
101
|
+
* ```ts
|
|
102
|
+
* import { updateOrgMetadata } from '@githat/nextjs/server';
|
|
103
|
+
*
|
|
104
|
+
* await updateOrgMetadata(orgId, { stripeAccountId: 'acct_xxx' }, {
|
|
105
|
+
* token: accessToken,
|
|
106
|
+
* apiUrl: 'https://api.githat.io'
|
|
107
|
+
* });
|
|
108
|
+
* ```
|
|
109
|
+
*/
|
|
110
|
+
declare function updateOrgMetadata(orgId: string, metadata: OrgMetadata, options: {
|
|
111
|
+
token: string;
|
|
112
|
+
apiUrl?: string;
|
|
113
|
+
}): Promise<OrgMetadata>;
|
|
114
|
+
|
|
115
|
+
export { type AuthPayload, COOKIE_NAMES, type OrgMetadata, type VerifyOptions, getAuth, getOrgMetadata, updateOrgMetadata, verifyToken };
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @githat/nextjs/server
|
|
3
|
+
*
|
|
4
|
+
* Server-side utilities for token verification in Next.js API routes and middleware.
|
|
5
|
+
* This module runs on the server only — do not import in client components.
|
|
6
|
+
*/
|
|
7
|
+
interface AuthPayload {
|
|
8
|
+
userId: string;
|
|
9
|
+
email: string;
|
|
10
|
+
orgId: string | null;
|
|
11
|
+
orgSlug: string | null;
|
|
12
|
+
role: 'owner' | 'admin' | 'member' | null;
|
|
13
|
+
tier: 'free' | 'basic' | 'pro' | 'enterprise' | null;
|
|
14
|
+
}
|
|
15
|
+
interface VerifyOptions {
|
|
16
|
+
/**
|
|
17
|
+
* Secret key for local JWT verification. If provided, tokens are verified
|
|
18
|
+
* locally without making an API call (~1ms vs ~50-100ms).
|
|
19
|
+
* Must match the JWT_SECRET used by the GitHat backend.
|
|
20
|
+
*/
|
|
21
|
+
secretKey?: string;
|
|
22
|
+
/**
|
|
23
|
+
* API URL for remote token verification. Defaults to https://api.githat.io
|
|
24
|
+
*/
|
|
25
|
+
apiUrl?: string;
|
|
26
|
+
}
|
|
27
|
+
interface OrgMetadata {
|
|
28
|
+
[key: string]: unknown;
|
|
29
|
+
}
|
|
30
|
+
declare const COOKIE_NAMES: {
|
|
31
|
+
readonly accessToken: "githat_access";
|
|
32
|
+
readonly refreshToken: "githat_refresh";
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* Verify a JWT token and return the decoded auth payload.
|
|
36
|
+
*
|
|
37
|
+
* Supports two verification modes:
|
|
38
|
+
* 1. Local verification (recommended for performance): Pass `secretKey` option
|
|
39
|
+
* 2. API-based verification: No secret key needed, calls api.githat.io/auth/verify
|
|
40
|
+
*
|
|
41
|
+
* @example Local verification (fast, ~1ms)
|
|
42
|
+
* ```ts
|
|
43
|
+
* const auth = await verifyToken(token, {
|
|
44
|
+
* secretKey: process.env.GITHAT_SECRET_KEY
|
|
45
|
+
* });
|
|
46
|
+
* ```
|
|
47
|
+
*
|
|
48
|
+
* @example API-based verification (simpler setup)
|
|
49
|
+
* ```ts
|
|
50
|
+
* const auth = await verifyToken(token);
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
declare function verifyToken(token: string, options?: VerifyOptions): Promise<AuthPayload>;
|
|
54
|
+
/**
|
|
55
|
+
* Extract and verify the auth token from a Next.js request.
|
|
56
|
+
* Checks cookies first (for httpOnly cookie mode), then Authorization header.
|
|
57
|
+
*
|
|
58
|
+
* Returns null if no token is found (unauthenticated request).
|
|
59
|
+
* Throws if a token is found but is invalid/expired.
|
|
60
|
+
*
|
|
61
|
+
* @example In a Next.js API route
|
|
62
|
+
* ```ts
|
|
63
|
+
* import { getAuth } from '@githat/nextjs/server';
|
|
64
|
+
*
|
|
65
|
+
* export async function GET(request: Request) {
|
|
66
|
+
* const auth = await getAuth(request, {
|
|
67
|
+
* secretKey: process.env.GITHAT_SECRET_KEY
|
|
68
|
+
* });
|
|
69
|
+
*
|
|
70
|
+
* if (!auth) {
|
|
71
|
+
* return Response.json({ error: 'Unauthorized' }, { status: 401 });
|
|
72
|
+
* }
|
|
73
|
+
*
|
|
74
|
+
* return Response.json({ userId: auth.userId });
|
|
75
|
+
* }
|
|
76
|
+
* ```
|
|
77
|
+
*/
|
|
78
|
+
declare function getAuth(request: Request, options?: VerifyOptions): Promise<AuthPayload | null>;
|
|
79
|
+
/**
|
|
80
|
+
* Get organization metadata from the server.
|
|
81
|
+
*
|
|
82
|
+
* @example
|
|
83
|
+
* ```ts
|
|
84
|
+
* import { getOrgMetadata } from '@githat/nextjs/server';
|
|
85
|
+
*
|
|
86
|
+
* const meta = await getOrgMetadata(orgId, {
|
|
87
|
+
* token: accessToken,
|
|
88
|
+
* apiUrl: 'https://api.githat.io'
|
|
89
|
+
* });
|
|
90
|
+
* console.log(meta.stripeAccountId);
|
|
91
|
+
* ```
|
|
92
|
+
*/
|
|
93
|
+
declare function getOrgMetadata(orgId: string, options: {
|
|
94
|
+
token: string;
|
|
95
|
+
apiUrl?: string;
|
|
96
|
+
}): Promise<OrgMetadata>;
|
|
97
|
+
/**
|
|
98
|
+
* Update organization metadata from the server.
|
|
99
|
+
*
|
|
100
|
+
* @example
|
|
101
|
+
* ```ts
|
|
102
|
+
* import { updateOrgMetadata } from '@githat/nextjs/server';
|
|
103
|
+
*
|
|
104
|
+
* await updateOrgMetadata(orgId, { stripeAccountId: 'acct_xxx' }, {
|
|
105
|
+
* token: accessToken,
|
|
106
|
+
* apiUrl: 'https://api.githat.io'
|
|
107
|
+
* });
|
|
108
|
+
* ```
|
|
109
|
+
*/
|
|
110
|
+
declare function updateOrgMetadata(orgId: string, metadata: OrgMetadata, options: {
|
|
111
|
+
token: string;
|
|
112
|
+
apiUrl?: string;
|
|
113
|
+
}): Promise<OrgMetadata>;
|
|
114
|
+
|
|
115
|
+
export { type AuthPayload, COOKIE_NAMES, type OrgMetadata, type VerifyOptions, getAuth, getOrgMetadata, updateOrgMetadata, verifyToken };
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/server.ts
|
|
31
|
+
var server_exports = {};
|
|
32
|
+
__export(server_exports, {
|
|
33
|
+
COOKIE_NAMES: () => COOKIE_NAMES,
|
|
34
|
+
getAuth: () => getAuth,
|
|
35
|
+
getOrgMetadata: () => getOrgMetadata,
|
|
36
|
+
updateOrgMetadata: () => updateOrgMetadata,
|
|
37
|
+
verifyToken: () => verifyToken
|
|
38
|
+
});
|
|
39
|
+
module.exports = __toCommonJS(server_exports);
|
|
40
|
+
var jose = __toESM(require("jose"));
|
|
41
|
+
|
|
42
|
+
// src/config.ts
|
|
43
|
+
var DEFAULT_API_URL = "https://api.githat.io";
|
|
44
|
+
|
|
45
|
+
// src/server.ts
|
|
46
|
+
var COOKIE_NAMES = {
|
|
47
|
+
accessToken: "githat_access",
|
|
48
|
+
refreshToken: "githat_refresh"
|
|
49
|
+
};
|
|
50
|
+
async function verifyToken(token, options = {}) {
|
|
51
|
+
const { secretKey, apiUrl = DEFAULT_API_URL } = options;
|
|
52
|
+
if (secretKey) {
|
|
53
|
+
return verifyTokenLocally(token, secretKey);
|
|
54
|
+
}
|
|
55
|
+
return verifyTokenViaAPI(token, apiUrl);
|
|
56
|
+
}
|
|
57
|
+
async function verifyTokenLocally(token, secretKey) {
|
|
58
|
+
try {
|
|
59
|
+
const secret = new TextEncoder().encode(secretKey);
|
|
60
|
+
const { payload } = await jose.jwtVerify(token, secret, {
|
|
61
|
+
algorithms: ["HS256"]
|
|
62
|
+
});
|
|
63
|
+
if (payload.type && payload.type !== "user") {
|
|
64
|
+
throw new Error("Invalid token type");
|
|
65
|
+
}
|
|
66
|
+
if (payload.tokenType !== "access") {
|
|
67
|
+
throw new Error("Invalid token type");
|
|
68
|
+
}
|
|
69
|
+
return {
|
|
70
|
+
userId: payload.userId,
|
|
71
|
+
email: payload.email,
|
|
72
|
+
orgId: payload.orgId || null,
|
|
73
|
+
orgSlug: payload.orgSlug || null,
|
|
74
|
+
role: payload.orgRole || null,
|
|
75
|
+
tier: null
|
|
76
|
+
// Tier requires DB lookup, not available in local verification
|
|
77
|
+
};
|
|
78
|
+
} catch (err) {
|
|
79
|
+
if (err instanceof jose.errors.JWTExpired) {
|
|
80
|
+
throw new Error("Token expired");
|
|
81
|
+
}
|
|
82
|
+
if (err instanceof jose.errors.JWTClaimValidationFailed) {
|
|
83
|
+
throw new Error("Invalid token claims");
|
|
84
|
+
}
|
|
85
|
+
if (err instanceof jose.errors.JWTInvalid) {
|
|
86
|
+
throw new Error("Invalid token");
|
|
87
|
+
}
|
|
88
|
+
if (err instanceof jose.errors.JWSSignatureVerificationFailed) {
|
|
89
|
+
throw new Error("Invalid token signature");
|
|
90
|
+
}
|
|
91
|
+
throw err;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
async function verifyTokenViaAPI(token, apiUrl) {
|
|
95
|
+
const response = await fetch(`${apiUrl}/auth/verify`, {
|
|
96
|
+
method: "GET",
|
|
97
|
+
headers: {
|
|
98
|
+
Authorization: `Bearer ${token}`,
|
|
99
|
+
"Content-Type": "application/json"
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
if (!response.ok) {
|
|
103
|
+
const data2 = await response.json().catch(() => ({}));
|
|
104
|
+
throw new Error(data2.error || "Token verification failed");
|
|
105
|
+
}
|
|
106
|
+
const data = await response.json();
|
|
107
|
+
return {
|
|
108
|
+
userId: data.userId,
|
|
109
|
+
email: data.email,
|
|
110
|
+
orgId: data.orgId,
|
|
111
|
+
orgSlug: data.orgSlug,
|
|
112
|
+
role: data.role,
|
|
113
|
+
tier: data.tier
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
async function getAuth(request, options = {}) {
|
|
117
|
+
const cookieHeader = request.headers.get("cookie");
|
|
118
|
+
let token = null;
|
|
119
|
+
if (cookieHeader) {
|
|
120
|
+
const cookies = parseCookies(cookieHeader);
|
|
121
|
+
token = cookies[COOKIE_NAMES.accessToken] || null;
|
|
122
|
+
}
|
|
123
|
+
if (!token) {
|
|
124
|
+
const authHeader = request.headers.get("authorization");
|
|
125
|
+
if (authHeader?.startsWith("Bearer ")) {
|
|
126
|
+
token = authHeader.slice(7);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
if (!token) {
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
return verifyToken(token, options);
|
|
133
|
+
}
|
|
134
|
+
function parseCookies(cookieHeader) {
|
|
135
|
+
const cookies = {};
|
|
136
|
+
const pairs = cookieHeader.split(";");
|
|
137
|
+
for (const pair of pairs) {
|
|
138
|
+
const [name, ...rest] = pair.trim().split("=");
|
|
139
|
+
if (name) {
|
|
140
|
+
cookies[name] = rest.join("=");
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return cookies;
|
|
144
|
+
}
|
|
145
|
+
async function getOrgMetadata(orgId, options) {
|
|
146
|
+
const { token, apiUrl = DEFAULT_API_URL } = options;
|
|
147
|
+
const response = await fetch(`${apiUrl}/orgs/${orgId}/metadata`, {
|
|
148
|
+
method: "GET",
|
|
149
|
+
headers: {
|
|
150
|
+
Authorization: `Bearer ${token}`,
|
|
151
|
+
"Content-Type": "application/json"
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
if (!response.ok) {
|
|
155
|
+
const data2 = await response.json().catch(() => ({}));
|
|
156
|
+
throw new Error(data2.error || "Failed to get org metadata");
|
|
157
|
+
}
|
|
158
|
+
const data = await response.json();
|
|
159
|
+
return data.metadata || {};
|
|
160
|
+
}
|
|
161
|
+
async function updateOrgMetadata(orgId, metadata, options) {
|
|
162
|
+
const { token, apiUrl = DEFAULT_API_URL } = options;
|
|
163
|
+
const response = await fetch(`${apiUrl}/orgs/${orgId}/metadata`, {
|
|
164
|
+
method: "PATCH",
|
|
165
|
+
headers: {
|
|
166
|
+
Authorization: `Bearer ${token}`,
|
|
167
|
+
"Content-Type": "application/json"
|
|
168
|
+
},
|
|
169
|
+
body: JSON.stringify(metadata)
|
|
170
|
+
});
|
|
171
|
+
if (!response.ok) {
|
|
172
|
+
const data2 = await response.json().catch(() => ({}));
|
|
173
|
+
throw new Error(data2.error || "Failed to update org metadata");
|
|
174
|
+
}
|
|
175
|
+
const data = await response.json();
|
|
176
|
+
return data.metadata || {};
|
|
177
|
+
}
|
|
178
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
179
|
+
0 && (module.exports = {
|
|
180
|
+
COOKIE_NAMES,
|
|
181
|
+
getAuth,
|
|
182
|
+
getOrgMetadata,
|
|
183
|
+
updateOrgMetadata,
|
|
184
|
+
verifyToken
|
|
185
|
+
});
|
|
186
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/server.ts","../src/config.ts"],"sourcesContent":["/**\n * @githat/nextjs/server\n *\n * Server-side utilities for token verification in Next.js API routes and middleware.\n * This module runs on the server only — do not import in client components.\n */\n\nimport * as jose from 'jose';\nimport { DEFAULT_API_URL } from './config';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface AuthPayload {\n userId: string;\n email: string;\n orgId: string | null;\n orgSlug: string | null;\n role: 'owner' | 'admin' | 'member' | null;\n tier: 'free' | 'basic' | 'pro' | 'enterprise' | null;\n}\n\nexport interface VerifyOptions {\n /**\n * Secret key for local JWT verification. If provided, tokens are verified\n * locally without making an API call (~1ms vs ~50-100ms).\n * Must match the JWT_SECRET used by the GitHat backend.\n */\n secretKey?: string;\n\n /**\n * API URL for remote token verification. Defaults to https://api.githat.io\n */\n apiUrl?: string;\n}\n\nexport interface OrgMetadata {\n [key: string]: unknown;\n}\n\n// ============================================================================\n// Cookie Names\n// ============================================================================\n\nexport const COOKIE_NAMES = {\n accessToken: 'githat_access',\n refreshToken: 'githat_refresh',\n} as const;\n\n// ============================================================================\n// Token Verification\n// ============================================================================\n\n/**\n * Verify a JWT token and return the decoded auth payload.\n *\n * Supports two verification modes:\n * 1. Local verification (recommended for performance): Pass `secretKey` option\n * 2. API-based verification: No secret key needed, calls api.githat.io/auth/verify\n *\n * @example Local verification (fast, ~1ms)\n * ```ts\n * const auth = await verifyToken(token, {\n * secretKey: process.env.GITHAT_SECRET_KEY\n * });\n * ```\n *\n * @example API-based verification (simpler setup)\n * ```ts\n * const auth = await verifyToken(token);\n * ```\n */\nexport async function verifyToken(\n token: string,\n options: VerifyOptions = {}\n): Promise<AuthPayload> {\n const { secretKey, apiUrl = DEFAULT_API_URL } = options;\n\n if (secretKey) {\n // Local verification using jose\n return verifyTokenLocally(token, secretKey);\n }\n\n // API-based verification\n return verifyTokenViaAPI(token, apiUrl);\n}\n\n/**\n * Verify token locally using the secret key (no network call)\n */\nasync function verifyTokenLocally(\n token: string,\n secretKey: string\n): Promise<AuthPayload> {\n try {\n const secret = new TextEncoder().encode(secretKey);\n const { payload } = await jose.jwtVerify(token, secret, {\n algorithms: ['HS256'],\n });\n\n // Validate this is a user access token\n if (payload.type && payload.type !== 'user') {\n throw new Error('Invalid token type');\n }\n if (payload.tokenType !== 'access') {\n throw new Error('Invalid token type');\n }\n\n return {\n userId: payload.userId as string,\n email: payload.email as string,\n orgId: (payload.orgId as string) || null,\n orgSlug: (payload.orgSlug as string) || null,\n role: (payload.orgRole as AuthPayload['role']) || null,\n tier: null, // Tier requires DB lookup, not available in local verification\n };\n } catch (err) {\n if (err instanceof jose.errors.JWTExpired) {\n throw new Error('Token expired');\n }\n if (err instanceof jose.errors.JWTClaimValidationFailed) {\n throw new Error('Invalid token claims');\n }\n if (err instanceof jose.errors.JWTInvalid) {\n throw new Error('Invalid token');\n }\n // JWSSignatureVerificationFailed is used for invalid signatures\n if (err instanceof jose.errors.JWSSignatureVerificationFailed) {\n throw new Error('Invalid token signature');\n }\n throw err;\n }\n}\n\n/**\n * Verify token via API call to api.githat.io/auth/verify\n */\nasync function verifyTokenViaAPI(\n token: string,\n apiUrl: string\n): Promise<AuthPayload> {\n const response = await fetch(`${apiUrl}/auth/verify`, {\n method: 'GET',\n headers: {\n Authorization: `Bearer ${token}`,\n 'Content-Type': 'application/json',\n },\n });\n\n if (!response.ok) {\n const data = await response.json().catch(() => ({}));\n throw new Error(data.error || 'Token verification failed');\n }\n\n const data = await response.json();\n return {\n userId: data.userId,\n email: data.email,\n orgId: data.orgId,\n orgSlug: data.orgSlug,\n role: data.role,\n tier: data.tier,\n };\n}\n\n// ============================================================================\n// Request Helpers\n// ============================================================================\n\n/**\n * Extract and verify the auth token from a Next.js request.\n * Checks cookies first (for httpOnly cookie mode), then Authorization header.\n *\n * Returns null if no token is found (unauthenticated request).\n * Throws if a token is found but is invalid/expired.\n *\n * @example In a Next.js API route\n * ```ts\n * import { getAuth } from '@githat/nextjs/server';\n *\n * export async function GET(request: Request) {\n * const auth = await getAuth(request, {\n * secretKey: process.env.GITHAT_SECRET_KEY\n * });\n *\n * if (!auth) {\n * return Response.json({ error: 'Unauthorized' }, { status: 401 });\n * }\n *\n * return Response.json({ userId: auth.userId });\n * }\n * ```\n */\nexport async function getAuth(\n request: Request,\n options: VerifyOptions = {}\n): Promise<AuthPayload | null> {\n // Try to get token from cookie first (httpOnly cookie mode)\n const cookieHeader = request.headers.get('cookie');\n let token: string | null = null;\n\n if (cookieHeader) {\n const cookies = parseCookies(cookieHeader);\n token = cookies[COOKIE_NAMES.accessToken] || null;\n }\n\n // Fallback to Authorization header\n if (!token) {\n const authHeader = request.headers.get('authorization');\n if (authHeader?.startsWith('Bearer ')) {\n token = authHeader.slice(7);\n }\n }\n\n // No token found — return null (unauthenticated)\n if (!token) {\n return null;\n }\n\n // Verify the token\n return verifyToken(token, options);\n}\n\n/**\n * Parse cookies from a cookie header string\n */\nfunction parseCookies(cookieHeader: string): Record<string, string> {\n const cookies: Record<string, string> = {};\n const pairs = cookieHeader.split(';');\n\n for (const pair of pairs) {\n const [name, ...rest] = pair.trim().split('=');\n if (name) {\n cookies[name] = rest.join('=');\n }\n }\n\n return cookies;\n}\n\n// ============================================================================\n// Org Metadata (Server-side)\n// ============================================================================\n\n/**\n * Get organization metadata from the server.\n *\n * @example\n * ```ts\n * import { getOrgMetadata } from '@githat/nextjs/server';\n *\n * const meta = await getOrgMetadata(orgId, {\n * token: accessToken,\n * apiUrl: 'https://api.githat.io'\n * });\n * console.log(meta.stripeAccountId);\n * ```\n */\nexport async function getOrgMetadata(\n orgId: string,\n options: { token: string; apiUrl?: string }\n): Promise<OrgMetadata> {\n const { token, apiUrl = DEFAULT_API_URL } = options;\n\n const response = await fetch(`${apiUrl}/orgs/${orgId}/metadata`, {\n method: 'GET',\n headers: {\n Authorization: `Bearer ${token}`,\n 'Content-Type': 'application/json',\n },\n });\n\n if (!response.ok) {\n const data = await response.json().catch(() => ({}));\n throw new Error(data.error || 'Failed to get org metadata');\n }\n\n const data = await response.json();\n return data.metadata || {};\n}\n\n/**\n * Update organization metadata from the server.\n *\n * @example\n * ```ts\n * import { updateOrgMetadata } from '@githat/nextjs/server';\n *\n * await updateOrgMetadata(orgId, { stripeAccountId: 'acct_xxx' }, {\n * token: accessToken,\n * apiUrl: 'https://api.githat.io'\n * });\n * ```\n */\nexport async function updateOrgMetadata(\n orgId: string,\n metadata: OrgMetadata,\n options: { token: string; apiUrl?: string }\n): Promise<OrgMetadata> {\n const { token, apiUrl = DEFAULT_API_URL } = options;\n\n const response = await fetch(`${apiUrl}/orgs/${orgId}/metadata`, {\n method: 'PATCH',\n headers: {\n Authorization: `Bearer ${token}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(metadata),\n });\n\n if (!response.ok) {\n const data = await response.json().catch(() => ({}));\n throw new Error(data.error || 'Failed to update org metadata');\n }\n\n const data = await response.json();\n return data.metadata || {};\n}\n","import type { GitHatConfig } from './types';\n\nexport const DEFAULT_API_URL = 'https://api.githat.io';\n\nexport const TOKEN_KEYS = {\n accessToken: 'githat_access_token',\n refreshToken: 'githat_refresh_token',\n user: 'githat_user',\n org: 'githat_org',\n} as const;\n\nexport const COOKIE_NAMES = {\n accessToken: 'githat_access',\n refreshToken: 'githat_refresh',\n} as const;\n\nexport function resolveConfig(config: GitHatConfig): Required<GitHatConfig> {\n return {\n publishableKey: config.publishableKey,\n apiUrl: config.apiUrl || DEFAULT_API_URL,\n signInUrl: config.signInUrl || '/sign-in',\n signUpUrl: config.signUpUrl || '/sign-up',\n afterSignInUrl: config.afterSignInUrl || '/dashboard',\n afterSignOutUrl: config.afterSignOutUrl || '/',\n tokenStorage: config.tokenStorage || 'localStorage',\n };\n}\n"],"mappings":"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","names":["data"]}
|
package/dist/server.mjs
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
// src/server.ts
|
|
2
|
+
import * as jose from "jose";
|
|
3
|
+
|
|
4
|
+
// src/config.ts
|
|
5
|
+
var DEFAULT_API_URL = "https://api.githat.io";
|
|
6
|
+
|
|
7
|
+
// src/server.ts
|
|
8
|
+
var COOKIE_NAMES = {
|
|
9
|
+
accessToken: "githat_access",
|
|
10
|
+
refreshToken: "githat_refresh"
|
|
11
|
+
};
|
|
12
|
+
async function verifyToken(token, options = {}) {
|
|
13
|
+
const { secretKey, apiUrl = DEFAULT_API_URL } = options;
|
|
14
|
+
if (secretKey) {
|
|
15
|
+
return verifyTokenLocally(token, secretKey);
|
|
16
|
+
}
|
|
17
|
+
return verifyTokenViaAPI(token, apiUrl);
|
|
18
|
+
}
|
|
19
|
+
async function verifyTokenLocally(token, secretKey) {
|
|
20
|
+
try {
|
|
21
|
+
const secret = new TextEncoder().encode(secretKey);
|
|
22
|
+
const { payload } = await jose.jwtVerify(token, secret, {
|
|
23
|
+
algorithms: ["HS256"]
|
|
24
|
+
});
|
|
25
|
+
if (payload.type && payload.type !== "user") {
|
|
26
|
+
throw new Error("Invalid token type");
|
|
27
|
+
}
|
|
28
|
+
if (payload.tokenType !== "access") {
|
|
29
|
+
throw new Error("Invalid token type");
|
|
30
|
+
}
|
|
31
|
+
return {
|
|
32
|
+
userId: payload.userId,
|
|
33
|
+
email: payload.email,
|
|
34
|
+
orgId: payload.orgId || null,
|
|
35
|
+
orgSlug: payload.orgSlug || null,
|
|
36
|
+
role: payload.orgRole || null,
|
|
37
|
+
tier: null
|
|
38
|
+
// Tier requires DB lookup, not available in local verification
|
|
39
|
+
};
|
|
40
|
+
} catch (err) {
|
|
41
|
+
if (err instanceof jose.errors.JWTExpired) {
|
|
42
|
+
throw new Error("Token expired");
|
|
43
|
+
}
|
|
44
|
+
if (err instanceof jose.errors.JWTClaimValidationFailed) {
|
|
45
|
+
throw new Error("Invalid token claims");
|
|
46
|
+
}
|
|
47
|
+
if (err instanceof jose.errors.JWTInvalid) {
|
|
48
|
+
throw new Error("Invalid token");
|
|
49
|
+
}
|
|
50
|
+
if (err instanceof jose.errors.JWSSignatureVerificationFailed) {
|
|
51
|
+
throw new Error("Invalid token signature");
|
|
52
|
+
}
|
|
53
|
+
throw err;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
async function verifyTokenViaAPI(token, apiUrl) {
|
|
57
|
+
const response = await fetch(`${apiUrl}/auth/verify`, {
|
|
58
|
+
method: "GET",
|
|
59
|
+
headers: {
|
|
60
|
+
Authorization: `Bearer ${token}`,
|
|
61
|
+
"Content-Type": "application/json"
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
if (!response.ok) {
|
|
65
|
+
const data2 = await response.json().catch(() => ({}));
|
|
66
|
+
throw new Error(data2.error || "Token verification failed");
|
|
67
|
+
}
|
|
68
|
+
const data = await response.json();
|
|
69
|
+
return {
|
|
70
|
+
userId: data.userId,
|
|
71
|
+
email: data.email,
|
|
72
|
+
orgId: data.orgId,
|
|
73
|
+
orgSlug: data.orgSlug,
|
|
74
|
+
role: data.role,
|
|
75
|
+
tier: data.tier
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
async function getAuth(request, options = {}) {
|
|
79
|
+
const cookieHeader = request.headers.get("cookie");
|
|
80
|
+
let token = null;
|
|
81
|
+
if (cookieHeader) {
|
|
82
|
+
const cookies = parseCookies(cookieHeader);
|
|
83
|
+
token = cookies[COOKIE_NAMES.accessToken] || null;
|
|
84
|
+
}
|
|
85
|
+
if (!token) {
|
|
86
|
+
const authHeader = request.headers.get("authorization");
|
|
87
|
+
if (authHeader?.startsWith("Bearer ")) {
|
|
88
|
+
token = authHeader.slice(7);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
if (!token) {
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
return verifyToken(token, options);
|
|
95
|
+
}
|
|
96
|
+
function parseCookies(cookieHeader) {
|
|
97
|
+
const cookies = {};
|
|
98
|
+
const pairs = cookieHeader.split(";");
|
|
99
|
+
for (const pair of pairs) {
|
|
100
|
+
const [name, ...rest] = pair.trim().split("=");
|
|
101
|
+
if (name) {
|
|
102
|
+
cookies[name] = rest.join("=");
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return cookies;
|
|
106
|
+
}
|
|
107
|
+
async function getOrgMetadata(orgId, options) {
|
|
108
|
+
const { token, apiUrl = DEFAULT_API_URL } = options;
|
|
109
|
+
const response = await fetch(`${apiUrl}/orgs/${orgId}/metadata`, {
|
|
110
|
+
method: "GET",
|
|
111
|
+
headers: {
|
|
112
|
+
Authorization: `Bearer ${token}`,
|
|
113
|
+
"Content-Type": "application/json"
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
if (!response.ok) {
|
|
117
|
+
const data2 = await response.json().catch(() => ({}));
|
|
118
|
+
throw new Error(data2.error || "Failed to get org metadata");
|
|
119
|
+
}
|
|
120
|
+
const data = await response.json();
|
|
121
|
+
return data.metadata || {};
|
|
122
|
+
}
|
|
123
|
+
async function updateOrgMetadata(orgId, metadata, options) {
|
|
124
|
+
const { token, apiUrl = DEFAULT_API_URL } = options;
|
|
125
|
+
const response = await fetch(`${apiUrl}/orgs/${orgId}/metadata`, {
|
|
126
|
+
method: "PATCH",
|
|
127
|
+
headers: {
|
|
128
|
+
Authorization: `Bearer ${token}`,
|
|
129
|
+
"Content-Type": "application/json"
|
|
130
|
+
},
|
|
131
|
+
body: JSON.stringify(metadata)
|
|
132
|
+
});
|
|
133
|
+
if (!response.ok) {
|
|
134
|
+
const data2 = await response.json().catch(() => ({}));
|
|
135
|
+
throw new Error(data2.error || "Failed to update org metadata");
|
|
136
|
+
}
|
|
137
|
+
const data = await response.json();
|
|
138
|
+
return data.metadata || {};
|
|
139
|
+
}
|
|
140
|
+
export {
|
|
141
|
+
COOKIE_NAMES,
|
|
142
|
+
getAuth,
|
|
143
|
+
getOrgMetadata,
|
|
144
|
+
updateOrgMetadata,
|
|
145
|
+
verifyToken
|
|
146
|
+
};
|
|
147
|
+
//# sourceMappingURL=server.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/server.ts","../src/config.ts"],"sourcesContent":["/**\n * @githat/nextjs/server\n *\n * Server-side utilities for token verification in Next.js API routes and middleware.\n * This module runs on the server only — do not import in client components.\n */\n\nimport * as jose from 'jose';\nimport { DEFAULT_API_URL } from './config';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface AuthPayload {\n userId: string;\n email: string;\n orgId: string | null;\n orgSlug: string | null;\n role: 'owner' | 'admin' | 'member' | null;\n tier: 'free' | 'basic' | 'pro' | 'enterprise' | null;\n}\n\nexport interface VerifyOptions {\n /**\n * Secret key for local JWT verification. If provided, tokens are verified\n * locally without making an API call (~1ms vs ~50-100ms).\n * Must match the JWT_SECRET used by the GitHat backend.\n */\n secretKey?: string;\n\n /**\n * API URL for remote token verification. Defaults to https://api.githat.io\n */\n apiUrl?: string;\n}\n\nexport interface OrgMetadata {\n [key: string]: unknown;\n}\n\n// ============================================================================\n// Cookie Names\n// ============================================================================\n\nexport const COOKIE_NAMES = {\n accessToken: 'githat_access',\n refreshToken: 'githat_refresh',\n} as const;\n\n// ============================================================================\n// Token Verification\n// ============================================================================\n\n/**\n * Verify a JWT token and return the decoded auth payload.\n *\n * Supports two verification modes:\n * 1. Local verification (recommended for performance): Pass `secretKey` option\n * 2. API-based verification: No secret key needed, calls api.githat.io/auth/verify\n *\n * @example Local verification (fast, ~1ms)\n * ```ts\n * const auth = await verifyToken(token, {\n * secretKey: process.env.GITHAT_SECRET_KEY\n * });\n * ```\n *\n * @example API-based verification (simpler setup)\n * ```ts\n * const auth = await verifyToken(token);\n * ```\n */\nexport async function verifyToken(\n token: string,\n options: VerifyOptions = {}\n): Promise<AuthPayload> {\n const { secretKey, apiUrl = DEFAULT_API_URL } = options;\n\n if (secretKey) {\n // Local verification using jose\n return verifyTokenLocally(token, secretKey);\n }\n\n // API-based verification\n return verifyTokenViaAPI(token, apiUrl);\n}\n\n/**\n * Verify token locally using the secret key (no network call)\n */\nasync function verifyTokenLocally(\n token: string,\n secretKey: string\n): Promise<AuthPayload> {\n try {\n const secret = new TextEncoder().encode(secretKey);\n const { payload } = await jose.jwtVerify(token, secret, {\n algorithms: ['HS256'],\n });\n\n // Validate this is a user access token\n if (payload.type && payload.type !== 'user') {\n throw new Error('Invalid token type');\n }\n if (payload.tokenType !== 'access') {\n throw new Error('Invalid token type');\n }\n\n return {\n userId: payload.userId as string,\n email: payload.email as string,\n orgId: (payload.orgId as string) || null,\n orgSlug: (payload.orgSlug as string) || null,\n role: (payload.orgRole as AuthPayload['role']) || null,\n tier: null, // Tier requires DB lookup, not available in local verification\n };\n } catch (err) {\n if (err instanceof jose.errors.JWTExpired) {\n throw new Error('Token expired');\n }\n if (err instanceof jose.errors.JWTClaimValidationFailed) {\n throw new Error('Invalid token claims');\n }\n if (err instanceof jose.errors.JWTInvalid) {\n throw new Error('Invalid token');\n }\n // JWSSignatureVerificationFailed is used for invalid signatures\n if (err instanceof jose.errors.JWSSignatureVerificationFailed) {\n throw new Error('Invalid token signature');\n }\n throw err;\n }\n}\n\n/**\n * Verify token via API call to api.githat.io/auth/verify\n */\nasync function verifyTokenViaAPI(\n token: string,\n apiUrl: string\n): Promise<AuthPayload> {\n const response = await fetch(`${apiUrl}/auth/verify`, {\n method: 'GET',\n headers: {\n Authorization: `Bearer ${token}`,\n 'Content-Type': 'application/json',\n },\n });\n\n if (!response.ok) {\n const data = await response.json().catch(() => ({}));\n throw new Error(data.error || 'Token verification failed');\n }\n\n const data = await response.json();\n return {\n userId: data.userId,\n email: data.email,\n orgId: data.orgId,\n orgSlug: data.orgSlug,\n role: data.role,\n tier: data.tier,\n };\n}\n\n// ============================================================================\n// Request Helpers\n// ============================================================================\n\n/**\n * Extract and verify the auth token from a Next.js request.\n * Checks cookies first (for httpOnly cookie mode), then Authorization header.\n *\n * Returns null if no token is found (unauthenticated request).\n * Throws if a token is found but is invalid/expired.\n *\n * @example In a Next.js API route\n * ```ts\n * import { getAuth } from '@githat/nextjs/server';\n *\n * export async function GET(request: Request) {\n * const auth = await getAuth(request, {\n * secretKey: process.env.GITHAT_SECRET_KEY\n * });\n *\n * if (!auth) {\n * return Response.json({ error: 'Unauthorized' }, { status: 401 });\n * }\n *\n * return Response.json({ userId: auth.userId });\n * }\n * ```\n */\nexport async function getAuth(\n request: Request,\n options: VerifyOptions = {}\n): Promise<AuthPayload | null> {\n // Try to get token from cookie first (httpOnly cookie mode)\n const cookieHeader = request.headers.get('cookie');\n let token: string | null = null;\n\n if (cookieHeader) {\n const cookies = parseCookies(cookieHeader);\n token = cookies[COOKIE_NAMES.accessToken] || null;\n }\n\n // Fallback to Authorization header\n if (!token) {\n const authHeader = request.headers.get('authorization');\n if (authHeader?.startsWith('Bearer ')) {\n token = authHeader.slice(7);\n }\n }\n\n // No token found — return null (unauthenticated)\n if (!token) {\n return null;\n }\n\n // Verify the token\n return verifyToken(token, options);\n}\n\n/**\n * Parse cookies from a cookie header string\n */\nfunction parseCookies(cookieHeader: string): Record<string, string> {\n const cookies: Record<string, string> = {};\n const pairs = cookieHeader.split(';');\n\n for (const pair of pairs) {\n const [name, ...rest] = pair.trim().split('=');\n if (name) {\n cookies[name] = rest.join('=');\n }\n }\n\n return cookies;\n}\n\n// ============================================================================\n// Org Metadata (Server-side)\n// ============================================================================\n\n/**\n * Get organization metadata from the server.\n *\n * @example\n * ```ts\n * import { getOrgMetadata } from '@githat/nextjs/server';\n *\n * const meta = await getOrgMetadata(orgId, {\n * token: accessToken,\n * apiUrl: 'https://api.githat.io'\n * });\n * console.log(meta.stripeAccountId);\n * ```\n */\nexport async function getOrgMetadata(\n orgId: string,\n options: { token: string; apiUrl?: string }\n): Promise<OrgMetadata> {\n const { token, apiUrl = DEFAULT_API_URL } = options;\n\n const response = await fetch(`${apiUrl}/orgs/${orgId}/metadata`, {\n method: 'GET',\n headers: {\n Authorization: `Bearer ${token}`,\n 'Content-Type': 'application/json',\n },\n });\n\n if (!response.ok) {\n const data = await response.json().catch(() => ({}));\n throw new Error(data.error || 'Failed to get org metadata');\n }\n\n const data = await response.json();\n return data.metadata || {};\n}\n\n/**\n * Update organization metadata from the server.\n *\n * @example\n * ```ts\n * import { updateOrgMetadata } from '@githat/nextjs/server';\n *\n * await updateOrgMetadata(orgId, { stripeAccountId: 'acct_xxx' }, {\n * token: accessToken,\n * apiUrl: 'https://api.githat.io'\n * });\n * ```\n */\nexport async function updateOrgMetadata(\n orgId: string,\n metadata: OrgMetadata,\n options: { token: string; apiUrl?: string }\n): Promise<OrgMetadata> {\n const { token, apiUrl = DEFAULT_API_URL } = options;\n\n const response = await fetch(`${apiUrl}/orgs/${orgId}/metadata`, {\n method: 'PATCH',\n headers: {\n Authorization: `Bearer ${token}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(metadata),\n });\n\n if (!response.ok) {\n const data = await response.json().catch(() => ({}));\n throw new Error(data.error || 'Failed to update org metadata');\n }\n\n const data = await response.json();\n return data.metadata || {};\n}\n","import type { GitHatConfig } from './types';\n\nexport const DEFAULT_API_URL = 'https://api.githat.io';\n\nexport const TOKEN_KEYS = {\n accessToken: 'githat_access_token',\n refreshToken: 'githat_refresh_token',\n user: 'githat_user',\n org: 'githat_org',\n} as const;\n\nexport const COOKIE_NAMES = {\n accessToken: 'githat_access',\n refreshToken: 'githat_refresh',\n} as const;\n\nexport function resolveConfig(config: GitHatConfig): Required<GitHatConfig> {\n return {\n publishableKey: config.publishableKey,\n apiUrl: config.apiUrl || DEFAULT_API_URL,\n signInUrl: config.signInUrl || '/sign-in',\n signUpUrl: config.signUpUrl || '/sign-up',\n afterSignInUrl: config.afterSignInUrl || '/dashboard',\n afterSignOutUrl: config.afterSignOutUrl || '/',\n tokenStorage: config.tokenStorage || 'localStorage',\n };\n}\n"],"mappings":"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","names":["data"]}
|