@guardcore/fastify 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/dist/index.cjs +254 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +46 -0
- package/dist/index.d.ts +46 -0
- package/dist/index.js +214 -0
- package/dist/index.js.map +1 -0
- package/package.json +55 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,254 @@
|
|
|
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/index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
BaseSecurityDecorator: () => import_core2.BaseSecurityDecorator,
|
|
34
|
+
BehaviorRule: () => import_core2.BehaviorRule,
|
|
35
|
+
FastifyGuardRequest: () => FastifyGuardRequest,
|
|
36
|
+
FastifyGuardResponse: () => FastifyGuardResponse,
|
|
37
|
+
FastifyResponseFactory: () => FastifyResponseFactory,
|
|
38
|
+
RouteConfig: () => import_core2.RouteConfig,
|
|
39
|
+
SecurityConfigSchema: () => import_core2.SecurityConfigSchema,
|
|
40
|
+
SecurityDecorator: () => import_core2.SecurityDecorator,
|
|
41
|
+
configureCors: () => configureCors,
|
|
42
|
+
defaultLogger: () => import_core2.defaultLogger,
|
|
43
|
+
guardPlugin: () => guardPlugin
|
|
44
|
+
});
|
|
45
|
+
module.exports = __toCommonJS(index_exports);
|
|
46
|
+
|
|
47
|
+
// src/plugin.ts
|
|
48
|
+
var import_core = require("@guardcore/core");
|
|
49
|
+
|
|
50
|
+
// src/adapters.ts
|
|
51
|
+
var FastifyGuardRequest = class {
|
|
52
|
+
constructor(req) {
|
|
53
|
+
this.req = req;
|
|
54
|
+
}
|
|
55
|
+
_state = {};
|
|
56
|
+
get urlPath() {
|
|
57
|
+
return this.req.url.split("?")[0];
|
|
58
|
+
}
|
|
59
|
+
get urlScheme() {
|
|
60
|
+
return this.req.protocol;
|
|
61
|
+
}
|
|
62
|
+
get urlFull() {
|
|
63
|
+
return `${this.req.protocol}://${this.req.hostname}${this.req.url}`;
|
|
64
|
+
}
|
|
65
|
+
urlReplaceScheme(scheme) {
|
|
66
|
+
return this.urlFull.replace(/^https?/, scheme);
|
|
67
|
+
}
|
|
68
|
+
get method() {
|
|
69
|
+
return this.req.method;
|
|
70
|
+
}
|
|
71
|
+
get clientHost() {
|
|
72
|
+
return this.req.ip ?? null;
|
|
73
|
+
}
|
|
74
|
+
get headers() {
|
|
75
|
+
return this.req.headers;
|
|
76
|
+
}
|
|
77
|
+
get queryParams() {
|
|
78
|
+
return this.req.query ?? {};
|
|
79
|
+
}
|
|
80
|
+
async body() {
|
|
81
|
+
const raw = this.req.body;
|
|
82
|
+
if (raw instanceof Buffer) return new Uint8Array(raw);
|
|
83
|
+
if (typeof raw === "string") return new TextEncoder().encode(raw);
|
|
84
|
+
if (raw !== void 0 && raw !== null) return new TextEncoder().encode(JSON.stringify(raw));
|
|
85
|
+
return new Uint8Array(0);
|
|
86
|
+
}
|
|
87
|
+
get state() {
|
|
88
|
+
return this._state;
|
|
89
|
+
}
|
|
90
|
+
get scope() {
|
|
91
|
+
return {};
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
var FastifyGuardResponse = class {
|
|
95
|
+
constructor(statusCode, content) {
|
|
96
|
+
this.statusCode = statusCode;
|
|
97
|
+
this._body = new TextEncoder().encode(content);
|
|
98
|
+
this._headers["content-type"] = "application/json";
|
|
99
|
+
}
|
|
100
|
+
_headers = {};
|
|
101
|
+
_body;
|
|
102
|
+
get headers() {
|
|
103
|
+
return this._headers;
|
|
104
|
+
}
|
|
105
|
+
setHeader(name, value) {
|
|
106
|
+
this._headers[name] = value;
|
|
107
|
+
}
|
|
108
|
+
get body() {
|
|
109
|
+
return this._body;
|
|
110
|
+
}
|
|
111
|
+
get bodyText() {
|
|
112
|
+
return this._body ? new TextDecoder().decode(this._body) : null;
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
var FastifyResponseFactory = class {
|
|
116
|
+
createResponse(content, statusCode) {
|
|
117
|
+
return new FastifyGuardResponse(statusCode, JSON.stringify({ detail: content }));
|
|
118
|
+
}
|
|
119
|
+
createRedirectResponse(url, statusCode) {
|
|
120
|
+
const resp = new FastifyGuardResponse(statusCode, "");
|
|
121
|
+
resp.setHeader("location", url);
|
|
122
|
+
return resp;
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
// src/plugin.ts
|
|
127
|
+
async function guardPlugin(fastify, options) {
|
|
128
|
+
const resolved = import_core.SecurityConfigSchema.parse(options.config);
|
|
129
|
+
const logger = resolved.logger ?? import_core.defaultLogger;
|
|
130
|
+
const responseFactory = new FastifyResponseFactory();
|
|
131
|
+
const components = await (0, import_core.initializeSecurityMiddleware)(
|
|
132
|
+
resolved,
|
|
133
|
+
logger,
|
|
134
|
+
responseFactory,
|
|
135
|
+
options.agentHandler,
|
|
136
|
+
options.geoIpHandler,
|
|
137
|
+
options.guardDecorator
|
|
138
|
+
);
|
|
139
|
+
logger.info("Guard security plugin initialized");
|
|
140
|
+
fastify.addHook("onRequest", async (request, reply) => {
|
|
141
|
+
const guardReq = new FastifyGuardRequest(request);
|
|
142
|
+
const passthrough = await components.bypassHandler.handlePassthrough(
|
|
143
|
+
guardReq,
|
|
144
|
+
async () => createPassthroughResponse()
|
|
145
|
+
);
|
|
146
|
+
if (passthrough) {
|
|
147
|
+
sendFastifyResponse(reply, passthrough);
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
const routeConfig = components.routeResolver.getRouteConfig(guardReq);
|
|
151
|
+
const bypass = await components.bypassHandler.handleSecurityBypass(
|
|
152
|
+
guardReq,
|
|
153
|
+
async () => createPassthroughResponse(),
|
|
154
|
+
routeConfig
|
|
155
|
+
);
|
|
156
|
+
if (bypass) {
|
|
157
|
+
sendFastifyResponse(reply, bypass);
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
const blockResponse = await components.pipeline.execute(guardReq);
|
|
161
|
+
if (blockResponse) {
|
|
162
|
+
sendFastifyResponse(reply, blockResponse);
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
if (routeConfig && routeConfig.behaviorRules.length > 0) {
|
|
166
|
+
const clientIp = guardReq.clientHost ?? "unknown";
|
|
167
|
+
await components.behavioralProcessor.processUsageRules(guardReq, clientIp, routeConfig);
|
|
168
|
+
}
|
|
169
|
+
request["_guardRequest"] = guardReq;
|
|
170
|
+
request["_guardRouteConfig"] = routeConfig;
|
|
171
|
+
request["_guardStartTime"] = performance.now();
|
|
172
|
+
});
|
|
173
|
+
fastify.addHook("onSend", async (request, reply, payload) => {
|
|
174
|
+
const guardReq = request["_guardRequest"];
|
|
175
|
+
const routeConfig = request["_guardRouteConfig"];
|
|
176
|
+
const startTime = request["_guardStartTime"];
|
|
177
|
+
if (!guardReq || startTime === void 0) return payload;
|
|
178
|
+
const responseTime = (performance.now() - startTime) / 1e3;
|
|
179
|
+
const bodyText = typeof payload === "string" ? payload : null;
|
|
180
|
+
const capturedResponse = {
|
|
181
|
+
statusCode: reply.statusCode,
|
|
182
|
+
headers: Object.fromEntries(
|
|
183
|
+
Object.entries(reply.getHeaders()).map(([k, v]) => [k, String(v)])
|
|
184
|
+
),
|
|
185
|
+
setHeader(name, value) {
|
|
186
|
+
reply.header(name, value);
|
|
187
|
+
},
|
|
188
|
+
body: bodyText ? new TextEncoder().encode(bodyText) : null,
|
|
189
|
+
bodyText
|
|
190
|
+
};
|
|
191
|
+
await components.errorResponseFactory.processResponse(
|
|
192
|
+
guardReq,
|
|
193
|
+
capturedResponse,
|
|
194
|
+
responseTime,
|
|
195
|
+
routeConfig ?? null,
|
|
196
|
+
routeConfig ? async (req, res, clientIp, rc) => {
|
|
197
|
+
await components.behavioralProcessor.processReturnRules(req, res, clientIp, rc);
|
|
198
|
+
} : void 0
|
|
199
|
+
);
|
|
200
|
+
return payload;
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
function sendFastifyResponse(reply, response) {
|
|
204
|
+
for (const [name, value] of Object.entries(response.headers)) {
|
|
205
|
+
reply.header(name, value);
|
|
206
|
+
}
|
|
207
|
+
if (response.headers["location"]) {
|
|
208
|
+
reply.redirect(response.headers["location"]);
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
reply.status(response.statusCode).send(response.bodyText ?? "");
|
|
212
|
+
}
|
|
213
|
+
function createPassthroughResponse() {
|
|
214
|
+
return { statusCode: 200, headers: {}, setHeader() {
|
|
215
|
+
}, body: null, bodyText: null };
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// src/cors.ts
|
|
219
|
+
async function configureCors(fastify, config) {
|
|
220
|
+
if (!config.enableCors) return;
|
|
221
|
+
try {
|
|
222
|
+
const fastifyCors = await import("@fastify/cors");
|
|
223
|
+
await fastify.register(fastifyCors.default ?? fastifyCors, {
|
|
224
|
+
origin: config.corsAllowOrigins,
|
|
225
|
+
methods: config.corsAllowMethods,
|
|
226
|
+
allowedHeaders: config.corsAllowHeaders,
|
|
227
|
+
credentials: config.corsAllowCredentials,
|
|
228
|
+
exposedHeaders: config.corsExposeHeaders,
|
|
229
|
+
maxAge: config.corsMaxAge
|
|
230
|
+
});
|
|
231
|
+
} catch {
|
|
232
|
+
throw new Error(
|
|
233
|
+
'@guardcore/fastify: CORS is enabled but "@fastify/cors" is not installed. Run: pnpm add @fastify/cors'
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// src/index.ts
|
|
239
|
+
var import_core2 = require("@guardcore/core");
|
|
240
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
241
|
+
0 && (module.exports = {
|
|
242
|
+
BaseSecurityDecorator,
|
|
243
|
+
BehaviorRule,
|
|
244
|
+
FastifyGuardRequest,
|
|
245
|
+
FastifyGuardResponse,
|
|
246
|
+
FastifyResponseFactory,
|
|
247
|
+
RouteConfig,
|
|
248
|
+
SecurityConfigSchema,
|
|
249
|
+
SecurityDecorator,
|
|
250
|
+
configureCors,
|
|
251
|
+
defaultLogger,
|
|
252
|
+
guardPlugin
|
|
253
|
+
});
|
|
254
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/plugin.ts","../src/adapters.ts","../src/cors.ts"],"sourcesContent":["export { guardPlugin } from './plugin.js';\nexport type { GuardPluginOptions } from './plugin.js';\nexport { configureCors } from './cors.js';\nexport { FastifyGuardRequest, FastifyGuardResponse, FastifyResponseFactory } from './adapters.js';\n\nexport {\n SecurityConfigSchema,\n BaseSecurityDecorator,\n SecurityDecorator,\n RouteConfig,\n BehaviorRule,\n defaultLogger,\n} from '@guardcore/core';\n\nexport type {\n SecurityConfig,\n ResolvedSecurityConfig,\n GuardRequest,\n GuardResponse,\n Logger,\n} from '@guardcore/core';\n","import type { FastifyInstance, FastifyReply } from 'fastify';\nimport type {\n SecurityConfig,\n GuardRequest,\n GuardResponse,\n Logger,\n AgentHandlerProtocol,\n GeoIPHandler,\n SecurityMiddlewareComponents,\n RouteConfig,\n} from '@guardcore/core';\nimport { SecurityConfigSchema, defaultLogger, initializeSecurityMiddleware } from '@guardcore/core';\nimport { FastifyGuardRequest, FastifyResponseFactory } from './adapters.js';\n\nexport interface GuardPluginOptions {\n config: SecurityConfig;\n agentHandler?: AgentHandlerProtocol;\n geoIpHandler?: GeoIPHandler;\n guardDecorator?: unknown;\n}\n\nexport async function guardPlugin(fastify: FastifyInstance, options: GuardPluginOptions): Promise<void> {\n const resolved = SecurityConfigSchema.parse(options.config);\n const logger: Logger = resolved.logger ?? defaultLogger;\n const responseFactory = new FastifyResponseFactory();\n\n const components: SecurityMiddlewareComponents = await initializeSecurityMiddleware(\n resolved, logger, responseFactory,\n options.agentHandler, options.geoIpHandler, options.guardDecorator,\n );\n\n logger.info('Guard security plugin initialized');\n\n fastify.addHook('onRequest', async (request, reply) => {\n const guardReq = new FastifyGuardRequest(request);\n\n const passthrough = await components.bypassHandler.handlePassthrough(\n guardReq, async () => createPassthroughResponse(),\n );\n if (passthrough) {\n sendFastifyResponse(reply, passthrough);\n return;\n }\n\n const routeConfig = components.routeResolver.getRouteConfig(guardReq);\n\n const bypass = await components.bypassHandler.handleSecurityBypass(\n guardReq, async () => createPassthroughResponse(), routeConfig,\n );\n if (bypass) {\n sendFastifyResponse(reply, bypass);\n return;\n }\n\n const blockResponse = await components.pipeline.execute(guardReq);\n if (blockResponse) {\n sendFastifyResponse(reply, blockResponse);\n return;\n }\n\n if (routeConfig && routeConfig.behaviorRules.length > 0) {\n const clientIp = guardReq.clientHost ?? 'unknown';\n await components.behavioralProcessor.processUsageRules(guardReq, clientIp, routeConfig);\n }\n\n (request as unknown as Record<string, unknown>)['_guardRequest'] = guardReq;\n (request as unknown as Record<string, unknown>)['_guardRouteConfig'] = routeConfig;\n (request as unknown as Record<string, unknown>)['_guardStartTime'] = performance.now();\n });\n\n fastify.addHook('onSend', async (request, reply, payload) => {\n const guardReq = (request as unknown as Record<string, unknown>)['_guardRequest'] as FastifyGuardRequest | undefined;\n const routeConfig = (request as unknown as Record<string, unknown>)['_guardRouteConfig'] as RouteConfig | null | undefined;\n const startTime = (request as unknown as Record<string, unknown>)['_guardStartTime'] as number | undefined;\n\n if (!guardReq || startTime === undefined) return payload;\n\n const responseTime = (performance.now() - startTime) / 1000;\n const bodyText = typeof payload === 'string' ? payload : null;\n const capturedResponse: GuardResponse = {\n statusCode: reply.statusCode,\n headers: Object.fromEntries(\n Object.entries(reply.getHeaders()).map(([k, v]) => [k, String(v)]),\n ),\n setHeader(name: string, value: string) { reply.header(name, value); },\n body: bodyText ? new TextEncoder().encode(bodyText) : null,\n bodyText,\n };\n\n await components.errorResponseFactory.processResponse(\n guardReq, capturedResponse, responseTime, routeConfig ?? null,\n routeConfig ? async (req: GuardRequest, res: GuardResponse, clientIp: string, rc: RouteConfig) => {\n await components.behavioralProcessor.processReturnRules(req, res, clientIp, rc);\n } : undefined,\n );\n\n return payload;\n });\n}\n\nfunction sendFastifyResponse(reply: FastifyReply, response: GuardResponse): void {\n for (const [name, value] of Object.entries(response.headers)) {\n reply.header(name, value);\n }\n\n if (response.headers['location']) {\n reply.redirect(response.headers['location']);\n return;\n }\n\n reply.status(response.statusCode).send(response.bodyText ?? '');\n}\n\nfunction createPassthroughResponse(): GuardResponse {\n return { statusCode: 200, headers: {}, setHeader() {}, body: null, bodyText: null };\n}\n","import type { FastifyRequest } from 'fastify';\nimport type { GuardRequest, GuardRequestState, GuardResponse, GuardResponseFactory } from '@guardcore/core';\n\nexport class FastifyGuardRequest implements GuardRequest {\n private _state: GuardRequestState = {};\n\n constructor(private readonly req: FastifyRequest) {}\n\n get urlPath(): string { return this.req.url.split('?')[0]; }\n get urlScheme(): string { return this.req.protocol; }\n get urlFull(): string { return `${this.req.protocol}://${this.req.hostname}${this.req.url}`; }\n urlReplaceScheme(scheme: string): string { return this.urlFull.replace(/^https?/, scheme); }\n get method(): string { return this.req.method; }\n get clientHost(): string | null { return this.req.ip ?? null; }\n get headers(): Readonly<Record<string, string>> { return this.req.headers as Record<string, string>; }\n get queryParams(): Readonly<Record<string, string>> { return (this.req.query ?? {}) as Record<string, string>; }\n async body(): Promise<Uint8Array> {\n const raw = this.req.body;\n if (raw instanceof Buffer) return new Uint8Array(raw);\n if (typeof raw === 'string') return new TextEncoder().encode(raw);\n if (raw !== undefined && raw !== null) return new TextEncoder().encode(JSON.stringify(raw));\n return new Uint8Array(0);\n }\n get state(): GuardRequestState { return this._state; }\n get scope(): Readonly<Record<string, unknown>> { return {}; }\n}\n\nexport class FastifyGuardResponse implements GuardResponse {\n private _headers: Record<string, string> = {};\n private _body: Uint8Array | null;\n\n constructor(readonly statusCode: number, content: string) {\n this._body = new TextEncoder().encode(content);\n this._headers['content-type'] = 'application/json';\n }\n\n get headers(): Record<string, string> { return this._headers; }\n setHeader(name: string, value: string): void { this._headers[name] = value; }\n get body(): Uint8Array | null { return this._body; }\n get bodyText(): string | null { return this._body ? new TextDecoder().decode(this._body) : null; }\n}\n\nexport class FastifyResponseFactory implements GuardResponseFactory {\n createResponse(content: string, statusCode: number): GuardResponse {\n return new FastifyGuardResponse(statusCode, JSON.stringify({ detail: content }));\n }\n\n createRedirectResponse(url: string, statusCode: number): GuardResponse {\n const resp = new FastifyGuardResponse(statusCode, '');\n resp.setHeader('location', url);\n return resp;\n }\n}\n","import type { FastifyInstance } from 'fastify';\nimport type { ResolvedSecurityConfig } from '@guardcore/core';\n\nexport async function configureCors(fastify: FastifyInstance, config: ResolvedSecurityConfig): Promise<void> {\n if (!config.enableCors) return;\n\n try {\n const fastifyCors = await import('@fastify/cors');\n await fastify.register(fastifyCors.default ?? fastifyCors, {\n origin: config.corsAllowOrigins,\n methods: config.corsAllowMethods,\n allowedHeaders: config.corsAllowHeaders,\n credentials: config.corsAllowCredentials,\n exposedHeaders: config.corsExposeHeaders,\n maxAge: config.corsMaxAge,\n });\n } catch {\n throw new Error(\n '@guardcore/fastify: CORS is enabled but \"@fastify/cors\" is not installed. ' +\n 'Run: pnpm add @fastify/cors',\n );\n }\n}\n"],"mappings":"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","names":["import_core"]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { FastifyInstance, FastifyRequest } from 'fastify';
|
|
2
|
+
import { SecurityConfig, AgentHandlerProtocol, GeoIPHandler, ResolvedSecurityConfig, GuardRequest, GuardRequestState, GuardResponse, GuardResponseFactory } from '@guardcore/core';
|
|
3
|
+
export { BaseSecurityDecorator, BehaviorRule, GuardRequest, GuardResponse, Logger, ResolvedSecurityConfig, RouteConfig, SecurityConfig, SecurityConfigSchema, SecurityDecorator, defaultLogger } from '@guardcore/core';
|
|
4
|
+
|
|
5
|
+
interface GuardPluginOptions {
|
|
6
|
+
config: SecurityConfig;
|
|
7
|
+
agentHandler?: AgentHandlerProtocol;
|
|
8
|
+
geoIpHandler?: GeoIPHandler;
|
|
9
|
+
guardDecorator?: unknown;
|
|
10
|
+
}
|
|
11
|
+
declare function guardPlugin(fastify: FastifyInstance, options: GuardPluginOptions): Promise<void>;
|
|
12
|
+
|
|
13
|
+
declare function configureCors(fastify: FastifyInstance, config: ResolvedSecurityConfig): Promise<void>;
|
|
14
|
+
|
|
15
|
+
declare class FastifyGuardRequest implements GuardRequest {
|
|
16
|
+
private readonly req;
|
|
17
|
+
private _state;
|
|
18
|
+
constructor(req: FastifyRequest);
|
|
19
|
+
get urlPath(): string;
|
|
20
|
+
get urlScheme(): string;
|
|
21
|
+
get urlFull(): string;
|
|
22
|
+
urlReplaceScheme(scheme: string): string;
|
|
23
|
+
get method(): string;
|
|
24
|
+
get clientHost(): string | null;
|
|
25
|
+
get headers(): Readonly<Record<string, string>>;
|
|
26
|
+
get queryParams(): Readonly<Record<string, string>>;
|
|
27
|
+
body(): Promise<Uint8Array>;
|
|
28
|
+
get state(): GuardRequestState;
|
|
29
|
+
get scope(): Readonly<Record<string, unknown>>;
|
|
30
|
+
}
|
|
31
|
+
declare class FastifyGuardResponse implements GuardResponse {
|
|
32
|
+
readonly statusCode: number;
|
|
33
|
+
private _headers;
|
|
34
|
+
private _body;
|
|
35
|
+
constructor(statusCode: number, content: string);
|
|
36
|
+
get headers(): Record<string, string>;
|
|
37
|
+
setHeader(name: string, value: string): void;
|
|
38
|
+
get body(): Uint8Array | null;
|
|
39
|
+
get bodyText(): string | null;
|
|
40
|
+
}
|
|
41
|
+
declare class FastifyResponseFactory implements GuardResponseFactory {
|
|
42
|
+
createResponse(content: string, statusCode: number): GuardResponse;
|
|
43
|
+
createRedirectResponse(url: string, statusCode: number): GuardResponse;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export { FastifyGuardRequest, FastifyGuardResponse, FastifyResponseFactory, type GuardPluginOptions, configureCors, guardPlugin };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { FastifyInstance, FastifyRequest } from 'fastify';
|
|
2
|
+
import { SecurityConfig, AgentHandlerProtocol, GeoIPHandler, ResolvedSecurityConfig, GuardRequest, GuardRequestState, GuardResponse, GuardResponseFactory } from '@guardcore/core';
|
|
3
|
+
export { BaseSecurityDecorator, BehaviorRule, GuardRequest, GuardResponse, Logger, ResolvedSecurityConfig, RouteConfig, SecurityConfig, SecurityConfigSchema, SecurityDecorator, defaultLogger } from '@guardcore/core';
|
|
4
|
+
|
|
5
|
+
interface GuardPluginOptions {
|
|
6
|
+
config: SecurityConfig;
|
|
7
|
+
agentHandler?: AgentHandlerProtocol;
|
|
8
|
+
geoIpHandler?: GeoIPHandler;
|
|
9
|
+
guardDecorator?: unknown;
|
|
10
|
+
}
|
|
11
|
+
declare function guardPlugin(fastify: FastifyInstance, options: GuardPluginOptions): Promise<void>;
|
|
12
|
+
|
|
13
|
+
declare function configureCors(fastify: FastifyInstance, config: ResolvedSecurityConfig): Promise<void>;
|
|
14
|
+
|
|
15
|
+
declare class FastifyGuardRequest implements GuardRequest {
|
|
16
|
+
private readonly req;
|
|
17
|
+
private _state;
|
|
18
|
+
constructor(req: FastifyRequest);
|
|
19
|
+
get urlPath(): string;
|
|
20
|
+
get urlScheme(): string;
|
|
21
|
+
get urlFull(): string;
|
|
22
|
+
urlReplaceScheme(scheme: string): string;
|
|
23
|
+
get method(): string;
|
|
24
|
+
get clientHost(): string | null;
|
|
25
|
+
get headers(): Readonly<Record<string, string>>;
|
|
26
|
+
get queryParams(): Readonly<Record<string, string>>;
|
|
27
|
+
body(): Promise<Uint8Array>;
|
|
28
|
+
get state(): GuardRequestState;
|
|
29
|
+
get scope(): Readonly<Record<string, unknown>>;
|
|
30
|
+
}
|
|
31
|
+
declare class FastifyGuardResponse implements GuardResponse {
|
|
32
|
+
readonly statusCode: number;
|
|
33
|
+
private _headers;
|
|
34
|
+
private _body;
|
|
35
|
+
constructor(statusCode: number, content: string);
|
|
36
|
+
get headers(): Record<string, string>;
|
|
37
|
+
setHeader(name: string, value: string): void;
|
|
38
|
+
get body(): Uint8Array | null;
|
|
39
|
+
get bodyText(): string | null;
|
|
40
|
+
}
|
|
41
|
+
declare class FastifyResponseFactory implements GuardResponseFactory {
|
|
42
|
+
createResponse(content: string, statusCode: number): GuardResponse;
|
|
43
|
+
createRedirectResponse(url: string, statusCode: number): GuardResponse;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export { FastifyGuardRequest, FastifyGuardResponse, FastifyResponseFactory, type GuardPluginOptions, configureCors, guardPlugin };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
// src/plugin.ts
|
|
2
|
+
import { SecurityConfigSchema, defaultLogger, initializeSecurityMiddleware } from "@guardcore/core";
|
|
3
|
+
|
|
4
|
+
// src/adapters.ts
|
|
5
|
+
var FastifyGuardRequest = class {
|
|
6
|
+
constructor(req) {
|
|
7
|
+
this.req = req;
|
|
8
|
+
}
|
|
9
|
+
_state = {};
|
|
10
|
+
get urlPath() {
|
|
11
|
+
return this.req.url.split("?")[0];
|
|
12
|
+
}
|
|
13
|
+
get urlScheme() {
|
|
14
|
+
return this.req.protocol;
|
|
15
|
+
}
|
|
16
|
+
get urlFull() {
|
|
17
|
+
return `${this.req.protocol}://${this.req.hostname}${this.req.url}`;
|
|
18
|
+
}
|
|
19
|
+
urlReplaceScheme(scheme) {
|
|
20
|
+
return this.urlFull.replace(/^https?/, scheme);
|
|
21
|
+
}
|
|
22
|
+
get method() {
|
|
23
|
+
return this.req.method;
|
|
24
|
+
}
|
|
25
|
+
get clientHost() {
|
|
26
|
+
return this.req.ip ?? null;
|
|
27
|
+
}
|
|
28
|
+
get headers() {
|
|
29
|
+
return this.req.headers;
|
|
30
|
+
}
|
|
31
|
+
get queryParams() {
|
|
32
|
+
return this.req.query ?? {};
|
|
33
|
+
}
|
|
34
|
+
async body() {
|
|
35
|
+
const raw = this.req.body;
|
|
36
|
+
if (raw instanceof Buffer) return new Uint8Array(raw);
|
|
37
|
+
if (typeof raw === "string") return new TextEncoder().encode(raw);
|
|
38
|
+
if (raw !== void 0 && raw !== null) return new TextEncoder().encode(JSON.stringify(raw));
|
|
39
|
+
return new Uint8Array(0);
|
|
40
|
+
}
|
|
41
|
+
get state() {
|
|
42
|
+
return this._state;
|
|
43
|
+
}
|
|
44
|
+
get scope() {
|
|
45
|
+
return {};
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
var FastifyGuardResponse = class {
|
|
49
|
+
constructor(statusCode, content) {
|
|
50
|
+
this.statusCode = statusCode;
|
|
51
|
+
this._body = new TextEncoder().encode(content);
|
|
52
|
+
this._headers["content-type"] = "application/json";
|
|
53
|
+
}
|
|
54
|
+
_headers = {};
|
|
55
|
+
_body;
|
|
56
|
+
get headers() {
|
|
57
|
+
return this._headers;
|
|
58
|
+
}
|
|
59
|
+
setHeader(name, value) {
|
|
60
|
+
this._headers[name] = value;
|
|
61
|
+
}
|
|
62
|
+
get body() {
|
|
63
|
+
return this._body;
|
|
64
|
+
}
|
|
65
|
+
get bodyText() {
|
|
66
|
+
return this._body ? new TextDecoder().decode(this._body) : null;
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
var FastifyResponseFactory = class {
|
|
70
|
+
createResponse(content, statusCode) {
|
|
71
|
+
return new FastifyGuardResponse(statusCode, JSON.stringify({ detail: content }));
|
|
72
|
+
}
|
|
73
|
+
createRedirectResponse(url, statusCode) {
|
|
74
|
+
const resp = new FastifyGuardResponse(statusCode, "");
|
|
75
|
+
resp.setHeader("location", url);
|
|
76
|
+
return resp;
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
// src/plugin.ts
|
|
81
|
+
async function guardPlugin(fastify, options) {
|
|
82
|
+
const resolved = SecurityConfigSchema.parse(options.config);
|
|
83
|
+
const logger = resolved.logger ?? defaultLogger;
|
|
84
|
+
const responseFactory = new FastifyResponseFactory();
|
|
85
|
+
const components = await initializeSecurityMiddleware(
|
|
86
|
+
resolved,
|
|
87
|
+
logger,
|
|
88
|
+
responseFactory,
|
|
89
|
+
options.agentHandler,
|
|
90
|
+
options.geoIpHandler,
|
|
91
|
+
options.guardDecorator
|
|
92
|
+
);
|
|
93
|
+
logger.info("Guard security plugin initialized");
|
|
94
|
+
fastify.addHook("onRequest", async (request, reply) => {
|
|
95
|
+
const guardReq = new FastifyGuardRequest(request);
|
|
96
|
+
const passthrough = await components.bypassHandler.handlePassthrough(
|
|
97
|
+
guardReq,
|
|
98
|
+
async () => createPassthroughResponse()
|
|
99
|
+
);
|
|
100
|
+
if (passthrough) {
|
|
101
|
+
sendFastifyResponse(reply, passthrough);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
const routeConfig = components.routeResolver.getRouteConfig(guardReq);
|
|
105
|
+
const bypass = await components.bypassHandler.handleSecurityBypass(
|
|
106
|
+
guardReq,
|
|
107
|
+
async () => createPassthroughResponse(),
|
|
108
|
+
routeConfig
|
|
109
|
+
);
|
|
110
|
+
if (bypass) {
|
|
111
|
+
sendFastifyResponse(reply, bypass);
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
const blockResponse = await components.pipeline.execute(guardReq);
|
|
115
|
+
if (blockResponse) {
|
|
116
|
+
sendFastifyResponse(reply, blockResponse);
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
if (routeConfig && routeConfig.behaviorRules.length > 0) {
|
|
120
|
+
const clientIp = guardReq.clientHost ?? "unknown";
|
|
121
|
+
await components.behavioralProcessor.processUsageRules(guardReq, clientIp, routeConfig);
|
|
122
|
+
}
|
|
123
|
+
request["_guardRequest"] = guardReq;
|
|
124
|
+
request["_guardRouteConfig"] = routeConfig;
|
|
125
|
+
request["_guardStartTime"] = performance.now();
|
|
126
|
+
});
|
|
127
|
+
fastify.addHook("onSend", async (request, reply, payload) => {
|
|
128
|
+
const guardReq = request["_guardRequest"];
|
|
129
|
+
const routeConfig = request["_guardRouteConfig"];
|
|
130
|
+
const startTime = request["_guardStartTime"];
|
|
131
|
+
if (!guardReq || startTime === void 0) return payload;
|
|
132
|
+
const responseTime = (performance.now() - startTime) / 1e3;
|
|
133
|
+
const bodyText = typeof payload === "string" ? payload : null;
|
|
134
|
+
const capturedResponse = {
|
|
135
|
+
statusCode: reply.statusCode,
|
|
136
|
+
headers: Object.fromEntries(
|
|
137
|
+
Object.entries(reply.getHeaders()).map(([k, v]) => [k, String(v)])
|
|
138
|
+
),
|
|
139
|
+
setHeader(name, value) {
|
|
140
|
+
reply.header(name, value);
|
|
141
|
+
},
|
|
142
|
+
body: bodyText ? new TextEncoder().encode(bodyText) : null,
|
|
143
|
+
bodyText
|
|
144
|
+
};
|
|
145
|
+
await components.errorResponseFactory.processResponse(
|
|
146
|
+
guardReq,
|
|
147
|
+
capturedResponse,
|
|
148
|
+
responseTime,
|
|
149
|
+
routeConfig ?? null,
|
|
150
|
+
routeConfig ? async (req, res, clientIp, rc) => {
|
|
151
|
+
await components.behavioralProcessor.processReturnRules(req, res, clientIp, rc);
|
|
152
|
+
} : void 0
|
|
153
|
+
);
|
|
154
|
+
return payload;
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
function sendFastifyResponse(reply, response) {
|
|
158
|
+
for (const [name, value] of Object.entries(response.headers)) {
|
|
159
|
+
reply.header(name, value);
|
|
160
|
+
}
|
|
161
|
+
if (response.headers["location"]) {
|
|
162
|
+
reply.redirect(response.headers["location"]);
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
reply.status(response.statusCode).send(response.bodyText ?? "");
|
|
166
|
+
}
|
|
167
|
+
function createPassthroughResponse() {
|
|
168
|
+
return { statusCode: 200, headers: {}, setHeader() {
|
|
169
|
+
}, body: null, bodyText: null };
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// src/cors.ts
|
|
173
|
+
async function configureCors(fastify, config) {
|
|
174
|
+
if (!config.enableCors) return;
|
|
175
|
+
try {
|
|
176
|
+
const fastifyCors = await import("@fastify/cors");
|
|
177
|
+
await fastify.register(fastifyCors.default ?? fastifyCors, {
|
|
178
|
+
origin: config.corsAllowOrigins,
|
|
179
|
+
methods: config.corsAllowMethods,
|
|
180
|
+
allowedHeaders: config.corsAllowHeaders,
|
|
181
|
+
credentials: config.corsAllowCredentials,
|
|
182
|
+
exposedHeaders: config.corsExposeHeaders,
|
|
183
|
+
maxAge: config.corsMaxAge
|
|
184
|
+
});
|
|
185
|
+
} catch {
|
|
186
|
+
throw new Error(
|
|
187
|
+
'@guardcore/fastify: CORS is enabled but "@fastify/cors" is not installed. Run: pnpm add @fastify/cors'
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// src/index.ts
|
|
193
|
+
import {
|
|
194
|
+
SecurityConfigSchema as SecurityConfigSchema2,
|
|
195
|
+
BaseSecurityDecorator,
|
|
196
|
+
SecurityDecorator,
|
|
197
|
+
RouteConfig,
|
|
198
|
+
BehaviorRule,
|
|
199
|
+
defaultLogger as defaultLogger2
|
|
200
|
+
} from "@guardcore/core";
|
|
201
|
+
export {
|
|
202
|
+
BaseSecurityDecorator,
|
|
203
|
+
BehaviorRule,
|
|
204
|
+
FastifyGuardRequest,
|
|
205
|
+
FastifyGuardResponse,
|
|
206
|
+
FastifyResponseFactory,
|
|
207
|
+
RouteConfig,
|
|
208
|
+
SecurityConfigSchema2 as SecurityConfigSchema,
|
|
209
|
+
SecurityDecorator,
|
|
210
|
+
configureCors,
|
|
211
|
+
defaultLogger2 as defaultLogger,
|
|
212
|
+
guardPlugin
|
|
213
|
+
};
|
|
214
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/plugin.ts","../src/adapters.ts","../src/cors.ts","../src/index.ts"],"sourcesContent":["import type { FastifyInstance, FastifyReply } from 'fastify';\nimport type {\n SecurityConfig,\n GuardRequest,\n GuardResponse,\n Logger,\n AgentHandlerProtocol,\n GeoIPHandler,\n SecurityMiddlewareComponents,\n RouteConfig,\n} from '@guardcore/core';\nimport { SecurityConfigSchema, defaultLogger, initializeSecurityMiddleware } from '@guardcore/core';\nimport { FastifyGuardRequest, FastifyResponseFactory } from './adapters.js';\n\nexport interface GuardPluginOptions {\n config: SecurityConfig;\n agentHandler?: AgentHandlerProtocol;\n geoIpHandler?: GeoIPHandler;\n guardDecorator?: unknown;\n}\n\nexport async function guardPlugin(fastify: FastifyInstance, options: GuardPluginOptions): Promise<void> {\n const resolved = SecurityConfigSchema.parse(options.config);\n const logger: Logger = resolved.logger ?? defaultLogger;\n const responseFactory = new FastifyResponseFactory();\n\n const components: SecurityMiddlewareComponents = await initializeSecurityMiddleware(\n resolved, logger, responseFactory,\n options.agentHandler, options.geoIpHandler, options.guardDecorator,\n );\n\n logger.info('Guard security plugin initialized');\n\n fastify.addHook('onRequest', async (request, reply) => {\n const guardReq = new FastifyGuardRequest(request);\n\n const passthrough = await components.bypassHandler.handlePassthrough(\n guardReq, async () => createPassthroughResponse(),\n );\n if (passthrough) {\n sendFastifyResponse(reply, passthrough);\n return;\n }\n\n const routeConfig = components.routeResolver.getRouteConfig(guardReq);\n\n const bypass = await components.bypassHandler.handleSecurityBypass(\n guardReq, async () => createPassthroughResponse(), routeConfig,\n );\n if (bypass) {\n sendFastifyResponse(reply, bypass);\n return;\n }\n\n const blockResponse = await components.pipeline.execute(guardReq);\n if (blockResponse) {\n sendFastifyResponse(reply, blockResponse);\n return;\n }\n\n if (routeConfig && routeConfig.behaviorRules.length > 0) {\n const clientIp = guardReq.clientHost ?? 'unknown';\n await components.behavioralProcessor.processUsageRules(guardReq, clientIp, routeConfig);\n }\n\n (request as unknown as Record<string, unknown>)['_guardRequest'] = guardReq;\n (request as unknown as Record<string, unknown>)['_guardRouteConfig'] = routeConfig;\n (request as unknown as Record<string, unknown>)['_guardStartTime'] = performance.now();\n });\n\n fastify.addHook('onSend', async (request, reply, payload) => {\n const guardReq = (request as unknown as Record<string, unknown>)['_guardRequest'] as FastifyGuardRequest | undefined;\n const routeConfig = (request as unknown as Record<string, unknown>)['_guardRouteConfig'] as RouteConfig | null | undefined;\n const startTime = (request as unknown as Record<string, unknown>)['_guardStartTime'] as number | undefined;\n\n if (!guardReq || startTime === undefined) return payload;\n\n const responseTime = (performance.now() - startTime) / 1000;\n const bodyText = typeof payload === 'string' ? payload : null;\n const capturedResponse: GuardResponse = {\n statusCode: reply.statusCode,\n headers: Object.fromEntries(\n Object.entries(reply.getHeaders()).map(([k, v]) => [k, String(v)]),\n ),\n setHeader(name: string, value: string) { reply.header(name, value); },\n body: bodyText ? new TextEncoder().encode(bodyText) : null,\n bodyText,\n };\n\n await components.errorResponseFactory.processResponse(\n guardReq, capturedResponse, responseTime, routeConfig ?? null,\n routeConfig ? async (req: GuardRequest, res: GuardResponse, clientIp: string, rc: RouteConfig) => {\n await components.behavioralProcessor.processReturnRules(req, res, clientIp, rc);\n } : undefined,\n );\n\n return payload;\n });\n}\n\nfunction sendFastifyResponse(reply: FastifyReply, response: GuardResponse): void {\n for (const [name, value] of Object.entries(response.headers)) {\n reply.header(name, value);\n }\n\n if (response.headers['location']) {\n reply.redirect(response.headers['location']);\n return;\n }\n\n reply.status(response.statusCode).send(response.bodyText ?? '');\n}\n\nfunction createPassthroughResponse(): GuardResponse {\n return { statusCode: 200, headers: {}, setHeader() {}, body: null, bodyText: null };\n}\n","import type { FastifyRequest } from 'fastify';\nimport type { GuardRequest, GuardRequestState, GuardResponse, GuardResponseFactory } from '@guardcore/core';\n\nexport class FastifyGuardRequest implements GuardRequest {\n private _state: GuardRequestState = {};\n\n constructor(private readonly req: FastifyRequest) {}\n\n get urlPath(): string { return this.req.url.split('?')[0]; }\n get urlScheme(): string { return this.req.protocol; }\n get urlFull(): string { return `${this.req.protocol}://${this.req.hostname}${this.req.url}`; }\n urlReplaceScheme(scheme: string): string { return this.urlFull.replace(/^https?/, scheme); }\n get method(): string { return this.req.method; }\n get clientHost(): string | null { return this.req.ip ?? null; }\n get headers(): Readonly<Record<string, string>> { return this.req.headers as Record<string, string>; }\n get queryParams(): Readonly<Record<string, string>> { return (this.req.query ?? {}) as Record<string, string>; }\n async body(): Promise<Uint8Array> {\n const raw = this.req.body;\n if (raw instanceof Buffer) return new Uint8Array(raw);\n if (typeof raw === 'string') return new TextEncoder().encode(raw);\n if (raw !== undefined && raw !== null) return new TextEncoder().encode(JSON.stringify(raw));\n return new Uint8Array(0);\n }\n get state(): GuardRequestState { return this._state; }\n get scope(): Readonly<Record<string, unknown>> { return {}; }\n}\n\nexport class FastifyGuardResponse implements GuardResponse {\n private _headers: Record<string, string> = {};\n private _body: Uint8Array | null;\n\n constructor(readonly statusCode: number, content: string) {\n this._body = new TextEncoder().encode(content);\n this._headers['content-type'] = 'application/json';\n }\n\n get headers(): Record<string, string> { return this._headers; }\n setHeader(name: string, value: string): void { this._headers[name] = value; }\n get body(): Uint8Array | null { return this._body; }\n get bodyText(): string | null { return this._body ? new TextDecoder().decode(this._body) : null; }\n}\n\nexport class FastifyResponseFactory implements GuardResponseFactory {\n createResponse(content: string, statusCode: number): GuardResponse {\n return new FastifyGuardResponse(statusCode, JSON.stringify({ detail: content }));\n }\n\n createRedirectResponse(url: string, statusCode: number): GuardResponse {\n const resp = new FastifyGuardResponse(statusCode, '');\n resp.setHeader('location', url);\n return resp;\n }\n}\n","import type { FastifyInstance } from 'fastify';\nimport type { ResolvedSecurityConfig } from '@guardcore/core';\n\nexport async function configureCors(fastify: FastifyInstance, config: ResolvedSecurityConfig): Promise<void> {\n if (!config.enableCors) return;\n\n try {\n const fastifyCors = await import('@fastify/cors');\n await fastify.register(fastifyCors.default ?? fastifyCors, {\n origin: config.corsAllowOrigins,\n methods: config.corsAllowMethods,\n allowedHeaders: config.corsAllowHeaders,\n credentials: config.corsAllowCredentials,\n exposedHeaders: config.corsExposeHeaders,\n maxAge: config.corsMaxAge,\n });\n } catch {\n throw new Error(\n '@guardcore/fastify: CORS is enabled but \"@fastify/cors\" is not installed. ' +\n 'Run: pnpm add @fastify/cors',\n );\n }\n}\n","export { guardPlugin } from './plugin.js';\nexport type { GuardPluginOptions } from './plugin.js';\nexport { configureCors } from './cors.js';\nexport { FastifyGuardRequest, FastifyGuardResponse, FastifyResponseFactory } from './adapters.js';\n\nexport {\n SecurityConfigSchema,\n BaseSecurityDecorator,\n SecurityDecorator,\n RouteConfig,\n BehaviorRule,\n defaultLogger,\n} from '@guardcore/core';\n\nexport type {\n SecurityConfig,\n ResolvedSecurityConfig,\n GuardRequest,\n GuardResponse,\n Logger,\n} from '@guardcore/core';\n"],"mappings":"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","names":["SecurityConfigSchema","defaultLogger"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@guardcore/fastify",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Fastify adapter for @guardcore/core security middleware",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.cjs",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js",
|
|
13
|
+
"require": "./dist/index.cjs"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist"
|
|
18
|
+
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "tsup",
|
|
21
|
+
"test": "vitest run",
|
|
22
|
+
"lint": "tsc --noEmit",
|
|
23
|
+
"clean": "rm -rf dist"
|
|
24
|
+
},
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"@guardcore/core": "workspace:*"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"tsup": "^8",
|
|
30
|
+
"vitest": "^4",
|
|
31
|
+
"typescript": "~5.9",
|
|
32
|
+
"fastify": "^5"
|
|
33
|
+
},
|
|
34
|
+
"peerDependencies": {
|
|
35
|
+
"fastify": "^5",
|
|
36
|
+
"@fastify/cors": "^9"
|
|
37
|
+
},
|
|
38
|
+
"peerDependenciesMeta": {
|
|
39
|
+
"@fastify/cors": {
|
|
40
|
+
"optional": true
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
"license": "MIT",
|
|
44
|
+
"engines": {
|
|
45
|
+
"node": ">=18"
|
|
46
|
+
},
|
|
47
|
+
"repository": {
|
|
48
|
+
"type": "git",
|
|
49
|
+
"url": "git+https://github.com/rennf93/guard-core-ts.git",
|
|
50
|
+
"directory": "packages/fastify"
|
|
51
|
+
},
|
|
52
|
+
"publishConfig": {
|
|
53
|
+
"access": "public"
|
|
54
|
+
}
|
|
55
|
+
}
|