cloudflare-access 1.0.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.
Files changed (70) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +452 -0
  3. package/dist/adapters/effect/index.d.mts +167 -0
  4. package/dist/adapters/effect/index.d.ts +167 -0
  5. package/dist/adapters/effect/index.js +221 -0
  6. package/dist/adapters/effect/index.js.map +1 -0
  7. package/dist/adapters/effect/index.mjs +221 -0
  8. package/dist/adapters/effect/index.mjs.map +1 -0
  9. package/dist/adapters/express/index.d.mts +74 -0
  10. package/dist/adapters/express/index.d.ts +74 -0
  11. package/dist/adapters/express/index.js +129 -0
  12. package/dist/adapters/express/index.js.map +1 -0
  13. package/dist/adapters/express/index.mjs +129 -0
  14. package/dist/adapters/express/index.mjs.map +1 -0
  15. package/dist/adapters/fastify/index.d.mts +111 -0
  16. package/dist/adapters/fastify/index.d.ts +111 -0
  17. package/dist/adapters/fastify/index.js +140 -0
  18. package/dist/adapters/fastify/index.js.map +1 -0
  19. package/dist/adapters/fastify/index.mjs +140 -0
  20. package/dist/adapters/fastify/index.mjs.map +1 -0
  21. package/dist/adapters/hono/index.d.mts +19 -0
  22. package/dist/adapters/hono/index.d.ts +19 -0
  23. package/dist/adapters/hono/index.js +45 -0
  24. package/dist/adapters/hono/index.js.map +1 -0
  25. package/dist/adapters/hono/index.mjs +45 -0
  26. package/dist/adapters/hono/index.mjs.map +1 -0
  27. package/dist/adapters/nestjs/index.d.mts +123 -0
  28. package/dist/adapters/nestjs/index.d.ts +123 -0
  29. package/dist/adapters/nestjs/index.js +117 -0
  30. package/dist/adapters/nestjs/index.js.map +1 -0
  31. package/dist/adapters/nestjs/index.mjs +117 -0
  32. package/dist/adapters/nestjs/index.mjs.map +1 -0
  33. package/dist/chunk-DM2KGIQX.mjs +320 -0
  34. package/dist/chunk-DM2KGIQX.mjs.map +1 -0
  35. package/dist/chunk-LQWCGHLJ.mjs +108 -0
  36. package/dist/chunk-LQWCGHLJ.mjs.map +1 -0
  37. package/dist/chunk-PMFPT3SI.js +108 -0
  38. package/dist/chunk-PMFPT3SI.js.map +1 -0
  39. package/dist/chunk-WUJPWM4T.js +320 -0
  40. package/dist/chunk-WUJPWM4T.js.map +1 -0
  41. package/dist/config-D4O7DXNT.d.mts +12 -0
  42. package/dist/config-ottUdc-K.d.ts +12 -0
  43. package/dist/core/index.d.mts +24 -0
  44. package/dist/core/index.d.ts +24 -0
  45. package/dist/core/index.js +41 -0
  46. package/dist/core/index.js.map +1 -0
  47. package/dist/core/index.mjs +41 -0
  48. package/dist/core/index.mjs.map +1 -0
  49. package/dist/index.d.mts +6 -0
  50. package/dist/index.d.ts +6 -0
  51. package/dist/index.js +41 -0
  52. package/dist/index.js.map +1 -0
  53. package/dist/index.mjs +41 -0
  54. package/dist/index.mjs.map +1 -0
  55. package/dist/jwks-ChdyyS_L.d.mts +173 -0
  56. package/dist/jwks-ChdyyS_L.d.ts +173 -0
  57. package/dist/middleware-BDl6jUCu.d.mts +83 -0
  58. package/dist/middleware-CgFsjM20.d.ts +83 -0
  59. package/examples/basic.ts +52 -0
  60. package/examples/cloudflare-workers.ts +84 -0
  61. package/examples/custom-handlers.ts +85 -0
  62. package/examples/effect/http-server.ts +205 -0
  63. package/examples/email-allowlist.ts +50 -0
  64. package/examples/express/basic.ts +26 -0
  65. package/examples/fastify/basic.ts +24 -0
  66. package/examples/hono/basic.ts +26 -0
  67. package/examples/hono-router.ts +74 -0
  68. package/examples/nestjs/basic.ts +39 -0
  69. package/examples/skip-dev-mode.ts +89 -0
  70. package/package.json +178 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/adapters/effect/errors.ts","../../../src/adapters/effect/context.ts","../../../src/adapters/effect/middleware.ts","../../../src/adapters/effect/utils.ts"],"sourcesContent":["import { Schema } from \"effect\";\nimport { HttpApiSchema } from \"@effect/platform\";\n\n/**\n * Unauthorized error schema for Effect Platform\n */\nexport class Unauthorized extends Schema.TaggedError<Unauthorized>()(\n \"Unauthorized\",\n {\n message: Schema.String,\n code: Schema.String,\n },\n HttpApiSchema.annotations({ status: 401 }),\n) {}\n\n/**\n * Forbidden error schema for Effect Platform\n */\nexport class Forbidden extends Schema.TaggedError<Forbidden>()(\n \"Forbidden\",\n {\n message: Schema.String,\n email: Schema.String,\n },\n HttpApiSchema.annotations({ status: 403 }),\n) {}\n","import { Context } from \"effect\";\nimport type { CloudflareAccessUser } from \"../../core/index\";\n\n/**\n * Context Tag for the authenticated user.\n * Use this to access the current user in your handlers.\n *\n * @example\n * ```typescript\n * import { Effect } from \"effect\";\n * import { CurrentUser } from \"cloudflare-access/effect\";\n *\n * const handler = Effect.gen(function* () {\n * const user = yield* CurrentUser;\n * console.log(`Hello ${user.email}`);\n * return user;\n * });\n * ```\n */\nexport class CurrentUser extends Context.Tag(\"cloudflare-access/CurrentUser\")<\n CurrentUser,\n CloudflareAccessUser\n>() {}\n","import { Effect, Layer, Redacted, Schema } from \"effect\";\nimport { HttpApiMiddleware, HttpApiSecurity, HttpServerRequest } from \"@effect/platform\";\nimport { validateCloudflareAccessToken, CloudflareAccessErrorCode } from \"../../core/index\";\nimport type { CloudflareAccessMiddlewareOptions } from \"./types\";\nimport { Unauthorized, Forbidden } from \"./errors\";\nimport { CurrentUser } from \"./context\";\n\n/**\n * Cloudflare Access authentication middleware class.\n *\n * Use this class directly in your API middleware definitions.\n *\n * @example\n * ```typescript\n * import { HttpApi, HttpApiGroup, HttpApiEndpoint } from \"@effect/platform\";\n * import { Schema, Effect, Layer } from \"effect\";\n * import {\n * CloudflareAccessAuth,\n * CurrentUser,\n * makeCloudflareAccessLive,\n * Unauthorized,\n * Forbidden\n * } from \"cloudflare-access/effect\";\n *\n * // Configure the middleware\n * const CloudflareAccessLive = makeCloudflareAccessLive({\n * accessConfig: {\n * teamDomain: \"https://yourteam.cloudflareaccess.com\",\n * audTag: \"your-audience-tag\",\n * },\n * allowedEmails: [\"admin@example.com\"],\n * skipInDev: true,\n * environment: \"dev\",\n * });\n *\n * // Define API with protected endpoints\n * const User = Schema.Struct({ email: Schema.String });\n *\n * const api = HttpApi.make(\"api\").add(\n * HttpApiGroup.make(\"users\").add(\n * HttpApiEndpoint.get(\"profile\", \"/profile\")\n * .addSuccess(User)\n * .middleware(CloudflareAccessAuth)\n * )\n * );\n *\n * // Implement handlers with access to CurrentUser\n * const UsersLive = HttpApiBuilder.group(api, \"users\", (handlers) =>\n * Effect.gen(function* () {\n * const user = yield* CurrentUser;\n * return handlers.handle(\"profile\", () =>\n * Effect.succeed({ email: user.email })\n * );\n * })\n * ).pipe(Layer.provide(CloudflareAccessLive));\n * ```\n */\nexport class CloudflareAccessAuth extends HttpApiMiddleware.Tag<CloudflareAccessAuth>()(\n \"CloudflareAccessAuth\",\n {\n provides: CurrentUser,\n failure: Schema.Union(Unauthorized, Forbidden),\n security: {\n bearer: HttpApiSecurity.bearer,\n },\n },\n) {}\n\n/**\n * Create a Layer that implements Cloudflare Access authentication.\n *\n * This layer validates the Cloudflare Access JWT token from the request\n * and provides the authenticated user via the CurrentUser context.\n *\n * @param options Configuration for Cloudflare Access\n * @returns Layer that provides authentication\n */\nexport const makeCloudflareAccessLive = (options: CloudflareAccessMiddlewareOptions) => {\n return Layer.succeed(\n CloudflareAccessAuth,\n CloudflareAccessAuth.of({\n bearer: (bearerToken) =>\n Effect.gen(function* () {\n const request = yield* HttpServerRequest.HttpServerRequest;\n const token = Redacted.value(bearerToken);\n\n // Wrap the async validation in an Effect\n const result = yield* Effect.promise(() =>\n validateCloudflareAccessToken(\n token,\n {\n accessConfig: options.accessConfig,\n allowedEmails: options.allowedEmails,\n skipInDev: options.skipInDev,\n environment: options.environment,\n },\n request.url,\n ),\n );\n\n // Handle validation result\n if (!result.success) {\n const errorCode = result.error?.code ?? CloudflareAccessErrorCode.INVALID_TOKEN;\n const errorMessage = result.error?.message ?? \"Authentication failed\";\n\n if (errorCode === CloudflareAccessErrorCode.AUTH_REQUIRED) {\n return yield* Effect.fail(\n new Unauthorized({ message: errorMessage, code: \"AUTH_REQUIRED\" }),\n );\n }\n\n if (errorCode === CloudflareAccessErrorCode.ACCESS_DENIED) {\n return yield* Effect.fail(\n new Forbidden({\n message: errorMessage,\n email: result.user?.email ?? \"unknown\",\n }),\n );\n }\n\n return yield* Effect.fail(new Unauthorized({ message: errorMessage, code: errorCode }));\n }\n\n // Dev mode skip case (result.user can be null when skipped)\n if (!result.user) {\n return {\n email: \"dev@example.com\",\n userId: \"dev-user\",\n country: \"dev\",\n };\n }\n\n return result.user;\n }),\n }),\n );\n};\n","import { Effect, Option } from \"effect\";\nimport { HttpServerRequest } from \"@effect/platform\";\nimport {\n validateCloudflareAccessToken,\n CloudflareAccessError,\n CloudflareAccessErrorCode,\n AuthRequiredError,\n InvalidTokenError,\n AccessDeniedError,\n isCloudflareAccessError,\n type CloudflareAccessUser,\n} from \"../../core/index\";\nimport type { CloudflareAccessMiddlewareOptions } from \"./types\";\n\n/**\n * Extract Cloudflare Access token from request headers.\n * Looks for CF-Access-JWT-Assertion header.\n *\n * @param request HTTP server request\n * @returns Option with token if present\n */\nexport const extractToken = (\n request: HttpServerRequest.HttpServerRequest,\n): Option.Option<string> => {\n const token = request.headers[\"cf-access-jwt-assertion\"];\n return Option.fromNullable(token);\n};\n\n/**\n * Authenticate using the CF-Access-JWT-Assertion header directly.\n * This bypasses the HttpApiSecurity bearer token pattern.\n *\n * @param request HTTP server request\n * @param options Authentication options\n * @returns Effect with authenticated user or error\n */\nexport const authenticateRequest = (\n request: HttpServerRequest.HttpServerRequest,\n options: CloudflareAccessMiddlewareOptions,\n): Effect.Effect<CloudflareAccessUser, CloudflareAccessError, never> => {\n return Effect.gen(function* () {\n const token = extractToken(request);\n\n const result = yield* Effect.tryPromise({\n try: async () => {\n return await validateCloudflareAccessToken(\n Option.getOrUndefined(token),\n {\n accessConfig: options.accessConfig,\n allowedEmails: options.allowedEmails,\n skipInDev: options.skipInDev,\n environment: options.environment,\n },\n request.url,\n );\n },\n catch: (error) => {\n if (isCloudflareAccessError(error)) {\n return error;\n }\n return new CloudflareAccessError(\n CloudflareAccessErrorCode.INVALID_TOKEN,\n error instanceof Error ? error.message : \"Unknown error\",\n { context: { requestUrl: request.url } },\n );\n },\n });\n\n // Handle case where catch block returned a CloudflareAccessError directly\n if (result instanceof CloudflareAccessError) {\n return yield* Effect.fail(result);\n }\n\n if (!result.success) {\n const errorCode = result.error?.code ?? CloudflareAccessErrorCode.INVALID_TOKEN;\n const errorMessage = result.error?.message ?? \"Authentication failed\";\n\n switch (errorCode) {\n case CloudflareAccessErrorCode.AUTH_REQUIRED:\n return yield* Effect.fail(\n new AuthRequiredError({\n context: { requestUrl: request.url, ...result.error.context },\n }),\n );\n case CloudflareAccessErrorCode.ACCESS_DENIED:\n return yield* Effect.fail(\n new AccessDeniedError(result.user?.email ?? \"unknown\", {\n requestUrl: request.url,\n allowedEmails: options.allowedEmails,\n }),\n );\n case CloudflareAccessErrorCode.INVALID_TOKEN:\n return yield* Effect.fail(\n new InvalidTokenError(errorMessage, {\n requestUrl: request.url,\n }),\n );\n default:\n return yield* Effect.fail(\n new CloudflareAccessError(errorCode, errorMessage, {\n context: { requestUrl: request.url, ...result.error.context },\n }),\n );\n }\n }\n\n // Dev mode skip case\n if (!result.user) {\n return {\n email: \"dev@example.com\",\n userId: \"dev-user\",\n country: \"dev\",\n };\n }\n\n return result.user;\n });\n};\n\n/**\n * Get user as Option (returns None on auth failure)\n */\nexport const getUser = (\n request: HttpServerRequest.HttpServerRequest,\n options: CloudflareAccessMiddlewareOptions,\n): Effect.Effect<Option.Option<CloudflareAccessUser>, never, never> => {\n return authenticateRequest(request, options).pipe(\n Effect.map((user) => Option.some(user)),\n Effect.catchAll(() => Effect.succeed(Option.none<CloudflareAccessUser>())),\n );\n};\n\n/**\n * Authenticate and return Either (for explicit error handling)\n */\nexport const authenticateEither = (\n request: HttpServerRequest.HttpServerRequest,\n options: CloudflareAccessMiddlewareOptions,\n) => {\n return Effect.either(authenticateRequest(request, options));\n};\n"],"mappings":"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","names":["Schema","Schema","Effect","Effect"]}
@@ -0,0 +1,74 @@
1
+ import { g as CloudflareAccessUser, C as CloudflareAccessConfig } from '../../jwks-ChdyyS_L.mjs';
2
+ export { A as AccessDeniedError, a as AuthRequiredError, c as CloudflareAccessError, d as CloudflareAccessErrorCode, e as CloudflareAccessMiddlewareEnv, f as CloudflareAccessPayload, h as ConfigurationError, I as InvalidTokenError, _ as __clearJwksCache, i as isAccessDeniedError, j as isAuthRequiredError, k as isCloudflareAccessError, l as isConfigurationError, m as isInvalidTokenError, t as toAuthError } from '../../jwks-ChdyyS_L.mjs';
3
+ import { Request, Response, NextFunction } from 'express';
4
+ import 'jose';
5
+
6
+ declare global {
7
+ namespace Express {
8
+ interface Request {
9
+ /** Authenticated user from Cloudflare Access */
10
+ user?: CloudflareAccessUser;
11
+ }
12
+ }
13
+ }
14
+ /**
15
+ * Options for creating Cloudflare Access authentication middleware for Express
16
+ */
17
+ interface CloudflareAccessAuthOptions {
18
+ /** Cloudflare Access configuration */
19
+ accessConfig: CloudflareAccessConfig;
20
+ /** Optional email allowlist. Access policy should still be configured at Cloudflare. */
21
+ allowedEmails?: string[];
22
+ /** Custom unauthorized handler */
23
+ onUnauthorized?: (req: Request, res: Response, reason: string) => void | Promise<void>;
24
+ /** Custom forbidden handler */
25
+ onForbidden?: (req: Request, res: Response, email: string) => void | Promise<void>;
26
+ /** Paths to exclude from auth check */
27
+ excludePaths?: string[];
28
+ /** Whether to skip JWT validation outside production */
29
+ skipInDev?: boolean;
30
+ /** Environment indicator */
31
+ environment?: string;
32
+ }
33
+
34
+ /**
35
+ * Generate unauthorized response
36
+ */
37
+ declare function unauthorizedResponse(res: Response, reason: string): void;
38
+ /**
39
+ * Generate auth required response
40
+ */
41
+ declare function authRequiredResponse(res: Response): void;
42
+ /**
43
+ * Generate forbidden response
44
+ */
45
+ declare function forbiddenResponse(res: Response): void;
46
+
47
+ /**
48
+ * Creates secure Cloudflare Access authentication middleware for Express.
49
+ *
50
+ * @param options - Configuration options
51
+ * @returns Express middleware handler
52
+ *
53
+ * @example
54
+ * ```typescript
55
+ * import express from 'express';
56
+ * import { cloudflareAccessAuth } from 'cloudflare-access/adapters/express';
57
+ *
58
+ * const app = express();
59
+ *
60
+ * app.use(cloudflareAccessAuth({
61
+ * accessConfig: {
62
+ * teamDomain: 'https://yourteam.cloudflareaccess.com',
63
+ * audTag: 'your-audience-tag',
64
+ * },
65
+ * }));
66
+ *
67
+ * app.get('/protected', (req, res) => {
68
+ * res.json({ email: req.user?.email });
69
+ * });
70
+ * ```
71
+ */
72
+ declare function cloudflareAccessAuth(options: CloudflareAccessAuthOptions): (req: Request, res: Response, next: NextFunction) => Promise<void>;
73
+
74
+ export { type CloudflareAccessAuthOptions, CloudflareAccessConfig, CloudflareAccessUser, authRequiredResponse, cloudflareAccessAuth, cloudflareAccessAuth as default, forbiddenResponse, unauthorizedResponse };
@@ -0,0 +1,74 @@
1
+ import { g as CloudflareAccessUser, C as CloudflareAccessConfig } from '../../jwks-ChdyyS_L.js';
2
+ export { A as AccessDeniedError, a as AuthRequiredError, c as CloudflareAccessError, d as CloudflareAccessErrorCode, e as CloudflareAccessMiddlewareEnv, f as CloudflareAccessPayload, h as ConfigurationError, I as InvalidTokenError, _ as __clearJwksCache, i as isAccessDeniedError, j as isAuthRequiredError, k as isCloudflareAccessError, l as isConfigurationError, m as isInvalidTokenError, t as toAuthError } from '../../jwks-ChdyyS_L.js';
3
+ import { Request, Response, NextFunction } from 'express';
4
+ import 'jose';
5
+
6
+ declare global {
7
+ namespace Express {
8
+ interface Request {
9
+ /** Authenticated user from Cloudflare Access */
10
+ user?: CloudflareAccessUser;
11
+ }
12
+ }
13
+ }
14
+ /**
15
+ * Options for creating Cloudflare Access authentication middleware for Express
16
+ */
17
+ interface CloudflareAccessAuthOptions {
18
+ /** Cloudflare Access configuration */
19
+ accessConfig: CloudflareAccessConfig;
20
+ /** Optional email allowlist. Access policy should still be configured at Cloudflare. */
21
+ allowedEmails?: string[];
22
+ /** Custom unauthorized handler */
23
+ onUnauthorized?: (req: Request, res: Response, reason: string) => void | Promise<void>;
24
+ /** Custom forbidden handler */
25
+ onForbidden?: (req: Request, res: Response, email: string) => void | Promise<void>;
26
+ /** Paths to exclude from auth check */
27
+ excludePaths?: string[];
28
+ /** Whether to skip JWT validation outside production */
29
+ skipInDev?: boolean;
30
+ /** Environment indicator */
31
+ environment?: string;
32
+ }
33
+
34
+ /**
35
+ * Generate unauthorized response
36
+ */
37
+ declare function unauthorizedResponse(res: Response, reason: string): void;
38
+ /**
39
+ * Generate auth required response
40
+ */
41
+ declare function authRequiredResponse(res: Response): void;
42
+ /**
43
+ * Generate forbidden response
44
+ */
45
+ declare function forbiddenResponse(res: Response): void;
46
+
47
+ /**
48
+ * Creates secure Cloudflare Access authentication middleware for Express.
49
+ *
50
+ * @param options - Configuration options
51
+ * @returns Express middleware handler
52
+ *
53
+ * @example
54
+ * ```typescript
55
+ * import express from 'express';
56
+ * import { cloudflareAccessAuth } from 'cloudflare-access/adapters/express';
57
+ *
58
+ * const app = express();
59
+ *
60
+ * app.use(cloudflareAccessAuth({
61
+ * accessConfig: {
62
+ * teamDomain: 'https://yourteam.cloudflareaccess.com',
63
+ * audTag: 'your-audience-tag',
64
+ * },
65
+ * }));
66
+ *
67
+ * app.get('/protected', (req, res) => {
68
+ * res.json({ email: req.user?.email });
69
+ * });
70
+ * ```
71
+ */
72
+ declare function cloudflareAccessAuth(options: CloudflareAccessAuthOptions): (req: Request, res: Response, next: NextFunction) => Promise<void>;
73
+
74
+ export { type CloudflareAccessAuthOptions, CloudflareAccessConfig, CloudflareAccessUser, authRequiredResponse, cloudflareAccessAuth, cloudflareAccessAuth as default, forbiddenResponse, unauthorizedResponse };
@@ -0,0 +1,129 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
2
+
3
+
4
+
5
+
6
+
7
+
8
+
9
+
10
+
11
+
12
+
13
+
14
+
15
+
16
+ var _chunkWUJPWM4Tjs = require('../../chunk-WUJPWM4T.js');
17
+
18
+ // src/adapters/express/responses.ts
19
+ function unauthorizedResponse(res, reason) {
20
+ res.status(401).json({
21
+ success: false,
22
+ error: {
23
+ code: "INVALID_TOKEN",
24
+ message: "Invalid authentication token",
25
+ why: reason,
26
+ fix: "Please sign in again via Cloudflare Access"
27
+ }
28
+ });
29
+ }
30
+ function authRequiredResponse(res) {
31
+ res.status(401).json({
32
+ success: false,
33
+ error: {
34
+ code: "AUTH_REQUIRED",
35
+ message: "Unauthorized",
36
+ why: "Authentication required via Cloudflare Access",
37
+ fix: "Sign in via Cloudflare Access"
38
+ }
39
+ });
40
+ }
41
+ function forbiddenResponse(res) {
42
+ res.status(403).json({
43
+ success: false,
44
+ error: {
45
+ code: "ACCESS_DENIED",
46
+ message: "Forbidden",
47
+ why: "Your email is not authorized to access this resource",
48
+ fix: "Contact an administrator if you need access"
49
+ }
50
+ });
51
+ }
52
+
53
+ // src/adapters/express/middleware.ts
54
+ function cloudflareAccessAuth(options) {
55
+ const allowedEmails = _nullishCoalesce(options.allowedEmails, () => ( null));
56
+ return async (req, res, next) => {
57
+ const path = req.path;
58
+ const method = req.method;
59
+ if (_optionalChain([options, 'access', _ => _.excludePaths, 'optionalAccess', _2 => _2.includes, 'call', _3 => _3(path)]) || method === "OPTIONS") {
60
+ next();
61
+ return;
62
+ }
63
+ const token = req.headers["cf-access-jwt-assertion"];
64
+ const protocol = req.headers["x-forwarded-proto"] || req.protocol;
65
+ const host = req.headers.host;
66
+ const url = `${protocol}://${host}${req.originalUrl}`;
67
+ const result = await _chunkWUJPWM4Tjs.validateCloudflareAccessToken.call(void 0,
68
+ token,
69
+ {
70
+ accessConfig: options.accessConfig,
71
+ allowedEmails: _nullishCoalesce(allowedEmails, () => ( void 0)),
72
+ skipInDev: options.skipInDev,
73
+ environment: options.environment
74
+ },
75
+ url
76
+ );
77
+ if (!result.success) {
78
+ if (_optionalChain([result, 'access', _4 => _4.error, 'optionalAccess', _5 => _5.code]) === "AUTH_REQUIRED") {
79
+ if (options.onUnauthorized) {
80
+ await options.onUnauthorized(req, res, result.error.why);
81
+ } else {
82
+ authRequiredResponse(res);
83
+ }
84
+ return;
85
+ }
86
+ if (_optionalChain([result, 'access', _6 => _6.error, 'optionalAccess', _7 => _7.code]) === "ACCESS_DENIED") {
87
+ const email = _nullishCoalesce(_optionalChain([result, 'access', _8 => _8.user, 'optionalAccess', _9 => _9.email]), () => ( "unknown"));
88
+ if (options.onForbidden) {
89
+ await options.onForbidden(req, res, email);
90
+ } else {
91
+ forbiddenResponse(res);
92
+ }
93
+ return;
94
+ }
95
+ if (options.onUnauthorized) {
96
+ await options.onUnauthorized(req, res, _nullishCoalesce(_optionalChain([result, 'access', _10 => _10.error, 'optionalAccess', _11 => _11.why]), () => ( "Unknown error")));
97
+ } else {
98
+ unauthorizedResponse(res, _nullishCoalesce(_optionalChain([result, 'access', _12 => _12.error, 'optionalAccess', _13 => _13.why]), () => ( "Unknown error")));
99
+ }
100
+ return;
101
+ }
102
+ if (result.user) {
103
+ req.user = result.user;
104
+ }
105
+ next();
106
+ };
107
+ }
108
+ var middleware_default = cloudflareAccessAuth;
109
+
110
+
111
+
112
+
113
+
114
+
115
+
116
+
117
+
118
+
119
+
120
+
121
+
122
+
123
+
124
+
125
+
126
+
127
+
128
+ exports.AccessDeniedError = _chunkWUJPWM4Tjs.AccessDeniedError; exports.AuthRequiredError = _chunkWUJPWM4Tjs.AuthRequiredError; exports.CloudflareAccessError = _chunkWUJPWM4Tjs.CloudflareAccessError; exports.CloudflareAccessErrorCode = _chunkWUJPWM4Tjs.CloudflareAccessErrorCode; exports.ConfigurationError = _chunkWUJPWM4Tjs.ConfigurationError; exports.InvalidTokenError = _chunkWUJPWM4Tjs.InvalidTokenError; exports.__clearJwksCache = _chunkWUJPWM4Tjs.__clearJwksCache; exports.authRequiredResponse = authRequiredResponse; exports.cloudflareAccessAuth = cloudflareAccessAuth; exports.default = middleware_default; exports.forbiddenResponse = forbiddenResponse; exports.isAccessDeniedError = _chunkWUJPWM4Tjs.isAccessDeniedError; exports.isAuthRequiredError = _chunkWUJPWM4Tjs.isAuthRequiredError; exports.isCloudflareAccessError = _chunkWUJPWM4Tjs.isCloudflareAccessError; exports.isConfigurationError = _chunkWUJPWM4Tjs.isConfigurationError; exports.isInvalidTokenError = _chunkWUJPWM4Tjs.isInvalidTokenError; exports.toAuthError = _chunkWUJPWM4Tjs.toAuthError; exports.unauthorizedResponse = unauthorizedResponse;
129
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/Users/v000281/personal/hono-cloudflare-access-middleware/dist/adapters/express/index.js","../../../src/adapters/express/responses.ts","../../../src/adapters/express/middleware.ts"],"names":[],"mappings":"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","file":"/Users/v000281/personal/hono-cloudflare-access-middleware/dist/adapters/express/index.js","sourcesContent":[null,"import type { Response } from \"express\";\n\n/**\n * Generate unauthorized response\n */\nexport function unauthorizedResponse(res: Response, reason: string): void {\n res.status(401).json({\n success: false,\n error: {\n code: \"INVALID_TOKEN\",\n message: \"Invalid authentication token\",\n why: reason,\n fix: \"Please sign in again via Cloudflare Access\",\n },\n });\n}\n\n/**\n * Generate auth required response\n */\nexport function authRequiredResponse(res: Response): void {\n res.status(401).json({\n success: false,\n error: {\n code: \"AUTH_REQUIRED\",\n message: \"Unauthorized\",\n why: \"Authentication required via Cloudflare Access\",\n fix: \"Sign in via Cloudflare Access\",\n },\n });\n}\n\n/**\n * Generate forbidden response\n */\nexport function forbiddenResponse(res: Response): void {\n res.status(403).json({\n success: false,\n error: {\n code: \"ACCESS_DENIED\",\n message: \"Forbidden\",\n why: \"Your email is not authorized to access this resource\",\n fix: \"Contact an administrator if you need access\",\n },\n });\n}\n","import type { Request, Response, NextFunction } from \"express\";\nimport { validateCloudflareAccessToken } from \"../../core\";\nimport type { CloudflareAccessAuthOptions } from \"./types\";\nimport { unauthorizedResponse, authRequiredResponse, forbiddenResponse } from \"./responses\";\n\n/**\n * Creates secure Cloudflare Access authentication middleware for Express.\n *\n * @param options - Configuration options\n * @returns Express middleware handler\n *\n * @example\n * ```typescript\n * import express from 'express';\n * import { cloudflareAccessAuth } from 'cloudflare-access/adapters/express';\n *\n * const app = express();\n *\n * app.use(cloudflareAccessAuth({\n * accessConfig: {\n * teamDomain: 'https://yourteam.cloudflareaccess.com',\n * audTag: 'your-audience-tag',\n * },\n * }));\n *\n * app.get('/protected', (req, res) => {\n * res.json({ email: req.user?.email });\n * });\n * ```\n */\nexport function cloudflareAccessAuth(\n options: CloudflareAccessAuthOptions,\n): (req: Request, res: Response, next: NextFunction) => Promise<void> {\n const allowedEmails = options.allowedEmails ?? null;\n\n return async (req: Request, res: Response, next: NextFunction): Promise<void> => {\n const path = req.path;\n const method = req.method;\n\n // Skip OPTIONS requests and excluded paths\n if (options.excludePaths?.includes(path) || method === \"OPTIONS\") {\n next();\n return;\n }\n\n const token = req.headers[\"cf-access-jwt-assertion\"] as string | undefined;\n const protocol = req.headers[\"x-forwarded-proto\"] || req.protocol;\n const host = req.headers.host;\n const url = `${protocol}://${host}${req.originalUrl}`;\n\n const result = await validateCloudflareAccessToken(\n token,\n {\n accessConfig: options.accessConfig,\n allowedEmails: allowedEmails ?? undefined,\n skipInDev: options.skipInDev,\n environment: options.environment,\n },\n url,\n );\n\n if (!result.success) {\n if (result.error?.code === \"AUTH_REQUIRED\") {\n if (options.onUnauthorized) {\n await options.onUnauthorized(req, res, result.error.why);\n } else {\n authRequiredResponse(res);\n }\n return;\n }\n\n if (result.error?.code === \"ACCESS_DENIED\") {\n const email = result.user?.email ?? \"unknown\";\n if (options.onForbidden) {\n await options.onForbidden(req, res, email);\n } else {\n forbiddenResponse(res);\n }\n return;\n }\n\n if (options.onUnauthorized) {\n await options.onUnauthorized(req, res, result.error?.why ?? \"Unknown error\");\n } else {\n unauthorizedResponse(res, result.error?.why ?? \"Unknown error\");\n }\n return;\n }\n\n // Set user in request\n if (result.user) {\n req.user = result.user;\n }\n\n next();\n };\n}\n\n// Also export as default for convenience\nexport default cloudflareAccessAuth;\n"]}
@@ -0,0 +1,129 @@
1
+ import {
2
+ AccessDeniedError,
3
+ AuthRequiredError,
4
+ CloudflareAccessError,
5
+ CloudflareAccessErrorCode,
6
+ ConfigurationError,
7
+ InvalidTokenError,
8
+ __clearJwksCache,
9
+ isAccessDeniedError,
10
+ isAuthRequiredError,
11
+ isCloudflareAccessError,
12
+ isConfigurationError,
13
+ isInvalidTokenError,
14
+ toAuthError,
15
+ validateCloudflareAccessToken
16
+ } from "../../chunk-DM2KGIQX.mjs";
17
+
18
+ // src/adapters/express/responses.ts
19
+ function unauthorizedResponse(res, reason) {
20
+ res.status(401).json({
21
+ success: false,
22
+ error: {
23
+ code: "INVALID_TOKEN",
24
+ message: "Invalid authentication token",
25
+ why: reason,
26
+ fix: "Please sign in again via Cloudflare Access"
27
+ }
28
+ });
29
+ }
30
+ function authRequiredResponse(res) {
31
+ res.status(401).json({
32
+ success: false,
33
+ error: {
34
+ code: "AUTH_REQUIRED",
35
+ message: "Unauthorized",
36
+ why: "Authentication required via Cloudflare Access",
37
+ fix: "Sign in via Cloudflare Access"
38
+ }
39
+ });
40
+ }
41
+ function forbiddenResponse(res) {
42
+ res.status(403).json({
43
+ success: false,
44
+ error: {
45
+ code: "ACCESS_DENIED",
46
+ message: "Forbidden",
47
+ why: "Your email is not authorized to access this resource",
48
+ fix: "Contact an administrator if you need access"
49
+ }
50
+ });
51
+ }
52
+
53
+ // src/adapters/express/middleware.ts
54
+ function cloudflareAccessAuth(options) {
55
+ const allowedEmails = options.allowedEmails ?? null;
56
+ return async (req, res, next) => {
57
+ const path = req.path;
58
+ const method = req.method;
59
+ if (options.excludePaths?.includes(path) || method === "OPTIONS") {
60
+ next();
61
+ return;
62
+ }
63
+ const token = req.headers["cf-access-jwt-assertion"];
64
+ const protocol = req.headers["x-forwarded-proto"] || req.protocol;
65
+ const host = req.headers.host;
66
+ const url = `${protocol}://${host}${req.originalUrl}`;
67
+ const result = await validateCloudflareAccessToken(
68
+ token,
69
+ {
70
+ accessConfig: options.accessConfig,
71
+ allowedEmails: allowedEmails ?? void 0,
72
+ skipInDev: options.skipInDev,
73
+ environment: options.environment
74
+ },
75
+ url
76
+ );
77
+ if (!result.success) {
78
+ if (result.error?.code === "AUTH_REQUIRED") {
79
+ if (options.onUnauthorized) {
80
+ await options.onUnauthorized(req, res, result.error.why);
81
+ } else {
82
+ authRequiredResponse(res);
83
+ }
84
+ return;
85
+ }
86
+ if (result.error?.code === "ACCESS_DENIED") {
87
+ const email = result.user?.email ?? "unknown";
88
+ if (options.onForbidden) {
89
+ await options.onForbidden(req, res, email);
90
+ } else {
91
+ forbiddenResponse(res);
92
+ }
93
+ return;
94
+ }
95
+ if (options.onUnauthorized) {
96
+ await options.onUnauthorized(req, res, result.error?.why ?? "Unknown error");
97
+ } else {
98
+ unauthorizedResponse(res, result.error?.why ?? "Unknown error");
99
+ }
100
+ return;
101
+ }
102
+ if (result.user) {
103
+ req.user = result.user;
104
+ }
105
+ next();
106
+ };
107
+ }
108
+ var middleware_default = cloudflareAccessAuth;
109
+ export {
110
+ AccessDeniedError,
111
+ AuthRequiredError,
112
+ CloudflareAccessError,
113
+ CloudflareAccessErrorCode,
114
+ ConfigurationError,
115
+ InvalidTokenError,
116
+ __clearJwksCache,
117
+ authRequiredResponse,
118
+ cloudflareAccessAuth,
119
+ middleware_default as default,
120
+ forbiddenResponse,
121
+ isAccessDeniedError,
122
+ isAuthRequiredError,
123
+ isCloudflareAccessError,
124
+ isConfigurationError,
125
+ isInvalidTokenError,
126
+ toAuthError,
127
+ unauthorizedResponse
128
+ };
129
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/adapters/express/responses.ts","../../../src/adapters/express/middleware.ts"],"sourcesContent":["import type { Response } from \"express\";\n\n/**\n * Generate unauthorized response\n */\nexport function unauthorizedResponse(res: Response, reason: string): void {\n res.status(401).json({\n success: false,\n error: {\n code: \"INVALID_TOKEN\",\n message: \"Invalid authentication token\",\n why: reason,\n fix: \"Please sign in again via Cloudflare Access\",\n },\n });\n}\n\n/**\n * Generate auth required response\n */\nexport function authRequiredResponse(res: Response): void {\n res.status(401).json({\n success: false,\n error: {\n code: \"AUTH_REQUIRED\",\n message: \"Unauthorized\",\n why: \"Authentication required via Cloudflare Access\",\n fix: \"Sign in via Cloudflare Access\",\n },\n });\n}\n\n/**\n * Generate forbidden response\n */\nexport function forbiddenResponse(res: Response): void {\n res.status(403).json({\n success: false,\n error: {\n code: \"ACCESS_DENIED\",\n message: \"Forbidden\",\n why: \"Your email is not authorized to access this resource\",\n fix: \"Contact an administrator if you need access\",\n },\n });\n}\n","import type { Request, Response, NextFunction } from \"express\";\nimport { validateCloudflareAccessToken } from \"../../core\";\nimport type { CloudflareAccessAuthOptions } from \"./types\";\nimport { unauthorizedResponse, authRequiredResponse, forbiddenResponse } from \"./responses\";\n\n/**\n * Creates secure Cloudflare Access authentication middleware for Express.\n *\n * @param options - Configuration options\n * @returns Express middleware handler\n *\n * @example\n * ```typescript\n * import express from 'express';\n * import { cloudflareAccessAuth } from 'cloudflare-access/adapters/express';\n *\n * const app = express();\n *\n * app.use(cloudflareAccessAuth({\n * accessConfig: {\n * teamDomain: 'https://yourteam.cloudflareaccess.com',\n * audTag: 'your-audience-tag',\n * },\n * }));\n *\n * app.get('/protected', (req, res) => {\n * res.json({ email: req.user?.email });\n * });\n * ```\n */\nexport function cloudflareAccessAuth(\n options: CloudflareAccessAuthOptions,\n): (req: Request, res: Response, next: NextFunction) => Promise<void> {\n const allowedEmails = options.allowedEmails ?? null;\n\n return async (req: Request, res: Response, next: NextFunction): Promise<void> => {\n const path = req.path;\n const method = req.method;\n\n // Skip OPTIONS requests and excluded paths\n if (options.excludePaths?.includes(path) || method === \"OPTIONS\") {\n next();\n return;\n }\n\n const token = req.headers[\"cf-access-jwt-assertion\"] as string | undefined;\n const protocol = req.headers[\"x-forwarded-proto\"] || req.protocol;\n const host = req.headers.host;\n const url = `${protocol}://${host}${req.originalUrl}`;\n\n const result = await validateCloudflareAccessToken(\n token,\n {\n accessConfig: options.accessConfig,\n allowedEmails: allowedEmails ?? undefined,\n skipInDev: options.skipInDev,\n environment: options.environment,\n },\n url,\n );\n\n if (!result.success) {\n if (result.error?.code === \"AUTH_REQUIRED\") {\n if (options.onUnauthorized) {\n await options.onUnauthorized(req, res, result.error.why);\n } else {\n authRequiredResponse(res);\n }\n return;\n }\n\n if (result.error?.code === \"ACCESS_DENIED\") {\n const email = result.user?.email ?? \"unknown\";\n if (options.onForbidden) {\n await options.onForbidden(req, res, email);\n } else {\n forbiddenResponse(res);\n }\n return;\n }\n\n if (options.onUnauthorized) {\n await options.onUnauthorized(req, res, result.error?.why ?? \"Unknown error\");\n } else {\n unauthorizedResponse(res, result.error?.why ?? \"Unknown error\");\n }\n return;\n }\n\n // Set user in request\n if (result.user) {\n req.user = result.user;\n }\n\n next();\n };\n}\n\n// Also export as default for convenience\nexport default cloudflareAccessAuth;\n"],"mappings":";;;;;;;;;;;;;;;;;;AAKO,SAAS,qBAAqB,KAAe,QAAsB;AACxE,MAAI,OAAO,GAAG,EAAE,KAAK;AAAA,IACnB,SAAS;AAAA,IACT,OAAO;AAAA,MACL,MAAM;AAAA,MACN,SAAS;AAAA,MACT,KAAK;AAAA,MACL,KAAK;AAAA,IACP;AAAA,EACF,CAAC;AACH;AAKO,SAAS,qBAAqB,KAAqB;AACxD,MAAI,OAAO,GAAG,EAAE,KAAK;AAAA,IACnB,SAAS;AAAA,IACT,OAAO;AAAA,MACL,MAAM;AAAA,MACN,SAAS;AAAA,MACT,KAAK;AAAA,MACL,KAAK;AAAA,IACP;AAAA,EACF,CAAC;AACH;AAKO,SAAS,kBAAkB,KAAqB;AACrD,MAAI,OAAO,GAAG,EAAE,KAAK;AAAA,IACnB,SAAS;AAAA,IACT,OAAO;AAAA,MACL,MAAM;AAAA,MACN,SAAS;AAAA,MACT,KAAK;AAAA,MACL,KAAK;AAAA,IACP;AAAA,EACF,CAAC;AACH;;;ACfO,SAAS,qBACd,SACoE;AACpE,QAAM,gBAAgB,QAAQ,iBAAiB;AAE/C,SAAO,OAAO,KAAc,KAAe,SAAsC;AAC/E,UAAM,OAAO,IAAI;AACjB,UAAM,SAAS,IAAI;AAGnB,QAAI,QAAQ,cAAc,SAAS,IAAI,KAAK,WAAW,WAAW;AAChE,WAAK;AACL;AAAA,IACF;AAEA,UAAM,QAAQ,IAAI,QAAQ,yBAAyB;AACnD,UAAM,WAAW,IAAI,QAAQ,mBAAmB,KAAK,IAAI;AACzD,UAAM,OAAO,IAAI,QAAQ;AACzB,UAAM,MAAM,GAAG,QAAQ,MAAM,IAAI,GAAG,IAAI,WAAW;AAEnD,UAAM,SAAS,MAAM;AAAA,MACnB;AAAA,MACA;AAAA,QACE,cAAc,QAAQ;AAAA,QACtB,eAAe,iBAAiB;AAAA,QAChC,WAAW,QAAQ;AAAA,QACnB,aAAa,QAAQ;AAAA,MACvB;AAAA,MACA;AAAA,IACF;AAEA,QAAI,CAAC,OAAO,SAAS;AACnB,UAAI,OAAO,OAAO,SAAS,iBAAiB;AAC1C,YAAI,QAAQ,gBAAgB;AAC1B,gBAAM,QAAQ,eAAe,KAAK,KAAK,OAAO,MAAM,GAAG;AAAA,QACzD,OAAO;AACL,+BAAqB,GAAG;AAAA,QAC1B;AACA;AAAA,MACF;AAEA,UAAI,OAAO,OAAO,SAAS,iBAAiB;AAC1C,cAAM,QAAQ,OAAO,MAAM,SAAS;AACpC,YAAI,QAAQ,aAAa;AACvB,gBAAM,QAAQ,YAAY,KAAK,KAAK,KAAK;AAAA,QAC3C,OAAO;AACL,4BAAkB,GAAG;AAAA,QACvB;AACA;AAAA,MACF;AAEA,UAAI,QAAQ,gBAAgB;AAC1B,cAAM,QAAQ,eAAe,KAAK,KAAK,OAAO,OAAO,OAAO,eAAe;AAAA,MAC7E,OAAO;AACL,6BAAqB,KAAK,OAAO,OAAO,OAAO,eAAe;AAAA,MAChE;AACA;AAAA,IACF;AAGA,QAAI,OAAO,MAAM;AACf,UAAI,OAAO,OAAO;AAAA,IACpB;AAEA,SAAK;AAAA,EACP;AACF;AAGA,IAAO,qBAAQ;","names":[]}
@@ -0,0 +1,111 @@
1
+ import { C as CloudflareAccessConfig, e as CloudflareAccessMiddlewareEnv, g as CloudflareAccessUser } from '../../jwks-ChdyyS_L.mjs';
2
+ export { A as AccessDeniedError, a as AuthRequiredError, c as CloudflareAccessError, d as CloudflareAccessErrorCode, f as CloudflareAccessPayload, h as ConfigurationError, I as InvalidTokenError, _ as __clearJwksCache, i as isAccessDeniedError, j as isAuthRequiredError, k as isCloudflareAccessError, l as isConfigurationError, m as isInvalidTokenError, t as toAuthError } from '../../jwks-ChdyyS_L.mjs';
3
+ import * as fastify from 'fastify';
4
+ import { FastifyReply, preHandlerHookHandler, FastifyPluginAsync } from 'fastify';
5
+ import 'jose';
6
+
7
+ /**
8
+ * Options for creating Cloudflare Access authentication for Fastify
9
+ */
10
+ interface CloudflareAccessAuthOptions {
11
+ /** Cloudflare Access configuration */
12
+ accessConfig: CloudflareAccessConfig;
13
+ /** Optional email allowlist. Access policy should still be configured at Cloudflare. */
14
+ allowedEmails?: string[];
15
+ /** Custom unauthorized handler */
16
+ onUnauthorized?: (request: fastify.FastifyRequest, reply: fastify.FastifyReply, reason: string) => void | Promise<void>;
17
+ /** Custom forbidden handler */
18
+ onForbidden?: (request: fastify.FastifyRequest, reply: fastify.FastifyReply, email: string) => void | Promise<void>;
19
+ /** Paths to exclude from auth check */
20
+ excludePaths?: string[];
21
+ /** Whether to skip JWT validation outside production */
22
+ skipInDev?: boolean;
23
+ /** Environment indicator */
24
+ environment?: string;
25
+ }
26
+ /**
27
+ * Get Cloudflare Access configuration from environment variables
28
+ */
29
+ declare function getCloudflareAccessConfigFromEnv(env: CloudflareAccessMiddlewareEnv): CloudflareAccessConfig;
30
+
31
+ /**
32
+ * Generate unauthorized response
33
+ */
34
+ declare function unauthorizedResponse(reply: FastifyReply, reason: string): void;
35
+ /**
36
+ * Generate auth required response
37
+ */
38
+ declare function authRequiredResponse(reply: FastifyReply): void;
39
+ /**
40
+ * Generate forbidden response
41
+ */
42
+ declare function forbiddenResponse(reply: FastifyReply): void;
43
+
44
+ declare module "fastify" {
45
+ interface FastifyRequest {
46
+ /** Authenticated user from Cloudflare Access */
47
+ user?: CloudflareAccessUser;
48
+ }
49
+ }
50
+ /**
51
+ * Creates a preHandler hook for Cloudflare Access authentication.
52
+ *
53
+ * @param options - Configuration options
54
+ * @returns Fastify preHandler hook
55
+ *
56
+ * @example
57
+ * ```typescript
58
+ * import fastify from 'fastify';
59
+ * import { cloudflareAccessPreHandler } from 'cloudflare-access/adapters/fastify';
60
+ *
61
+ * const app = fastify();
62
+ *
63
+ * app.addHook('preHandler', cloudflareAccessPreHandler({
64
+ * accessConfig: {
65
+ * teamDomain: 'https://yourteam.cloudflareaccess.com',
66
+ * audTag: 'your-audience-tag',
67
+ * },
68
+ * }));
69
+ *
70
+ * app.get('/protected', async (request, reply) => {
71
+ * return { email: request.user?.email };
72
+ * });
73
+ * ```
74
+ */
75
+ declare function cloudflareAccessPreHandler(options: CloudflareAccessAuthOptions): preHandlerHookHandler;
76
+
77
+ /**
78
+ * Creates a Fastify plugin for Cloudflare Access authentication.
79
+ *
80
+ * @param options - Configuration options
81
+ * @returns Fastify plugin
82
+ *
83
+ * @example
84
+ * ```typescript
85
+ * import fastify from 'fastify';
86
+ * import { cloudflareAccessPlugin } from 'cloudflare-access/adapters/fastify';
87
+ *
88
+ * const app = fastify();
89
+ *
90
+ * app.register(cloudflareAccessPlugin, {
91
+ * accessConfig: {
92
+ * teamDomain: 'https://yourteam.cloudflareaccess.com',
93
+ * audTag: 'your-audience-tag',
94
+ * },
95
+ * });
96
+ *
97
+ * app.get('/protected', async (request, reply) => {
98
+ * return { email: request.user?.email };
99
+ * });
100
+ * ```
101
+ */
102
+ declare const cloudflareAccessPlugin: FastifyPluginAsync<CloudflareAccessAuthOptions>;
103
+
104
+ declare module "fastify" {
105
+ interface FastifyRequest {
106
+ /** Authenticated user from Cloudflare Access */
107
+ user?: CloudflareAccessUser;
108
+ }
109
+ }
110
+
111
+ export { type CloudflareAccessAuthOptions, CloudflareAccessConfig, CloudflareAccessMiddlewareEnv, CloudflareAccessUser, authRequiredResponse, cloudflareAccessPlugin, cloudflareAccessPreHandler, cloudflareAccessPlugin as default, forbiddenResponse, getCloudflareAccessConfigFromEnv, unauthorizedResponse };