@githat/nextjs 0.3.0 → 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,26 +1,98 @@
1
- // src/middleware/index.ts
1
+ // src/lib/auth-handler.ts
2
2
  import { NextResponse } from "next/server";
3
- function authMiddleware(options = {}) {
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 = {}) {
4
26
  const {
5
27
  publicRoutes = ["/"],
6
28
  signInUrl = "/sign-in",
7
- tokenKey = "githat_access_token"
29
+ tokenCookie = "githat_access",
30
+ legacyTokenCookie = "githat_access_token",
31
+ injectHeaders = false,
32
+ secretKey
8
33
  } = options;
9
- return function middleware(request) {
10
- const { pathname } = request.nextUrl;
11
- if (publicRoutes.some((route) => pathname === route || pathname.startsWith(route + "/"))) {
12
- return NextResponse.next();
13
- }
14
- if (pathname.startsWith("/_next") || pathname.startsWith("/api") || pathname.includes(".")) {
15
- 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);
16
39
  }
17
- const token = request.cookies.get(tokenKey)?.value;
18
- 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) {
19
64
  const signInUrlObj = new URL(signInUrl, request.url);
20
65
  signInUrlObj.searchParams.set("redirect_url", pathname);
21
66
  return NextResponse.redirect(signInUrlObj);
22
67
  }
23
- 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));
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/middleware/index.ts
93
+ function authMiddleware(options = {}) {
94
+ return async function middleware(request) {
95
+ return handleAuthRequest(request, options);
24
96
  };
25
97
  }
26
98
  export {
@@ -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 tokenKey?: string;\n}\n\nexport function authMiddleware(options: AuthMiddlewareOptions = {}) {\n const {\n publicRoutes = ['/'],\n signInUrl = '/sign-in',\n tokenKey = 'githat_access_token',\n } = options;\n\n return function middleware(request: NextRequest) {\n const { pathname } = request.nextUrl;\n\n // Allow public routes\n if (publicRoutes.some(route => pathname === route || pathname.startsWith(route + '/'))) {\n return NextResponse.next();\n }\n\n // Allow static files and API routes\n if (pathname.startsWith('/_next') || pathname.startsWith('/api') || pathname.includes('.')) {\n return NextResponse.next();\n }\n\n // Check for token in cookies (set by client-side localStorage bridge)\n // Note: since tokens are in localStorage (not cookies), middleware can only\n // do a presence check. Real validation happens on API calls.\n const token = request.cookies.get(tokenKey)?.value;\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 return NextResponse.next();\n };\n}\n"],"mappings":";AAAA,SAAS,oBAAoB;AAStB,SAAS,eAAe,UAAiC,CAAC,GAAG;AAClE,QAAM;AAAA,IACJ,eAAe,CAAC,GAAG;AAAA,IACnB,YAAY;AAAA,IACZ,WAAW;AAAA,EACb,IAAI;AAEJ,SAAO,SAAS,WAAW,SAAsB;AAC/C,UAAM,EAAE,SAAS,IAAI,QAAQ;AAG7B,QAAI,aAAa,KAAK,WAAS,aAAa,SAAS,SAAS,WAAW,QAAQ,GAAG,CAAC,GAAG;AACtF,aAAO,aAAa,KAAK;AAAA,IAC3B;AAGA,QAAI,SAAS,WAAW,QAAQ,KAAK,SAAS,WAAW,MAAM,KAAK,SAAS,SAAS,GAAG,GAAG;AAC1F,aAAO,aAAa,KAAK;AAAA,IAC3B;AAKA,UAAM,QAAQ,QAAQ,QAAQ,IAAI,QAAQ,GAAG;AAC7C,QAAI,CAAC,OAAO;AACV,YAAM,eAAe,IAAI,IAAI,WAAW,QAAQ,GAAG;AACnD,mBAAa,aAAa,IAAI,gBAAgB,QAAQ;AACtD,aAAO,aAAa,SAAS,YAAY;AAAA,IAC3C;AAEA,WAAO,aAAa,KAAK;AAAA,EAC3B;AACF;","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":"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","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":"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","names":[]}