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.
- package/LICENSE +21 -0
- package/README.md +452 -0
- package/dist/adapters/effect/index.d.mts +167 -0
- package/dist/adapters/effect/index.d.ts +167 -0
- package/dist/adapters/effect/index.js +221 -0
- package/dist/adapters/effect/index.js.map +1 -0
- package/dist/adapters/effect/index.mjs +221 -0
- package/dist/adapters/effect/index.mjs.map +1 -0
- package/dist/adapters/express/index.d.mts +74 -0
- package/dist/adapters/express/index.d.ts +74 -0
- package/dist/adapters/express/index.js +129 -0
- package/dist/adapters/express/index.js.map +1 -0
- package/dist/adapters/express/index.mjs +129 -0
- package/dist/adapters/express/index.mjs.map +1 -0
- package/dist/adapters/fastify/index.d.mts +111 -0
- package/dist/adapters/fastify/index.d.ts +111 -0
- package/dist/adapters/fastify/index.js +140 -0
- package/dist/adapters/fastify/index.js.map +1 -0
- package/dist/adapters/fastify/index.mjs +140 -0
- package/dist/adapters/fastify/index.mjs.map +1 -0
- package/dist/adapters/hono/index.d.mts +19 -0
- package/dist/adapters/hono/index.d.ts +19 -0
- package/dist/adapters/hono/index.js +45 -0
- package/dist/adapters/hono/index.js.map +1 -0
- package/dist/adapters/hono/index.mjs +45 -0
- package/dist/adapters/hono/index.mjs.map +1 -0
- package/dist/adapters/nestjs/index.d.mts +123 -0
- package/dist/adapters/nestjs/index.d.ts +123 -0
- package/dist/adapters/nestjs/index.js +117 -0
- package/dist/adapters/nestjs/index.js.map +1 -0
- package/dist/adapters/nestjs/index.mjs +117 -0
- package/dist/adapters/nestjs/index.mjs.map +1 -0
- package/dist/chunk-DM2KGIQX.mjs +320 -0
- package/dist/chunk-DM2KGIQX.mjs.map +1 -0
- package/dist/chunk-LQWCGHLJ.mjs +108 -0
- package/dist/chunk-LQWCGHLJ.mjs.map +1 -0
- package/dist/chunk-PMFPT3SI.js +108 -0
- package/dist/chunk-PMFPT3SI.js.map +1 -0
- package/dist/chunk-WUJPWM4T.js +320 -0
- package/dist/chunk-WUJPWM4T.js.map +1 -0
- package/dist/config-D4O7DXNT.d.mts +12 -0
- package/dist/config-ottUdc-K.d.ts +12 -0
- package/dist/core/index.d.mts +24 -0
- package/dist/core/index.d.ts +24 -0
- package/dist/core/index.js +41 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/index.mjs +41 -0
- package/dist/core/index.mjs.map +1 -0
- package/dist/index.d.mts +6 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +41 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +41 -0
- package/dist/index.mjs.map +1 -0
- package/dist/jwks-ChdyyS_L.d.mts +173 -0
- package/dist/jwks-ChdyyS_L.d.ts +173 -0
- package/dist/middleware-BDl6jUCu.d.mts +83 -0
- package/dist/middleware-CgFsjM20.d.ts +83 -0
- package/examples/basic.ts +52 -0
- package/examples/cloudflare-workers.ts +84 -0
- package/examples/custom-handlers.ts +85 -0
- package/examples/effect/http-server.ts +205 -0
- package/examples/email-allowlist.ts +50 -0
- package/examples/express/basic.ts +26 -0
- package/examples/fastify/basic.ts +24 -0
- package/examples/hono/basic.ts +26 -0
- package/examples/hono-router.ts +74 -0
- package/examples/nestjs/basic.ts +39 -0
- package/examples/skip-dev-mode.ts +89 -0
- package/package.json +178 -0
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { JWTPayload, createRemoteJWKSet } from 'jose';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Cloudflare Access JWT payload with additional claims
|
|
5
|
+
*/
|
|
6
|
+
interface CloudflareAccessPayload extends JWTPayload {
|
|
7
|
+
/** User's email address */
|
|
8
|
+
email?: string;
|
|
9
|
+
/** Token type */
|
|
10
|
+
type?: string;
|
|
11
|
+
/** Identity nonce for additional verification */
|
|
12
|
+
identity_nonce?: string;
|
|
13
|
+
/** User's country */
|
|
14
|
+
country?: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Cloudflare Access configuration
|
|
18
|
+
*/
|
|
19
|
+
interface CloudflareAccessConfig {
|
|
20
|
+
/** Exact Cloudflare Access issuer domain, e.g. https://myteam.cloudflareaccess.com */
|
|
21
|
+
teamDomain: string;
|
|
22
|
+
/** Exact Access application audience tag */
|
|
23
|
+
audTag: string;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* User information extracted from Cloudflare Access token
|
|
27
|
+
*/
|
|
28
|
+
interface CloudflareAccessUser {
|
|
29
|
+
email: string;
|
|
30
|
+
userId?: string;
|
|
31
|
+
country?: string;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Expected environment bindings for Cloudflare Access
|
|
35
|
+
*/
|
|
36
|
+
interface CloudflareAccessMiddlewareEnv {
|
|
37
|
+
CF_ACCESS_TEAM_DOMAIN?: string;
|
|
38
|
+
CF_ACCESS_AUD?: string;
|
|
39
|
+
ENVIRONMENT?: string;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Error codes for Cloudflare Access authentication
|
|
43
|
+
*/
|
|
44
|
+
declare const CloudflareAccessErrorCode: {
|
|
45
|
+
/** Missing or invalid authentication token */
|
|
46
|
+
readonly AUTH_REQUIRED: "AUTH_REQUIRED";
|
|
47
|
+
/** Token validation failed (expired, wrong signature, etc.) */
|
|
48
|
+
readonly INVALID_TOKEN: "INVALID_TOKEN";
|
|
49
|
+
/** User email not in allowlist */
|
|
50
|
+
readonly ACCESS_DENIED: "ACCESS_DENIED";
|
|
51
|
+
/** Invalid team domain configuration */
|
|
52
|
+
readonly INVALID_TEAM_DOMAIN: "INVALID_TEAM_DOMAIN";
|
|
53
|
+
/** Missing audience tag configuration */
|
|
54
|
+
readonly MISSING_AUDIENCE_TAG: "MISSING_AUDIENCE_TAG";
|
|
55
|
+
/** Missing environment configuration */
|
|
56
|
+
readonly MISSING_CONFIG: "MISSING_CONFIG";
|
|
57
|
+
/** JWKS fetch failed */
|
|
58
|
+
readonly JWKS_FETCH_ERROR: "JWKS_FETCH_ERROR";
|
|
59
|
+
};
|
|
60
|
+
type CloudflareAccessErrorCode = (typeof CloudflareAccessErrorCode)[keyof typeof CloudflareAccessErrorCode];
|
|
61
|
+
/**
|
|
62
|
+
* Rich authentication error with actionable information
|
|
63
|
+
*/
|
|
64
|
+
interface AuthError {
|
|
65
|
+
code: CloudflareAccessErrorCode;
|
|
66
|
+
message: string;
|
|
67
|
+
why: string;
|
|
68
|
+
fix: string;
|
|
69
|
+
context?: Record<string, unknown>;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Authentication result
|
|
73
|
+
*/
|
|
74
|
+
type AuthResult = {
|
|
75
|
+
success: true;
|
|
76
|
+
user: CloudflareAccessUser | null;
|
|
77
|
+
error?: never;
|
|
78
|
+
} | {
|
|
79
|
+
success: false;
|
|
80
|
+
user?: CloudflareAccessUser | null;
|
|
81
|
+
error: AuthError;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Base error class for Cloudflare Access authentication errors
|
|
86
|
+
*/
|
|
87
|
+
declare class CloudflareAccessError extends Error {
|
|
88
|
+
readonly code: CloudflareAccessErrorCode;
|
|
89
|
+
readonly context?: Record<string, unknown>;
|
|
90
|
+
readonly requestUrl?: string;
|
|
91
|
+
readonly timestamp: string;
|
|
92
|
+
constructor(code: CloudflareAccessErrorCode, message: string, options?: {
|
|
93
|
+
cause?: Error;
|
|
94
|
+
context?: Record<string, unknown>;
|
|
95
|
+
requestUrl?: string;
|
|
96
|
+
});
|
|
97
|
+
/**
|
|
98
|
+
* Get a user-friendly error message with fix instructions
|
|
99
|
+
*/
|
|
100
|
+
toJSON(): Record<string, unknown>;
|
|
101
|
+
/**
|
|
102
|
+
* Get fix instructions based on error code
|
|
103
|
+
*/
|
|
104
|
+
private getFixInstructions;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Error thrown when authentication is required but missing/invalid
|
|
108
|
+
*/
|
|
109
|
+
declare class AuthRequiredError extends CloudflareAccessError {
|
|
110
|
+
constructor(options?: {
|
|
111
|
+
requestUrl?: string;
|
|
112
|
+
context?: Record<string, unknown>;
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Error thrown when token validation fails
|
|
117
|
+
*/
|
|
118
|
+
declare class InvalidTokenError extends CloudflareAccessError {
|
|
119
|
+
readonly reason: string;
|
|
120
|
+
constructor(reason: string, options?: {
|
|
121
|
+
requestUrl?: string;
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Error thrown when user is not in email allowlist
|
|
126
|
+
*/
|
|
127
|
+
declare class AccessDeniedError extends CloudflareAccessError {
|
|
128
|
+
readonly email: string;
|
|
129
|
+
constructor(email: string, options?: {
|
|
130
|
+
requestUrl?: string;
|
|
131
|
+
allowedEmails?: string[];
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Error thrown when configuration is invalid or missing
|
|
136
|
+
*/
|
|
137
|
+
declare class ConfigurationError extends CloudflareAccessError {
|
|
138
|
+
constructor(message: string, options?: {
|
|
139
|
+
cause?: Error;
|
|
140
|
+
context?: Record<string, unknown>;
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Type guard to check if error is a CloudflareAccessError
|
|
145
|
+
*/
|
|
146
|
+
declare function isCloudflareAccessError(error: unknown): error is CloudflareAccessError;
|
|
147
|
+
/**
|
|
148
|
+
* Type guard for specific error types
|
|
149
|
+
*/
|
|
150
|
+
declare function isAuthRequiredError(error: unknown): error is AuthRequiredError;
|
|
151
|
+
declare function isInvalidTokenError(error: unknown): error is InvalidTokenError;
|
|
152
|
+
declare function isAccessDeniedError(error: unknown): error is AccessDeniedError;
|
|
153
|
+
declare function isConfigurationError(error: unknown): error is ConfigurationError;
|
|
154
|
+
/**
|
|
155
|
+
* Convert unknown error to CloudflareAccessError
|
|
156
|
+
*/
|
|
157
|
+
declare function toAuthError(error: unknown): CloudflareAccessError;
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Clear the JWKS cache. Useful for testing.
|
|
161
|
+
* @internal
|
|
162
|
+
*/
|
|
163
|
+
declare function __clearJwksCache(): void;
|
|
164
|
+
/**
|
|
165
|
+
* Get or create cached JWKS for a team domain
|
|
166
|
+
*/
|
|
167
|
+
declare function getRemoteJwks(teamDomain: string): ReturnType<typeof createRemoteJWKSet>;
|
|
168
|
+
/**
|
|
169
|
+
* Check if request is from local development
|
|
170
|
+
*/
|
|
171
|
+
declare function isLocalDevelopmentRequest(url: string): boolean;
|
|
172
|
+
|
|
173
|
+
export { AccessDeniedError as A, type CloudflareAccessConfig as C, InvalidTokenError as I, __clearJwksCache as _, AuthRequiredError as a, type AuthResult as b, CloudflareAccessError as c, CloudflareAccessErrorCode as d, type CloudflareAccessMiddlewareEnv as e, type CloudflareAccessPayload as f, type CloudflareAccessUser as g, ConfigurationError as h, isAccessDeniedError as i, isAuthRequiredError as j, isCloudflareAccessError as k, isConfigurationError as l, isInvalidTokenError as m, type AuthError as n, getRemoteJwks as o, isLocalDevelopmentRequest as p, toAuthError as t };
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { JWTPayload, createRemoteJWKSet } from 'jose';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Cloudflare Access JWT payload with additional claims
|
|
5
|
+
*/
|
|
6
|
+
interface CloudflareAccessPayload extends JWTPayload {
|
|
7
|
+
/** User's email address */
|
|
8
|
+
email?: string;
|
|
9
|
+
/** Token type */
|
|
10
|
+
type?: string;
|
|
11
|
+
/** Identity nonce for additional verification */
|
|
12
|
+
identity_nonce?: string;
|
|
13
|
+
/** User's country */
|
|
14
|
+
country?: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Cloudflare Access configuration
|
|
18
|
+
*/
|
|
19
|
+
interface CloudflareAccessConfig {
|
|
20
|
+
/** Exact Cloudflare Access issuer domain, e.g. https://myteam.cloudflareaccess.com */
|
|
21
|
+
teamDomain: string;
|
|
22
|
+
/** Exact Access application audience tag */
|
|
23
|
+
audTag: string;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* User information extracted from Cloudflare Access token
|
|
27
|
+
*/
|
|
28
|
+
interface CloudflareAccessUser {
|
|
29
|
+
email: string;
|
|
30
|
+
userId?: string;
|
|
31
|
+
country?: string;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Expected environment bindings for Cloudflare Access
|
|
35
|
+
*/
|
|
36
|
+
interface CloudflareAccessMiddlewareEnv {
|
|
37
|
+
CF_ACCESS_TEAM_DOMAIN?: string;
|
|
38
|
+
CF_ACCESS_AUD?: string;
|
|
39
|
+
ENVIRONMENT?: string;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Error codes for Cloudflare Access authentication
|
|
43
|
+
*/
|
|
44
|
+
declare const CloudflareAccessErrorCode: {
|
|
45
|
+
/** Missing or invalid authentication token */
|
|
46
|
+
readonly AUTH_REQUIRED: "AUTH_REQUIRED";
|
|
47
|
+
/** Token validation failed (expired, wrong signature, etc.) */
|
|
48
|
+
readonly INVALID_TOKEN: "INVALID_TOKEN";
|
|
49
|
+
/** User email not in allowlist */
|
|
50
|
+
readonly ACCESS_DENIED: "ACCESS_DENIED";
|
|
51
|
+
/** Invalid team domain configuration */
|
|
52
|
+
readonly INVALID_TEAM_DOMAIN: "INVALID_TEAM_DOMAIN";
|
|
53
|
+
/** Missing audience tag configuration */
|
|
54
|
+
readonly MISSING_AUDIENCE_TAG: "MISSING_AUDIENCE_TAG";
|
|
55
|
+
/** Missing environment configuration */
|
|
56
|
+
readonly MISSING_CONFIG: "MISSING_CONFIG";
|
|
57
|
+
/** JWKS fetch failed */
|
|
58
|
+
readonly JWKS_FETCH_ERROR: "JWKS_FETCH_ERROR";
|
|
59
|
+
};
|
|
60
|
+
type CloudflareAccessErrorCode = (typeof CloudflareAccessErrorCode)[keyof typeof CloudflareAccessErrorCode];
|
|
61
|
+
/**
|
|
62
|
+
* Rich authentication error with actionable information
|
|
63
|
+
*/
|
|
64
|
+
interface AuthError {
|
|
65
|
+
code: CloudflareAccessErrorCode;
|
|
66
|
+
message: string;
|
|
67
|
+
why: string;
|
|
68
|
+
fix: string;
|
|
69
|
+
context?: Record<string, unknown>;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Authentication result
|
|
73
|
+
*/
|
|
74
|
+
type AuthResult = {
|
|
75
|
+
success: true;
|
|
76
|
+
user: CloudflareAccessUser | null;
|
|
77
|
+
error?: never;
|
|
78
|
+
} | {
|
|
79
|
+
success: false;
|
|
80
|
+
user?: CloudflareAccessUser | null;
|
|
81
|
+
error: AuthError;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Base error class for Cloudflare Access authentication errors
|
|
86
|
+
*/
|
|
87
|
+
declare class CloudflareAccessError extends Error {
|
|
88
|
+
readonly code: CloudflareAccessErrorCode;
|
|
89
|
+
readonly context?: Record<string, unknown>;
|
|
90
|
+
readonly requestUrl?: string;
|
|
91
|
+
readonly timestamp: string;
|
|
92
|
+
constructor(code: CloudflareAccessErrorCode, message: string, options?: {
|
|
93
|
+
cause?: Error;
|
|
94
|
+
context?: Record<string, unknown>;
|
|
95
|
+
requestUrl?: string;
|
|
96
|
+
});
|
|
97
|
+
/**
|
|
98
|
+
* Get a user-friendly error message with fix instructions
|
|
99
|
+
*/
|
|
100
|
+
toJSON(): Record<string, unknown>;
|
|
101
|
+
/**
|
|
102
|
+
* Get fix instructions based on error code
|
|
103
|
+
*/
|
|
104
|
+
private getFixInstructions;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Error thrown when authentication is required but missing/invalid
|
|
108
|
+
*/
|
|
109
|
+
declare class AuthRequiredError extends CloudflareAccessError {
|
|
110
|
+
constructor(options?: {
|
|
111
|
+
requestUrl?: string;
|
|
112
|
+
context?: Record<string, unknown>;
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Error thrown when token validation fails
|
|
117
|
+
*/
|
|
118
|
+
declare class InvalidTokenError extends CloudflareAccessError {
|
|
119
|
+
readonly reason: string;
|
|
120
|
+
constructor(reason: string, options?: {
|
|
121
|
+
requestUrl?: string;
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Error thrown when user is not in email allowlist
|
|
126
|
+
*/
|
|
127
|
+
declare class AccessDeniedError extends CloudflareAccessError {
|
|
128
|
+
readonly email: string;
|
|
129
|
+
constructor(email: string, options?: {
|
|
130
|
+
requestUrl?: string;
|
|
131
|
+
allowedEmails?: string[];
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Error thrown when configuration is invalid or missing
|
|
136
|
+
*/
|
|
137
|
+
declare class ConfigurationError extends CloudflareAccessError {
|
|
138
|
+
constructor(message: string, options?: {
|
|
139
|
+
cause?: Error;
|
|
140
|
+
context?: Record<string, unknown>;
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Type guard to check if error is a CloudflareAccessError
|
|
145
|
+
*/
|
|
146
|
+
declare function isCloudflareAccessError(error: unknown): error is CloudflareAccessError;
|
|
147
|
+
/**
|
|
148
|
+
* Type guard for specific error types
|
|
149
|
+
*/
|
|
150
|
+
declare function isAuthRequiredError(error: unknown): error is AuthRequiredError;
|
|
151
|
+
declare function isInvalidTokenError(error: unknown): error is InvalidTokenError;
|
|
152
|
+
declare function isAccessDeniedError(error: unknown): error is AccessDeniedError;
|
|
153
|
+
declare function isConfigurationError(error: unknown): error is ConfigurationError;
|
|
154
|
+
/**
|
|
155
|
+
* Convert unknown error to CloudflareAccessError
|
|
156
|
+
*/
|
|
157
|
+
declare function toAuthError(error: unknown): CloudflareAccessError;
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Clear the JWKS cache. Useful for testing.
|
|
161
|
+
* @internal
|
|
162
|
+
*/
|
|
163
|
+
declare function __clearJwksCache(): void;
|
|
164
|
+
/**
|
|
165
|
+
* Get or create cached JWKS for a team domain
|
|
166
|
+
*/
|
|
167
|
+
declare function getRemoteJwks(teamDomain: string): ReturnType<typeof createRemoteJWKSet>;
|
|
168
|
+
/**
|
|
169
|
+
* Check if request is from local development
|
|
170
|
+
*/
|
|
171
|
+
declare function isLocalDevelopmentRequest(url: string): boolean;
|
|
172
|
+
|
|
173
|
+
export { AccessDeniedError as A, type CloudflareAccessConfig as C, InvalidTokenError as I, __clearJwksCache as _, AuthRequiredError as a, type AuthResult as b, CloudflareAccessError as c, CloudflareAccessErrorCode as d, type CloudflareAccessMiddlewareEnv as e, type CloudflareAccessPayload as f, type CloudflareAccessUser as g, ConfigurationError as h, isAccessDeniedError as i, isAuthRequiredError as j, isCloudflareAccessError as k, isConfigurationError as l, isInvalidTokenError as m, type AuthError as n, getRemoteJwks as o, isLocalDevelopmentRequest as p, toAuthError as t };
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import * as hono from 'hono';
|
|
2
|
+
import { Context, MiddlewareHandler } from 'hono';
|
|
3
|
+
import { C as CloudflareAccessConfig, g as CloudflareAccessUser, e as CloudflareAccessMiddlewareEnv } from './jwks-ChdyyS_L.mjs';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Extended Hono variables type
|
|
7
|
+
*/
|
|
8
|
+
interface CloudflareAccessVariables {
|
|
9
|
+
/** Authenticated user from Cloudflare Access */
|
|
10
|
+
user?: CloudflareAccessUser;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Helper type to create a typed Hono app with Cloudflare Access variables
|
|
14
|
+
*/
|
|
15
|
+
type CloudflareAccessHono = hono.Hono<{
|
|
16
|
+
Variables: CloudflareAccessVariables;
|
|
17
|
+
}>;
|
|
18
|
+
/**
|
|
19
|
+
* Configuration resolver type - can be static config or a function
|
|
20
|
+
*/
|
|
21
|
+
type CloudflareAccessConfigResolver$1 = CloudflareAccessConfig | ((c: Context) => CloudflareAccessConfig);
|
|
22
|
+
/**
|
|
23
|
+
* Options for creating Cloudflare Access authentication middleware for Hono
|
|
24
|
+
*/
|
|
25
|
+
interface CloudflareAccessAuthOptions {
|
|
26
|
+
/**
|
|
27
|
+
* Exact Cloudflare Access config or a resolver that reads it from bindings.
|
|
28
|
+
* Both `teamDomain` and `audTag` are required for secure validation.
|
|
29
|
+
*/
|
|
30
|
+
accessConfig: CloudflareAccessConfigResolver$1;
|
|
31
|
+
/** Optional email allowlist. Access policy should still be configured at Cloudflare. */
|
|
32
|
+
allowedEmails?: string[];
|
|
33
|
+
/** Custom unauthorized handler */
|
|
34
|
+
onUnauthorized?: (c: Context, reason: string) => Response | Promise<Response>;
|
|
35
|
+
/** Custom forbidden handler */
|
|
36
|
+
onForbidden?: (c: Context, email: string) => Response | Promise<Response>;
|
|
37
|
+
/** Paths to exclude from auth check */
|
|
38
|
+
excludePaths?: string[];
|
|
39
|
+
/** Whether to skip JWT validation outside production */
|
|
40
|
+
skipInDev?: boolean;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Configuration resolver type - can be static config or a function
|
|
45
|
+
*/
|
|
46
|
+
type CloudflareAccessConfigResolver = CloudflareAccessConfig | ((c: Context) => CloudflareAccessConfig);
|
|
47
|
+
/**
|
|
48
|
+
* Get Cloudflare Access configuration from Hono context bindings
|
|
49
|
+
*/
|
|
50
|
+
declare function getCloudflareAccessConfigFromBindings(c: Context<{
|
|
51
|
+
Bindings: CloudflareAccessMiddlewareEnv;
|
|
52
|
+
}>): CloudflareAccessConfig;
|
|
53
|
+
/**
|
|
54
|
+
* Get access configuration from resolver
|
|
55
|
+
*/
|
|
56
|
+
declare function resolveConfig(resolver: CloudflareAccessConfigResolver, c: Context): CloudflareAccessConfig;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Creates secure Cloudflare Access authentication middleware for Hono.
|
|
60
|
+
*
|
|
61
|
+
* @param options - Configuration options
|
|
62
|
+
* @returns Hono middleware handler
|
|
63
|
+
*
|
|
64
|
+
* @example
|
|
65
|
+
* ```typescript
|
|
66
|
+
* import { Hono } from 'hono';
|
|
67
|
+
* import { createCloudflareAccessAuth, getCloudflareAccessConfigFromBindings } from 'cloudflare-access/adapters/hono';
|
|
68
|
+
*
|
|
69
|
+
* const app = new Hono();
|
|
70
|
+
*
|
|
71
|
+
* app.use(createCloudflareAccessAuth({
|
|
72
|
+
* accessConfig: getCloudflareAccessConfigFromBindings,
|
|
73
|
+
* }));
|
|
74
|
+
*
|
|
75
|
+
* app.get('/protected', (c) => {
|
|
76
|
+
* const user = c.get('user');
|
|
77
|
+
* return c.json({ email: user?.email });
|
|
78
|
+
* });
|
|
79
|
+
* ```
|
|
80
|
+
*/
|
|
81
|
+
declare function createCloudflareAccessAuth(options: CloudflareAccessAuthOptions): MiddlewareHandler;
|
|
82
|
+
|
|
83
|
+
export { type CloudflareAccessAuthOptions as C, type CloudflareAccessConfigResolver$1 as a, type CloudflareAccessHono as b, type CloudflareAccessVariables as c, createCloudflareAccessAuth as d, getCloudflareAccessConfigFromBindings as g, resolveConfig as r };
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import * as hono from 'hono';
|
|
2
|
+
import { Context, MiddlewareHandler } from 'hono';
|
|
3
|
+
import { C as CloudflareAccessConfig, g as CloudflareAccessUser, e as CloudflareAccessMiddlewareEnv } from './jwks-ChdyyS_L.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Extended Hono variables type
|
|
7
|
+
*/
|
|
8
|
+
interface CloudflareAccessVariables {
|
|
9
|
+
/** Authenticated user from Cloudflare Access */
|
|
10
|
+
user?: CloudflareAccessUser;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Helper type to create a typed Hono app with Cloudflare Access variables
|
|
14
|
+
*/
|
|
15
|
+
type CloudflareAccessHono = hono.Hono<{
|
|
16
|
+
Variables: CloudflareAccessVariables;
|
|
17
|
+
}>;
|
|
18
|
+
/**
|
|
19
|
+
* Configuration resolver type - can be static config or a function
|
|
20
|
+
*/
|
|
21
|
+
type CloudflareAccessConfigResolver$1 = CloudflareAccessConfig | ((c: Context) => CloudflareAccessConfig);
|
|
22
|
+
/**
|
|
23
|
+
* Options for creating Cloudflare Access authentication middleware for Hono
|
|
24
|
+
*/
|
|
25
|
+
interface CloudflareAccessAuthOptions {
|
|
26
|
+
/**
|
|
27
|
+
* Exact Cloudflare Access config or a resolver that reads it from bindings.
|
|
28
|
+
* Both `teamDomain` and `audTag` are required for secure validation.
|
|
29
|
+
*/
|
|
30
|
+
accessConfig: CloudflareAccessConfigResolver$1;
|
|
31
|
+
/** Optional email allowlist. Access policy should still be configured at Cloudflare. */
|
|
32
|
+
allowedEmails?: string[];
|
|
33
|
+
/** Custom unauthorized handler */
|
|
34
|
+
onUnauthorized?: (c: Context, reason: string) => Response | Promise<Response>;
|
|
35
|
+
/** Custom forbidden handler */
|
|
36
|
+
onForbidden?: (c: Context, email: string) => Response | Promise<Response>;
|
|
37
|
+
/** Paths to exclude from auth check */
|
|
38
|
+
excludePaths?: string[];
|
|
39
|
+
/** Whether to skip JWT validation outside production */
|
|
40
|
+
skipInDev?: boolean;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Configuration resolver type - can be static config or a function
|
|
45
|
+
*/
|
|
46
|
+
type CloudflareAccessConfigResolver = CloudflareAccessConfig | ((c: Context) => CloudflareAccessConfig);
|
|
47
|
+
/**
|
|
48
|
+
* Get Cloudflare Access configuration from Hono context bindings
|
|
49
|
+
*/
|
|
50
|
+
declare function getCloudflareAccessConfigFromBindings(c: Context<{
|
|
51
|
+
Bindings: CloudflareAccessMiddlewareEnv;
|
|
52
|
+
}>): CloudflareAccessConfig;
|
|
53
|
+
/**
|
|
54
|
+
* Get access configuration from resolver
|
|
55
|
+
*/
|
|
56
|
+
declare function resolveConfig(resolver: CloudflareAccessConfigResolver, c: Context): CloudflareAccessConfig;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Creates secure Cloudflare Access authentication middleware for Hono.
|
|
60
|
+
*
|
|
61
|
+
* @param options - Configuration options
|
|
62
|
+
* @returns Hono middleware handler
|
|
63
|
+
*
|
|
64
|
+
* @example
|
|
65
|
+
* ```typescript
|
|
66
|
+
* import { Hono } from 'hono';
|
|
67
|
+
* import { createCloudflareAccessAuth, getCloudflareAccessConfigFromBindings } from 'cloudflare-access/adapters/hono';
|
|
68
|
+
*
|
|
69
|
+
* const app = new Hono();
|
|
70
|
+
*
|
|
71
|
+
* app.use(createCloudflareAccessAuth({
|
|
72
|
+
* accessConfig: getCloudflareAccessConfigFromBindings,
|
|
73
|
+
* }));
|
|
74
|
+
*
|
|
75
|
+
* app.get('/protected', (c) => {
|
|
76
|
+
* const user = c.get('user');
|
|
77
|
+
* return c.json({ email: user?.email });
|
|
78
|
+
* });
|
|
79
|
+
* ```
|
|
80
|
+
*/
|
|
81
|
+
declare function createCloudflareAccessAuth(options: CloudflareAccessAuthOptions): MiddlewareHandler;
|
|
82
|
+
|
|
83
|
+
export { type CloudflareAccessAuthOptions as C, type CloudflareAccessConfigResolver$1 as a, type CloudflareAccessHono as b, type CloudflareAccessVariables as c, createCloudflareAccessAuth as d, getCloudflareAccessConfigFromBindings as g, resolveConfig as r };
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Basic Example - Static Configuration
|
|
3
|
+
*
|
|
4
|
+
* This example shows the simplest usage of the middleware with
|
|
5
|
+
* hardcoded configuration. Not recommended for production use.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { Hono } from "hono";
|
|
9
|
+
import { createCloudflareAccessAuth, type CloudflareAccessHono } from "cloudflare-access/hono";
|
|
10
|
+
|
|
11
|
+
// Use the helper type for proper typing
|
|
12
|
+
const app: CloudflareAccessHono = new Hono();
|
|
13
|
+
|
|
14
|
+
// Apply middleware with static configuration
|
|
15
|
+
app.use(
|
|
16
|
+
createCloudflareAccessAuth({
|
|
17
|
+
accessConfig: {
|
|
18
|
+
teamDomain: "https://myteam.cloudflareaccess.com",
|
|
19
|
+
audTag: "my-application-audience-tag",
|
|
20
|
+
},
|
|
21
|
+
}),
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
// Protected route - user is properly typed
|
|
25
|
+
app.get("/api/protected", (c) => {
|
|
26
|
+
const user = c.get("user");
|
|
27
|
+
return c.json({
|
|
28
|
+
message: "Hello from protected route",
|
|
29
|
+
user: {
|
|
30
|
+
email: user?.email,
|
|
31
|
+
userId: user?.userId,
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// Health check (no auth required if excluded in config)
|
|
37
|
+
app.get("/health", (c) => {
|
|
38
|
+
return c.json({ status: "ok" });
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
export default app;
|
|
42
|
+
|
|
43
|
+
// For local testing with Bun
|
|
44
|
+
if (import.meta.main) {
|
|
45
|
+
console.log("Basic example - Static configuration");
|
|
46
|
+
console.log("This example requires valid Cloudflare Access credentials.");
|
|
47
|
+
console.log("");
|
|
48
|
+
console.log("To test with a real token:");
|
|
49
|
+
console.log(
|
|
50
|
+
' curl -H "CF-Access-JWT-Assertion: <your-token>" http://localhost:8787/api/protected',
|
|
51
|
+
);
|
|
52
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cloudflare Workers Example - Environment Bindings
|
|
3
|
+
*
|
|
4
|
+
* This is the recommended approach for production. Configuration is read
|
|
5
|
+
* from environment bindings set in wrangler.toml or Cloudflare dashboard.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { Hono } from "hono";
|
|
9
|
+
import {
|
|
10
|
+
createCloudflareAccessAuth,
|
|
11
|
+
getCloudflareAccessConfigFromBindings,
|
|
12
|
+
type CloudflareAccessUser,
|
|
13
|
+
} from "cloudflare-access/hono";
|
|
14
|
+
|
|
15
|
+
// Define your environment bindings type
|
|
16
|
+
interface Bindings {
|
|
17
|
+
// Cloudflare Access configuration
|
|
18
|
+
CF_ACCESS_TEAM_DOMAIN: string;
|
|
19
|
+
CF_ACCESS_AUD: string;
|
|
20
|
+
|
|
21
|
+
// Your other bindings
|
|
22
|
+
ENVIRONMENT: string;
|
|
23
|
+
DB?: D1Database;
|
|
24
|
+
CACHE?: KVNamespace;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Define variables type
|
|
28
|
+
interface Variables {
|
|
29
|
+
user?: CloudflareAccessUser;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const app = new Hono<{ Bindings: Bindings; Variables: Variables }>();
|
|
33
|
+
|
|
34
|
+
// Apply middleware using bindings
|
|
35
|
+
app.use(
|
|
36
|
+
createCloudflareAccessAuth({
|
|
37
|
+
accessConfig: getCloudflareAccessConfigFromBindings,
|
|
38
|
+
skipInDev: true, // Skip auth on localhost in non-prod environments
|
|
39
|
+
}),
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
// Protected API routes
|
|
43
|
+
app.get("/api/user", (c) => {
|
|
44
|
+
const user = c.get("user");
|
|
45
|
+
|
|
46
|
+
return c.json({
|
|
47
|
+
email: user?.email,
|
|
48
|
+
userId: user?.userId,
|
|
49
|
+
country: user?.country,
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
app.get("/api/admin", (c) => {
|
|
54
|
+
const user = c.get("user");
|
|
55
|
+
|
|
56
|
+
// You can access the authenticated user's email
|
|
57
|
+
console.log(`Admin access by: ${user?.email}`);
|
|
58
|
+
|
|
59
|
+
return c.json({
|
|
60
|
+
message: "Admin panel",
|
|
61
|
+
admin: user?.email,
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// Export for Cloudflare Workers
|
|
66
|
+
export default app;
|
|
67
|
+
|
|
68
|
+
/*
|
|
69
|
+
## wrangler.toml configuration:
|
|
70
|
+
|
|
71
|
+
name = "my-app"
|
|
72
|
+
main = "src/index.ts"
|
|
73
|
+
compatibility_date = "2024-01-01"
|
|
74
|
+
|
|
75
|
+
[vars]
|
|
76
|
+
ENVIRONMENT = "prod"
|
|
77
|
+
CF_ACCESS_TEAM_DOMAIN = "https://myteam.cloudflareaccess.com"
|
|
78
|
+
|
|
79
|
+
# Secrets (set via wrangler secret put)
|
|
80
|
+
# CF_ACCESS_AUD = "your-audience-tag"
|
|
81
|
+
|
|
82
|
+
## To set secrets:
|
|
83
|
+
# wrangler secret put CF_ACCESS_AUD
|
|
84
|
+
*/
|