@githat/nextjs 0.3.1 → 0.4.0

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.
@@ -1,6 +1,6 @@
1
1
  import { NextRequest, NextResponse } from 'next/server';
2
2
 
3
- interface AuthMiddlewareOptions {
3
+ interface AuthHandlerOptions {
4
4
  /**
5
5
  * Routes that don't require authentication.
6
6
  * Supports exact paths ('/') and path prefixes ('/public/*').
@@ -42,6 +42,38 @@ interface AuthMiddlewareOptions {
42
42
  */
43
43
  secretKey?: string;
44
44
  }
45
- declare function authMiddleware(options?: AuthMiddlewareOptions): (request: NextRequest) => Promise<NextResponse<unknown>>;
46
45
 
47
- export { authMiddleware };
46
+ /**
47
+ * Options for the authMiddleware function.
48
+ * @see AuthHandlerOptions for detailed property documentation.
49
+ */
50
+ interface AuthMiddlewareOptions extends AuthHandlerOptions {
51
+ }
52
+ /**
53
+ * Creates an auth middleware handler for Next.js 14/15.
54
+ *
55
+ * @deprecated For Next.js 16+, use `authProxy` from `@githat/nextjs/proxy` instead.
56
+ * Next.js 16 renamed middleware.ts to proxy.ts. This export continues to work
57
+ * for Next.js 14/15 applications.
58
+ *
59
+ * @example
60
+ * ```typescript
61
+ * // middleware.ts (Next.js 14/15)
62
+ * import { authMiddleware } from '@githat/nextjs/middleware';
63
+ *
64
+ * export default authMiddleware({
65
+ * publicRoutes: ['/', '/about', '/pricing'],
66
+ * signInUrl: '/sign-in',
67
+ * });
68
+ *
69
+ * export const config = {
70
+ * matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],
71
+ * };
72
+ * ```
73
+ *
74
+ * @param options - Configuration options for the auth middleware
75
+ * @returns A middleware function compatible with Next.js 14/15 middleware.ts convention
76
+ */
77
+ declare function authMiddleware(options?: AuthMiddlewareOptions): (request: NextRequest) => Promise<NextResponse>;
78
+
79
+ export { type AuthHandlerOptions, type AuthMiddlewareOptions as AuthMiddlewareConfig, type AuthMiddlewareOptions, authMiddleware };
@@ -1,6 +1,6 @@
1
1
  import { NextRequest, NextResponse } from 'next/server';
2
2
 
3
- interface AuthMiddlewareOptions {
3
+ interface AuthHandlerOptions {
4
4
  /**
5
5
  * Routes that don't require authentication.
6
6
  * Supports exact paths ('/') and path prefixes ('/public/*').
@@ -42,6 +42,38 @@ interface AuthMiddlewareOptions {
42
42
  */
43
43
  secretKey?: string;
44
44
  }
45
- declare function authMiddleware(options?: AuthMiddlewareOptions): (request: NextRequest) => Promise<NextResponse<unknown>>;
46
45
 
47
- export { authMiddleware };
46
+ /**
47
+ * Options for the authMiddleware function.
48
+ * @see AuthHandlerOptions for detailed property documentation.
49
+ */
50
+ interface AuthMiddlewareOptions extends AuthHandlerOptions {
51
+ }
52
+ /**
53
+ * Creates an auth middleware handler for Next.js 14/15.
54
+ *
55
+ * @deprecated For Next.js 16+, use `authProxy` from `@githat/nextjs/proxy` instead.
56
+ * Next.js 16 renamed middleware.ts to proxy.ts. This export continues to work
57
+ * for Next.js 14/15 applications.
58
+ *
59
+ * @example
60
+ * ```typescript
61
+ * // middleware.ts (Next.js 14/15)
62
+ * import { authMiddleware } from '@githat/nextjs/middleware';
63
+ *
64
+ * export default authMiddleware({
65
+ * publicRoutes: ['/', '/about', '/pricing'],
66
+ * signInUrl: '/sign-in',
67
+ * });
68
+ *
69
+ * export const config = {
70
+ * matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],
71
+ * };
72
+ * ```
73
+ *
74
+ * @param options - Configuration options for the auth middleware
75
+ * @returns A middleware function compatible with Next.js 14/15 middleware.ts convention
76
+ */
77
+ declare function authMiddleware(options?: AuthMiddlewareOptions): (request: NextRequest) => Promise<NextResponse>;
78
+
79
+ export { type AuthHandlerOptions, type AuthMiddlewareOptions as AuthMiddlewareConfig, type AuthMiddlewareOptions, authMiddleware };
@@ -33,6 +33,8 @@ __export(middleware_exports, {
33
33
  authMiddleware: () => authMiddleware
34
34
  });
35
35
  module.exports = __toCommonJS(middleware_exports);
36
+
37
+ // src/lib/auth-handler.ts
36
38
  var import_server = require("next/server");
37
39
  var jose = __toESM(require("jose"));
38
40
  function decodeJwtPayload(token) {
@@ -56,7 +58,7 @@ async function verifyJwt(token, secretKey) {
56
58
  return null;
57
59
  }
58
60
  }
59
- function authMiddleware(options = {}) {
61
+ async function handleAuthRequest(request, options = {}) {
60
62
  const {
61
63
  publicRoutes = ["/"],
62
64
  signInUrl = "/sign-in",
@@ -65,63 +67,68 @@ function authMiddleware(options = {}) {
65
67
  injectHeaders = false,
66
68
  secretKey
67
69
  } = options;
68
- return async function middleware(request) {
69
- const { pathname } = request.nextUrl;
70
- const isPublic = publicRoutes.some((route) => {
71
- if (route.endsWith("/*")) {
72
- const prefix = route.slice(0, -1);
73
- return pathname === prefix.slice(0, -1) || pathname.startsWith(prefix);
74
- }
75
- return pathname === route;
76
- });
77
- if (isPublic) {
78
- return import_server.NextResponse.next();
79
- }
80
- if (pathname.startsWith("/_next") || pathname.startsWith("/api") || pathname.includes(".")) {
81
- return import_server.NextResponse.next();
70
+ const { pathname } = request.nextUrl;
71
+ const isPublic = publicRoutes.some((route) => {
72
+ if (route.endsWith("/*")) {
73
+ const prefix = route.slice(0, -1);
74
+ return pathname === prefix.slice(0, -1) || pathname.startsWith(prefix);
82
75
  }
83
- let token = request.cookies.get(tokenCookie)?.value;
84
- if (!token) {
85
- token = request.cookies.get(legacyTokenCookie)?.value;
86
- }
87
- if (!token) {
76
+ return pathname === route;
77
+ });
78
+ if (isPublic) {
79
+ return import_server.NextResponse.next();
80
+ }
81
+ if (pathname.startsWith("/_next") || pathname.startsWith("/api") || pathname.includes(".")) {
82
+ return import_server.NextResponse.next();
83
+ }
84
+ let token = request.cookies.get(tokenCookie)?.value;
85
+ if (!token) {
86
+ token = request.cookies.get(legacyTokenCookie)?.value;
87
+ }
88
+ if (!token) {
89
+ const signInUrlObj = new URL(signInUrl, request.url);
90
+ signInUrlObj.searchParams.set("redirect_url", pathname);
91
+ return import_server.NextResponse.redirect(signInUrlObj);
92
+ }
93
+ if (!injectHeaders) {
94
+ return import_server.NextResponse.next();
95
+ }
96
+ let payload = null;
97
+ if (secretKey) {
98
+ payload = await verifyJwt(token, secretKey);
99
+ if (!payload) {
88
100
  const signInUrlObj = new URL(signInUrl, request.url);
89
101
  signInUrlObj.searchParams.set("redirect_url", pathname);
90
102
  return import_server.NextResponse.redirect(signInUrlObj);
91
103
  }
92
- if (!injectHeaders) {
93
- return import_server.NextResponse.next();
104
+ } else {
105
+ payload = decodeJwtPayload(token);
106
+ }
107
+ const response = import_server.NextResponse.next();
108
+ if (payload) {
109
+ if (payload.userId) {
110
+ response.headers.set("x-githat-user-id", String(payload.userId));
111
+ }
112
+ if (payload.email) {
113
+ response.headers.set("x-githat-email", String(payload.email));
94
114
  }
95
- let payload = null;
96
- if (secretKey) {
97
- payload = await verifyJwt(token, secretKey);
98
- if (!payload) {
99
- const signInUrlObj = new URL(signInUrl, request.url);
100
- signInUrlObj.searchParams.set("redirect_url", pathname);
101
- return import_server.NextResponse.redirect(signInUrlObj);
102
- }
103
- } else {
104
- payload = decodeJwtPayload(token);
115
+ if (payload.orgId) {
116
+ response.headers.set("x-githat-org-id", String(payload.orgId));
105
117
  }
106
- const response = import_server.NextResponse.next();
107
- if (payload) {
108
- if (payload.userId) {
109
- response.headers.set("x-githat-user-id", String(payload.userId));
110
- }
111
- if (payload.email) {
112
- response.headers.set("x-githat-email", String(payload.email));
113
- }
114
- if (payload.orgId) {
115
- response.headers.set("x-githat-org-id", String(payload.orgId));
116
- }
117
- if (payload.orgSlug) {
118
- response.headers.set("x-githat-org-slug", String(payload.orgSlug));
119
- }
120
- if (payload.orgRole) {
121
- response.headers.set("x-githat-role", String(payload.orgRole));
122
- }
118
+ if (payload.orgSlug) {
119
+ response.headers.set("x-githat-org-slug", String(payload.orgSlug));
123
120
  }
124
- return response;
121
+ if (payload.orgRole) {
122
+ response.headers.set("x-githat-role", String(payload.orgRole));
123
+ }
124
+ }
125
+ return response;
126
+ }
127
+
128
+ // src/middleware/index.ts
129
+ function authMiddleware(options = {}) {
130
+ return async function middleware(request) {
131
+ return handleAuthRequest(request, options);
125
132
  };
126
133
  }
127
134
  // Annotate the CommonJS export names for ESM import in node:
@@ -1 +1 @@
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":[]}
1
+ {"version":3,"sources":["../src/middleware/index.ts","../src/lib/auth-handler.ts"],"sourcesContent":["import type { NextRequest } from 'next/server';\nimport type { NextResponse } from 'next/server';\nimport { handleAuthRequest, AuthHandlerOptions } from '../lib/auth-handler';\n\n/**\n * Options for the authMiddleware function.\n * @see AuthHandlerOptions for detailed property documentation.\n */\nexport interface AuthMiddlewareOptions extends AuthHandlerOptions {}\n\n/**\n * Creates an auth middleware handler for Next.js 14/15.\n *\n * @deprecated For Next.js 16+, use `authProxy` from `@githat/nextjs/proxy` instead.\n * Next.js 16 renamed middleware.ts to proxy.ts. This export continues to work\n * for Next.js 14/15 applications.\n *\n * @example\n * ```typescript\n * // middleware.ts (Next.js 14/15)\n * import { authMiddleware } from '@githat/nextjs/middleware';\n *\n * export default authMiddleware({\n * publicRoutes: ['/', '/about', '/pricing'],\n * signInUrl: '/sign-in',\n * });\n *\n * export const config = {\n * matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],\n * };\n * ```\n *\n * @param options - Configuration options for the auth middleware\n * @returns A middleware function compatible with Next.js 14/15 middleware.ts convention\n */\nexport function authMiddleware(options: AuthMiddlewareOptions = {}) {\n return async function middleware(request: NextRequest): Promise<NextResponse> {\n return handleAuthRequest(request, options);\n };\n}\n\n// Re-export types for convenience\nexport type { AuthMiddlewareOptions as AuthMiddlewareConfig };\nexport type { AuthHandlerOptions };\n","import { NextResponse } from 'next/server';\nimport type { NextRequest } from 'next/server';\nimport * as jose from 'jose';\n\nexport interface AuthHandlerOptions {\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\n/**\n * Shared auth handling logic for both middleware (Next.js 14/15) and proxy (Next.js 16+).\n * Validates authentication tokens and optionally injects headers with user/org info.\n */\nexport async function handleAuthRequest(\n request: NextRequest,\n options: AuthHandlerOptions = {}\n): Promise<NextResponse> {\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 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"],"mappings":"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","names":[]}
@@ -1,4 +1,4 @@
1
- // src/middleware/index.ts
1
+ // src/lib/auth-handler.ts
2
2
  import { NextResponse } from "next/server";
3
3
  import * as jose from "jose";
4
4
  function decodeJwtPayload(token) {
@@ -22,7 +22,7 @@ async function verifyJwt(token, secretKey) {
22
22
  return null;
23
23
  }
24
24
  }
25
- function authMiddleware(options = {}) {
25
+ async function handleAuthRequest(request, options = {}) {
26
26
  const {
27
27
  publicRoutes = ["/"],
28
28
  signInUrl = "/sign-in",
@@ -31,63 +31,68 @@ function authMiddleware(options = {}) {
31
31
  injectHeaders = false,
32
32
  secretKey
33
33
  } = options;
34
- return async function middleware(request) {
35
- const { pathname } = request.nextUrl;
36
- const isPublic = publicRoutes.some((route) => {
37
- if (route.endsWith("/*")) {
38
- const prefix = route.slice(0, -1);
39
- return pathname === prefix.slice(0, -1) || pathname.startsWith(prefix);
40
- }
41
- return pathname === route;
42
- });
43
- if (isPublic) {
44
- return NextResponse.next();
45
- }
46
- if (pathname.startsWith("/_next") || pathname.startsWith("/api") || pathname.includes(".")) {
47
- return NextResponse.next();
34
+ const { pathname } = request.nextUrl;
35
+ const isPublic = publicRoutes.some((route) => {
36
+ if (route.endsWith("/*")) {
37
+ const prefix = route.slice(0, -1);
38
+ return pathname === prefix.slice(0, -1) || pathname.startsWith(prefix);
48
39
  }
49
- let token = request.cookies.get(tokenCookie)?.value;
50
- if (!token) {
51
- token = request.cookies.get(legacyTokenCookie)?.value;
52
- }
53
- if (!token) {
40
+ return pathname === route;
41
+ });
42
+ if (isPublic) {
43
+ return NextResponse.next();
44
+ }
45
+ if (pathname.startsWith("/_next") || pathname.startsWith("/api") || pathname.includes(".")) {
46
+ return NextResponse.next();
47
+ }
48
+ let token = request.cookies.get(tokenCookie)?.value;
49
+ if (!token) {
50
+ token = request.cookies.get(legacyTokenCookie)?.value;
51
+ }
52
+ if (!token) {
53
+ const signInUrlObj = new URL(signInUrl, request.url);
54
+ signInUrlObj.searchParams.set("redirect_url", pathname);
55
+ return NextResponse.redirect(signInUrlObj);
56
+ }
57
+ if (!injectHeaders) {
58
+ return NextResponse.next();
59
+ }
60
+ let payload = null;
61
+ if (secretKey) {
62
+ payload = await verifyJwt(token, secretKey);
63
+ if (!payload) {
54
64
  const signInUrlObj = new URL(signInUrl, request.url);
55
65
  signInUrlObj.searchParams.set("redirect_url", pathname);
56
66
  return NextResponse.redirect(signInUrlObj);
57
67
  }
58
- if (!injectHeaders) {
59
- return NextResponse.next();
68
+ } else {
69
+ payload = decodeJwtPayload(token);
70
+ }
71
+ const response = NextResponse.next();
72
+ if (payload) {
73
+ if (payload.userId) {
74
+ response.headers.set("x-githat-user-id", String(payload.userId));
75
+ }
76
+ if (payload.email) {
77
+ response.headers.set("x-githat-email", String(payload.email));
60
78
  }
61
- let payload = null;
62
- if (secretKey) {
63
- payload = await verifyJwt(token, secretKey);
64
- if (!payload) {
65
- const signInUrlObj = new URL(signInUrl, request.url);
66
- signInUrlObj.searchParams.set("redirect_url", pathname);
67
- return NextResponse.redirect(signInUrlObj);
68
- }
69
- } else {
70
- payload = decodeJwtPayload(token);
79
+ if (payload.orgId) {
80
+ response.headers.set("x-githat-org-id", String(payload.orgId));
71
81
  }
72
- const response = NextResponse.next();
73
- if (payload) {
74
- if (payload.userId) {
75
- response.headers.set("x-githat-user-id", String(payload.userId));
76
- }
77
- if (payload.email) {
78
- response.headers.set("x-githat-email", String(payload.email));
79
- }
80
- if (payload.orgId) {
81
- response.headers.set("x-githat-org-id", String(payload.orgId));
82
- }
83
- if (payload.orgSlug) {
84
- response.headers.set("x-githat-org-slug", String(payload.orgSlug));
85
- }
86
- if (payload.orgRole) {
87
- response.headers.set("x-githat-role", String(payload.orgRole));
88
- }
82
+ if (payload.orgSlug) {
83
+ response.headers.set("x-githat-org-slug", String(payload.orgSlug));
89
84
  }
90
- return response;
85
+ if (payload.orgRole) {
86
+ response.headers.set("x-githat-role", String(payload.orgRole));
87
+ }
88
+ }
89
+ return response;
90
+ }
91
+
92
+ // src/middleware/index.ts
93
+ function authMiddleware(options = {}) {
94
+ return async function middleware(request) {
95
+ return handleAuthRequest(request, options);
91
96
  };
92
97
  }
93
98
  export {
@@ -1 +1 @@
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":[]}
1
+ {"version":3,"sources":["../src/lib/auth-handler.ts","../src/middleware/index.ts"],"sourcesContent":["import { NextResponse } from 'next/server';\nimport type { NextRequest } from 'next/server';\nimport * as jose from 'jose';\n\nexport interface AuthHandlerOptions {\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\n/**\n * Shared auth handling logic for both middleware (Next.js 14/15) and proxy (Next.js 16+).\n * Validates authentication tokens and optionally injects headers with user/org info.\n */\nexport async function handleAuthRequest(\n request: NextRequest,\n options: AuthHandlerOptions = {}\n): Promise<NextResponse> {\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 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","import type { NextRequest } from 'next/server';\nimport type { NextResponse } from 'next/server';\nimport { handleAuthRequest, AuthHandlerOptions } from '../lib/auth-handler';\n\n/**\n * Options for the authMiddleware function.\n * @see AuthHandlerOptions for detailed property documentation.\n */\nexport interface AuthMiddlewareOptions extends AuthHandlerOptions {}\n\n/**\n * Creates an auth middleware handler for Next.js 14/15.\n *\n * @deprecated For Next.js 16+, use `authProxy` from `@githat/nextjs/proxy` instead.\n * Next.js 16 renamed middleware.ts to proxy.ts. This export continues to work\n * for Next.js 14/15 applications.\n *\n * @example\n * ```typescript\n * // middleware.ts (Next.js 14/15)\n * import { authMiddleware } from '@githat/nextjs/middleware';\n *\n * export default authMiddleware({\n * publicRoutes: ['/', '/about', '/pricing'],\n * signInUrl: '/sign-in',\n * });\n *\n * export const config = {\n * matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],\n * };\n * ```\n *\n * @param options - Configuration options for the auth middleware\n * @returns A middleware function compatible with Next.js 14/15 middleware.ts convention\n */\nexport function authMiddleware(options: AuthMiddlewareOptions = {}) {\n return async function middleware(request: NextRequest): Promise<NextResponse> {\n return handleAuthRequest(request, options);\n };\n}\n\n// Re-export types for convenience\nexport type { AuthMiddlewareOptions as AuthMiddlewareConfig };\nexport type { AuthHandlerOptions };\n"],"mappings":";AAAA,SAAS,oBAAoB;AAE7B,YAAY,UAAU;AAsDtB,SAAS,iBAAiB,OAA+C;AACvE,MAAI;AACF,UAAM,QAAQ,MAAM,MAAM,GAAG;AAC7B,QAAI,MAAM,WAAW,EAAG,QAAO;AAC/B,UAAM,UAAU,KAAK,MAAM,KAAK,MAAM,CAAC,CAAC,CAAC;AACzC,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAMA,eAAe,UACb,OACA,WACyC;AACzC,MAAI;AACF,UAAM,SAAS,IAAI,YAAY,EAAE,OAAO,SAAS;AACjD,UAAM,EAAE,QAAQ,IAAI,MAAW,eAAU,OAAO,QAAQ;AAAA,MACtD,YAAY,CAAC,OAAO;AAAA,IACtB,CAAC;AACD,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAMA,eAAsB,kBACpB,SACA,UAA8B,CAAC,GACR;AACvB,QAAM;AAAA,IACJ,eAAe,CAAC,GAAG;AAAA,IACnB,YAAY;AAAA,IACZ,cAAc;AAAA,IACd,oBAAoB;AAAA,IACpB,gBAAgB;AAAA,IAChB;AAAA,EACF,IAAI;AAEJ,QAAM,EAAE,SAAS,IAAI,QAAQ;AAG7B,QAAM,WAAW,aAAa,KAAK,CAAC,UAAU;AAC5C,QAAI,MAAM,SAAS,IAAI,GAAG;AACxB,YAAM,SAAS,MAAM,MAAM,GAAG,EAAE;AAChC,aAAO,aAAa,OAAO,MAAM,GAAG,EAAE,KAAK,SAAS,WAAW,MAAM;AAAA,IACvE;AACA,WAAO,aAAa;AAAA,EACtB,CAAC;AAED,MAAI,UAAU;AACZ,WAAO,aAAa,KAAK;AAAA,EAC3B;AAGA,MACE,SAAS,WAAW,QAAQ,KAC5B,SAAS,WAAW,MAAM,KAC1B,SAAS,SAAS,GAAG,GACrB;AACA,WAAO,aAAa,KAAK;AAAA,EAC3B;AAGA,MAAI,QAAQ,QAAQ,QAAQ,IAAI,WAAW,GAAG;AAC9C,MAAI,CAAC,OAAO;AACV,YAAQ,QAAQ,QAAQ,IAAI,iBAAiB,GAAG;AAAA,EAClD;AAGA,MAAI,CAAC,OAAO;AACV,UAAM,eAAe,IAAI,IAAI,WAAW,QAAQ,GAAG;AACnD,iBAAa,aAAa,IAAI,gBAAgB,QAAQ;AACtD,WAAO,aAAa,SAAS,YAAY;AAAA,EAC3C;AAGA,MAAI,CAAC,eAAe;AAClB,WAAO,aAAa,KAAK;AAAA,EAC3B;AAGA,MAAI,UAA0C;AAE9C,MAAI,WAAW;AAEb,cAAU,MAAM,UAAU,OAAO,SAAS;AAC1C,QAAI,CAAC,SAAS;AAEZ,YAAM,eAAe,IAAI,IAAI,WAAW,QAAQ,GAAG;AACnD,mBAAa,aAAa,IAAI,gBAAgB,QAAQ;AACtD,aAAO,aAAa,SAAS,YAAY;AAAA,IAC3C;AAAA,EACF,OAAO;AAEL,cAAU,iBAAiB,KAAK;AAAA,EAClC;AAGA,QAAM,WAAW,aAAa,KAAK;AAEnC,MAAI,SAAS;AAEX,QAAI,QAAQ,QAAQ;AAClB,eAAS,QAAQ,IAAI,oBAAoB,OAAO,QAAQ,MAAM,CAAC;AAAA,IACjE;AACA,QAAI,QAAQ,OAAO;AACjB,eAAS,QAAQ,IAAI,kBAAkB,OAAO,QAAQ,KAAK,CAAC;AAAA,IAC9D;AACA,QAAI,QAAQ,OAAO;AACjB,eAAS,QAAQ,IAAI,mBAAmB,OAAO,QAAQ,KAAK,CAAC;AAAA,IAC/D;AACA,QAAI,QAAQ,SAAS;AACnB,eAAS,QAAQ,IAAI,qBAAqB,OAAO,QAAQ,OAAO,CAAC;AAAA,IACnE;AACA,QAAI,QAAQ,SAAS;AACnB,eAAS,QAAQ,IAAI,iBAAiB,OAAO,QAAQ,OAAO,CAAC;AAAA,IAC/D;AAAA,EACF;AAEA,SAAO;AACT;;;ACtJO,SAAS,eAAe,UAAiC,CAAC,GAAG;AAClE,SAAO,eAAe,WAAW,SAA6C;AAC5E,WAAO,kBAAkB,SAAS,OAAO;AAAA,EAC3C;AACF;","names":[]}
@@ -0,0 +1,78 @@
1
+ import { NextRequest, NextResponse } from 'next/server';
2
+
3
+ interface AuthHandlerOptions {
4
+ /**
5
+ * Routes that don't require authentication.
6
+ * Supports exact paths ('/') and path prefixes ('/public/*').
7
+ */
8
+ publicRoutes?: string[];
9
+ /**
10
+ * URL to redirect to when authentication is required but not present.
11
+ * @default '/sign-in'
12
+ */
13
+ signInUrl?: string;
14
+ /**
15
+ * Cookie name for the access token.
16
+ * @default 'githat_access'
17
+ */
18
+ tokenCookie?: string;
19
+ /**
20
+ * Legacy localStorage token cookie name (for backward compatibility).
21
+ * @default 'githat_access_token'
22
+ */
23
+ legacyTokenCookie?: string;
24
+ /**
25
+ * When true, decode the JWT and inject x-githat-* headers into the request.
26
+ * This allows downstream API routes to access user/org info without re-verifying.
27
+ *
28
+ * Injected headers:
29
+ * - x-githat-user-id: User's unique ID
30
+ * - x-githat-email: User's email address
31
+ * - x-githat-org-id: Current org ID (if any)
32
+ * - x-githat-org-slug: Current org slug (if any)
33
+ * - x-githat-role: User's role in the org (owner/admin/member)
34
+ *
35
+ * @default false
36
+ */
37
+ injectHeaders?: boolean;
38
+ /**
39
+ * Secret key for local JWT verification when injectHeaders is true.
40
+ * If not provided, headers are populated from the decoded (unverified) token payload.
41
+ * For production, always provide the secret key for full verification.
42
+ */
43
+ secretKey?: string;
44
+ }
45
+
46
+ /**
47
+ * Options for the authProxy function.
48
+ * @see AuthHandlerOptions for detailed property documentation.
49
+ */
50
+ interface AuthProxyOptions extends AuthHandlerOptions {
51
+ }
52
+ /**
53
+ * Creates an auth proxy handler for Next.js 16+.
54
+ *
55
+ * Next.js 16 renamed middleware.ts to proxy.ts and the export from
56
+ * `export default middleware` to `export const proxy`.
57
+ *
58
+ * @example
59
+ * ```typescript
60
+ * // proxy.ts (Next.js 16+)
61
+ * import { authProxy } from '@githat/nextjs/proxy';
62
+ *
63
+ * export const proxy = authProxy({
64
+ * publicRoutes: ['/', '/about', '/pricing'],
65
+ * signInUrl: '/sign-in',
66
+ * });
67
+ *
68
+ * export const config = {
69
+ * matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],
70
+ * };
71
+ * ```
72
+ *
73
+ * @param options - Configuration options for the auth proxy
74
+ * @returns A proxy function compatible with Next.js 16+ proxy.ts convention
75
+ */
76
+ declare function authProxy(options?: AuthProxyOptions): (request: NextRequest) => Promise<NextResponse>;
77
+
78
+ export { type AuthHandlerOptions, type AuthProxyOptions as AuthProxyConfig, type AuthProxyOptions, authProxy };
@@ -0,0 +1,78 @@
1
+ import { NextRequest, NextResponse } from 'next/server';
2
+
3
+ interface AuthHandlerOptions {
4
+ /**
5
+ * Routes that don't require authentication.
6
+ * Supports exact paths ('/') and path prefixes ('/public/*').
7
+ */
8
+ publicRoutes?: string[];
9
+ /**
10
+ * URL to redirect to when authentication is required but not present.
11
+ * @default '/sign-in'
12
+ */
13
+ signInUrl?: string;
14
+ /**
15
+ * Cookie name for the access token.
16
+ * @default 'githat_access'
17
+ */
18
+ tokenCookie?: string;
19
+ /**
20
+ * Legacy localStorage token cookie name (for backward compatibility).
21
+ * @default 'githat_access_token'
22
+ */
23
+ legacyTokenCookie?: string;
24
+ /**
25
+ * When true, decode the JWT and inject x-githat-* headers into the request.
26
+ * This allows downstream API routes to access user/org info without re-verifying.
27
+ *
28
+ * Injected headers:
29
+ * - x-githat-user-id: User's unique ID
30
+ * - x-githat-email: User's email address
31
+ * - x-githat-org-id: Current org ID (if any)
32
+ * - x-githat-org-slug: Current org slug (if any)
33
+ * - x-githat-role: User's role in the org (owner/admin/member)
34
+ *
35
+ * @default false
36
+ */
37
+ injectHeaders?: boolean;
38
+ /**
39
+ * Secret key for local JWT verification when injectHeaders is true.
40
+ * If not provided, headers are populated from the decoded (unverified) token payload.
41
+ * For production, always provide the secret key for full verification.
42
+ */
43
+ secretKey?: string;
44
+ }
45
+
46
+ /**
47
+ * Options for the authProxy function.
48
+ * @see AuthHandlerOptions for detailed property documentation.
49
+ */
50
+ interface AuthProxyOptions extends AuthHandlerOptions {
51
+ }
52
+ /**
53
+ * Creates an auth proxy handler for Next.js 16+.
54
+ *
55
+ * Next.js 16 renamed middleware.ts to proxy.ts and the export from
56
+ * `export default middleware` to `export const proxy`.
57
+ *
58
+ * @example
59
+ * ```typescript
60
+ * // proxy.ts (Next.js 16+)
61
+ * import { authProxy } from '@githat/nextjs/proxy';
62
+ *
63
+ * export const proxy = authProxy({
64
+ * publicRoutes: ['/', '/about', '/pricing'],
65
+ * signInUrl: '/sign-in',
66
+ * });
67
+ *
68
+ * export const config = {
69
+ * matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],
70
+ * };
71
+ * ```
72
+ *
73
+ * @param options - Configuration options for the auth proxy
74
+ * @returns A proxy function compatible with Next.js 16+ proxy.ts convention
75
+ */
76
+ declare function authProxy(options?: AuthProxyOptions): (request: NextRequest) => Promise<NextResponse>;
77
+
78
+ export { type AuthHandlerOptions, type AuthProxyOptions as AuthProxyConfig, type AuthProxyOptions, authProxy };
package/dist/proxy.js ADDED
@@ -0,0 +1,138 @@
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/proxy/index.ts
31
+ var proxy_exports = {};
32
+ __export(proxy_exports, {
33
+ authProxy: () => authProxy
34
+ });
35
+ module.exports = __toCommonJS(proxy_exports);
36
+
37
+ // src/lib/auth-handler.ts
38
+ var import_server = require("next/server");
39
+ var jose = __toESM(require("jose"));
40
+ function decodeJwtPayload(token) {
41
+ try {
42
+ const parts = token.split(".");
43
+ if (parts.length !== 3) return null;
44
+ const payload = JSON.parse(atob(parts[1]));
45
+ return payload;
46
+ } catch {
47
+ return null;
48
+ }
49
+ }
50
+ async function verifyJwt(token, secretKey) {
51
+ try {
52
+ const secret = new TextEncoder().encode(secretKey);
53
+ const { payload } = await jose.jwtVerify(token, secret, {
54
+ algorithms: ["HS256"]
55
+ });
56
+ return payload;
57
+ } catch {
58
+ return null;
59
+ }
60
+ }
61
+ async function handleAuthRequest(request, options = {}) {
62
+ const {
63
+ publicRoutes = ["/"],
64
+ signInUrl = "/sign-in",
65
+ tokenCookie = "githat_access",
66
+ legacyTokenCookie = "githat_access_token",
67
+ injectHeaders = false,
68
+ secretKey
69
+ } = options;
70
+ const { pathname } = request.nextUrl;
71
+ const isPublic = publicRoutes.some((route) => {
72
+ if (route.endsWith("/*")) {
73
+ const prefix = route.slice(0, -1);
74
+ return pathname === prefix.slice(0, -1) || pathname.startsWith(prefix);
75
+ }
76
+ return pathname === route;
77
+ });
78
+ if (isPublic) {
79
+ return import_server.NextResponse.next();
80
+ }
81
+ if (pathname.startsWith("/_next") || pathname.startsWith("/api") || pathname.includes(".")) {
82
+ return import_server.NextResponse.next();
83
+ }
84
+ let token = request.cookies.get(tokenCookie)?.value;
85
+ if (!token) {
86
+ token = request.cookies.get(legacyTokenCookie)?.value;
87
+ }
88
+ if (!token) {
89
+ const signInUrlObj = new URL(signInUrl, request.url);
90
+ signInUrlObj.searchParams.set("redirect_url", pathname);
91
+ return import_server.NextResponse.redirect(signInUrlObj);
92
+ }
93
+ if (!injectHeaders) {
94
+ return import_server.NextResponse.next();
95
+ }
96
+ let payload = null;
97
+ if (secretKey) {
98
+ payload = await verifyJwt(token, secretKey);
99
+ if (!payload) {
100
+ const signInUrlObj = new URL(signInUrl, request.url);
101
+ signInUrlObj.searchParams.set("redirect_url", pathname);
102
+ return import_server.NextResponse.redirect(signInUrlObj);
103
+ }
104
+ } else {
105
+ payload = decodeJwtPayload(token);
106
+ }
107
+ const response = import_server.NextResponse.next();
108
+ if (payload) {
109
+ if (payload.userId) {
110
+ response.headers.set("x-githat-user-id", String(payload.userId));
111
+ }
112
+ if (payload.email) {
113
+ response.headers.set("x-githat-email", String(payload.email));
114
+ }
115
+ if (payload.orgId) {
116
+ response.headers.set("x-githat-org-id", String(payload.orgId));
117
+ }
118
+ if (payload.orgSlug) {
119
+ response.headers.set("x-githat-org-slug", String(payload.orgSlug));
120
+ }
121
+ if (payload.orgRole) {
122
+ response.headers.set("x-githat-role", String(payload.orgRole));
123
+ }
124
+ }
125
+ return response;
126
+ }
127
+
128
+ // src/proxy/index.ts
129
+ function authProxy(options = {}) {
130
+ return async function proxy(request) {
131
+ return handleAuthRequest(request, options);
132
+ };
133
+ }
134
+ // Annotate the CommonJS export names for ESM import in node:
135
+ 0 && (module.exports = {
136
+ authProxy
137
+ });
138
+ //# sourceMappingURL=proxy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/proxy/index.ts","../src/lib/auth-handler.ts"],"sourcesContent":["import type { NextRequest } from 'next/server';\nimport type { NextResponse } from 'next/server';\nimport { handleAuthRequest, AuthHandlerOptions } from '../lib/auth-handler';\n\n/**\n * Options for the authProxy function.\n * @see AuthHandlerOptions for detailed property documentation.\n */\nexport interface AuthProxyOptions extends AuthHandlerOptions {}\n\n/**\n * Creates an auth proxy handler for Next.js 16+.\n *\n * Next.js 16 renamed middleware.ts to proxy.ts and the export from\n * `export default middleware` to `export const proxy`.\n *\n * @example\n * ```typescript\n * // proxy.ts (Next.js 16+)\n * import { authProxy } from '@githat/nextjs/proxy';\n *\n * export const proxy = authProxy({\n * publicRoutes: ['/', '/about', '/pricing'],\n * signInUrl: '/sign-in',\n * });\n *\n * export const config = {\n * matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],\n * };\n * ```\n *\n * @param options - Configuration options for the auth proxy\n * @returns A proxy function compatible with Next.js 16+ proxy.ts convention\n */\nexport function authProxy(options: AuthProxyOptions = {}) {\n return async function proxy(request: NextRequest): Promise<NextResponse> {\n return handleAuthRequest(request, options);\n };\n}\n\n// Re-export types for convenience\nexport type { AuthProxyOptions as AuthProxyConfig };\nexport type { AuthHandlerOptions };\n","import { NextResponse } from 'next/server';\nimport type { NextRequest } from 'next/server';\nimport * as jose from 'jose';\n\nexport interface AuthHandlerOptions {\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\n/**\n * Shared auth handling logic for both middleware (Next.js 14/15) and proxy (Next.js 16+).\n * Validates authentication tokens and optionally injects headers with user/org info.\n */\nexport async function handleAuthRequest(\n request: NextRequest,\n options: AuthHandlerOptions = {}\n): Promise<NextResponse> {\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 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"],"mappings":"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","names":[]}
package/dist/proxy.mjs ADDED
@@ -0,0 +1,101 @@
1
+ // src/lib/auth-handler.ts
2
+ import { NextResponse } from "next/server";
3
+ import * as jose from "jose";
4
+ function decodeJwtPayload(token) {
5
+ try {
6
+ const parts = token.split(".");
7
+ if (parts.length !== 3) return null;
8
+ const payload = JSON.parse(atob(parts[1]));
9
+ return payload;
10
+ } catch {
11
+ return null;
12
+ }
13
+ }
14
+ async function verifyJwt(token, secretKey) {
15
+ try {
16
+ const secret = new TextEncoder().encode(secretKey);
17
+ const { payload } = await jose.jwtVerify(token, secret, {
18
+ algorithms: ["HS256"]
19
+ });
20
+ return payload;
21
+ } catch {
22
+ return null;
23
+ }
24
+ }
25
+ async function handleAuthRequest(request, options = {}) {
26
+ const {
27
+ publicRoutes = ["/"],
28
+ signInUrl = "/sign-in",
29
+ tokenCookie = "githat_access",
30
+ legacyTokenCookie = "githat_access_token",
31
+ injectHeaders = false,
32
+ secretKey
33
+ } = options;
34
+ const { pathname } = request.nextUrl;
35
+ const isPublic = publicRoutes.some((route) => {
36
+ if (route.endsWith("/*")) {
37
+ const prefix = route.slice(0, -1);
38
+ return pathname === prefix.slice(0, -1) || pathname.startsWith(prefix);
39
+ }
40
+ return pathname === route;
41
+ });
42
+ if (isPublic) {
43
+ return NextResponse.next();
44
+ }
45
+ if (pathname.startsWith("/_next") || pathname.startsWith("/api") || pathname.includes(".")) {
46
+ return NextResponse.next();
47
+ }
48
+ let token = request.cookies.get(tokenCookie)?.value;
49
+ if (!token) {
50
+ token = request.cookies.get(legacyTokenCookie)?.value;
51
+ }
52
+ if (!token) {
53
+ const signInUrlObj = new URL(signInUrl, request.url);
54
+ signInUrlObj.searchParams.set("redirect_url", pathname);
55
+ return NextResponse.redirect(signInUrlObj);
56
+ }
57
+ if (!injectHeaders) {
58
+ return NextResponse.next();
59
+ }
60
+ let payload = null;
61
+ if (secretKey) {
62
+ payload = await verifyJwt(token, secretKey);
63
+ if (!payload) {
64
+ const signInUrlObj = new URL(signInUrl, request.url);
65
+ signInUrlObj.searchParams.set("redirect_url", pathname);
66
+ return NextResponse.redirect(signInUrlObj);
67
+ }
68
+ } else {
69
+ payload = decodeJwtPayload(token);
70
+ }
71
+ const response = NextResponse.next();
72
+ if (payload) {
73
+ if (payload.userId) {
74
+ response.headers.set("x-githat-user-id", String(payload.userId));
75
+ }
76
+ if (payload.email) {
77
+ response.headers.set("x-githat-email", String(payload.email));
78
+ }
79
+ if (payload.orgId) {
80
+ response.headers.set("x-githat-org-id", String(payload.orgId));
81
+ }
82
+ if (payload.orgSlug) {
83
+ response.headers.set("x-githat-org-slug", String(payload.orgSlug));
84
+ }
85
+ if (payload.orgRole) {
86
+ response.headers.set("x-githat-role", String(payload.orgRole));
87
+ }
88
+ }
89
+ return response;
90
+ }
91
+
92
+ // src/proxy/index.ts
93
+ function authProxy(options = {}) {
94
+ return async function proxy(request) {
95
+ return handleAuthRequest(request, options);
96
+ };
97
+ }
98
+ export {
99
+ authProxy
100
+ };
101
+ //# sourceMappingURL=proxy.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/auth-handler.ts","../src/proxy/index.ts"],"sourcesContent":["import { NextResponse } from 'next/server';\nimport type { NextRequest } from 'next/server';\nimport * as jose from 'jose';\n\nexport interface AuthHandlerOptions {\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\n/**\n * Shared auth handling logic for both middleware (Next.js 14/15) and proxy (Next.js 16+).\n * Validates authentication tokens and optionally injects headers with user/org info.\n */\nexport async function handleAuthRequest(\n request: NextRequest,\n options: AuthHandlerOptions = {}\n): Promise<NextResponse> {\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 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","import type { NextRequest } from 'next/server';\nimport type { NextResponse } from 'next/server';\nimport { handleAuthRequest, AuthHandlerOptions } from '../lib/auth-handler';\n\n/**\n * Options for the authProxy function.\n * @see AuthHandlerOptions for detailed property documentation.\n */\nexport interface AuthProxyOptions extends AuthHandlerOptions {}\n\n/**\n * Creates an auth proxy handler for Next.js 16+.\n *\n * Next.js 16 renamed middleware.ts to proxy.ts and the export from\n * `export default middleware` to `export const proxy`.\n *\n * @example\n * ```typescript\n * // proxy.ts (Next.js 16+)\n * import { authProxy } from '@githat/nextjs/proxy';\n *\n * export const proxy = authProxy({\n * publicRoutes: ['/', '/about', '/pricing'],\n * signInUrl: '/sign-in',\n * });\n *\n * export const config = {\n * matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],\n * };\n * ```\n *\n * @param options - Configuration options for the auth proxy\n * @returns A proxy function compatible with Next.js 16+ proxy.ts convention\n */\nexport function authProxy(options: AuthProxyOptions = {}) {\n return async function proxy(request: NextRequest): Promise<NextResponse> {\n return handleAuthRequest(request, options);\n };\n}\n\n// Re-export types for convenience\nexport type { AuthProxyOptions as AuthProxyConfig };\nexport type { AuthHandlerOptions };\n"],"mappings":";AAAA,SAAS,oBAAoB;AAE7B,YAAY,UAAU;AAsDtB,SAAS,iBAAiB,OAA+C;AACvE,MAAI;AACF,UAAM,QAAQ,MAAM,MAAM,GAAG;AAC7B,QAAI,MAAM,WAAW,EAAG,QAAO;AAC/B,UAAM,UAAU,KAAK,MAAM,KAAK,MAAM,CAAC,CAAC,CAAC;AACzC,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAMA,eAAe,UACb,OACA,WACyC;AACzC,MAAI;AACF,UAAM,SAAS,IAAI,YAAY,EAAE,OAAO,SAAS;AACjD,UAAM,EAAE,QAAQ,IAAI,MAAW,eAAU,OAAO,QAAQ;AAAA,MACtD,YAAY,CAAC,OAAO;AAAA,IACtB,CAAC;AACD,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAMA,eAAsB,kBACpB,SACA,UAA8B,CAAC,GACR;AACvB,QAAM;AAAA,IACJ,eAAe,CAAC,GAAG;AAAA,IACnB,YAAY;AAAA,IACZ,cAAc;AAAA,IACd,oBAAoB;AAAA,IACpB,gBAAgB;AAAA,IAChB;AAAA,EACF,IAAI;AAEJ,QAAM,EAAE,SAAS,IAAI,QAAQ;AAG7B,QAAM,WAAW,aAAa,KAAK,CAAC,UAAU;AAC5C,QAAI,MAAM,SAAS,IAAI,GAAG;AACxB,YAAM,SAAS,MAAM,MAAM,GAAG,EAAE;AAChC,aAAO,aAAa,OAAO,MAAM,GAAG,EAAE,KAAK,SAAS,WAAW,MAAM;AAAA,IACvE;AACA,WAAO,aAAa;AAAA,EACtB,CAAC;AAED,MAAI,UAAU;AACZ,WAAO,aAAa,KAAK;AAAA,EAC3B;AAGA,MACE,SAAS,WAAW,QAAQ,KAC5B,SAAS,WAAW,MAAM,KAC1B,SAAS,SAAS,GAAG,GACrB;AACA,WAAO,aAAa,KAAK;AAAA,EAC3B;AAGA,MAAI,QAAQ,QAAQ,QAAQ,IAAI,WAAW,GAAG;AAC9C,MAAI,CAAC,OAAO;AACV,YAAQ,QAAQ,QAAQ,IAAI,iBAAiB,GAAG;AAAA,EAClD;AAGA,MAAI,CAAC,OAAO;AACV,UAAM,eAAe,IAAI,IAAI,WAAW,QAAQ,GAAG;AACnD,iBAAa,aAAa,IAAI,gBAAgB,QAAQ;AACtD,WAAO,aAAa,SAAS,YAAY;AAAA,EAC3C;AAGA,MAAI,CAAC,eAAe;AAClB,WAAO,aAAa,KAAK;AAAA,EAC3B;AAGA,MAAI,UAA0C;AAE9C,MAAI,WAAW;AAEb,cAAU,MAAM,UAAU,OAAO,SAAS;AAC1C,QAAI,CAAC,SAAS;AAEZ,YAAM,eAAe,IAAI,IAAI,WAAW,QAAQ,GAAG;AACnD,mBAAa,aAAa,IAAI,gBAAgB,QAAQ;AACtD,aAAO,aAAa,SAAS,YAAY;AAAA,IAC3C;AAAA,EACF,OAAO;AAEL,cAAU,iBAAiB,KAAK;AAAA,EAClC;AAGA,QAAM,WAAW,aAAa,KAAK;AAEnC,MAAI,SAAS;AAEX,QAAI,QAAQ,QAAQ;AAClB,eAAS,QAAQ,IAAI,oBAAoB,OAAO,QAAQ,MAAM,CAAC;AAAA,IACjE;AACA,QAAI,QAAQ,OAAO;AACjB,eAAS,QAAQ,IAAI,kBAAkB,OAAO,QAAQ,KAAK,CAAC;AAAA,IAC9D;AACA,QAAI,QAAQ,OAAO;AACjB,eAAS,QAAQ,IAAI,mBAAmB,OAAO,QAAQ,KAAK,CAAC;AAAA,IAC/D;AACA,QAAI,QAAQ,SAAS;AACnB,eAAS,QAAQ,IAAI,qBAAqB,OAAO,QAAQ,OAAO,CAAC;AAAA,IACnE;AACA,QAAI,QAAQ,SAAS;AACnB,eAAS,QAAQ,IAAI,iBAAiB,OAAO,QAAQ,OAAO,CAAC;AAAA,IAC/D;AAAA,EACF;AAEA,SAAO;AACT;;;ACvJO,SAAS,UAAU,UAA4B,CAAC,GAAG;AACxD,SAAO,eAAe,MAAM,SAA6C;AACvE,WAAO,kBAAkB,SAAS,OAAO;AAAA,EAC3C;AACF;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@githat/nextjs",
3
- "version": "0.3.1",
3
+ "version": "0.4.0",
4
4
  "description": "GitHat identity SDK for Next.js applications",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -16,6 +16,11 @@
16
16
  "import": "./dist/middleware.mjs",
17
17
  "require": "./dist/middleware.js"
18
18
  },
19
+ "./proxy": {
20
+ "types": "./dist/proxy.d.ts",
21
+ "import": "./dist/proxy.mjs",
22
+ "require": "./dist/proxy.js"
23
+ },
19
24
  "./server": {
20
25
  "types": "./dist/server.d.ts",
21
26
  "import": "./dist/server.mjs",