@holo-js/security 0.3.28 → 0.4.1
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/dist/.holo-build-fingerprint +1 -0
- package/dist/{chunk-WRMZRO77.mjs → chunk-D3ZN5MLJ.mjs} +112 -6
- package/dist/{chunk-PC2YCWDH.mjs → chunk-EINZXLC2.mjs} +7 -5
- package/dist/{chunk-PHPA3C6L.mjs → chunk-EN2H3TN3.mjs} +14 -15
- package/dist/{chunk-26IYURJG.mjs → chunk-RYQLS2OO.mjs} +428 -211
- package/dist/{chunk-FQ4X7X6L.mjs → chunk-U25HSHLJ.mjs} +4 -16
- package/dist/client.d.ts +1 -1
- package/dist/client.mjs +2 -2
- package/dist/config-BK6q6Yh9.d.ts +145 -0
- package/dist/config.d.ts +2 -136
- package/dist/config.mjs +1 -1
- package/dist/contracts.d.ts +15 -4
- package/dist/contracts.mjs +2 -4
- package/dist/drivers/redis-adapter.d.ts +3 -8
- package/dist/drivers/redis-adapter.mjs +13 -29
- package/dist/index.d.ts +24 -74
- package/dist/index.mjs +5 -6
- package/dist/next/server.d.ts +2 -1
- package/dist/next/server.mjs +28 -10
- package/dist/nuxt/server.mjs +50 -14
- package/dist/sveltekit/server.d.ts +2 -1
- package/dist/sveltekit/server.mjs +12 -6
- package/package.json +19 -13
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
normalizeCorsConfig,
|
|
3
3
|
normalizeSecurityConfig
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-D3ZN5MLJ.mjs";
|
|
5
5
|
|
|
6
6
|
// src/contracts.ts
|
|
7
7
|
var SecurityCsrfError = class extends Error {
|
|
@@ -27,6 +27,8 @@ var PendingSecurityLimiterDefinition = class {
|
|
|
27
27
|
this.maxAttempts = maxAttempts;
|
|
28
28
|
this.decaySeconds = decaySeconds;
|
|
29
29
|
}
|
|
30
|
+
maxAttempts;
|
|
31
|
+
decaySeconds;
|
|
30
32
|
by(key) {
|
|
31
33
|
if (typeof key !== "function") {
|
|
32
34
|
throw new TypeError("[@holo-js/security] Rate limiter key resolvers must be functions.");
|
|
@@ -64,20 +66,6 @@ var limit = Object.freeze({
|
|
|
64
66
|
);
|
|
65
67
|
}
|
|
66
68
|
});
|
|
67
|
-
function ip(request, trustedProxy = false) {
|
|
68
|
-
if (!trustedProxy) {
|
|
69
|
-
return "unknown";
|
|
70
|
-
}
|
|
71
|
-
const forwarded = request.headers.get("x-forwarded-for")?.split(",", 1)[0]?.trim();
|
|
72
|
-
if (forwarded) {
|
|
73
|
-
return forwarded;
|
|
74
|
-
}
|
|
75
|
-
const realIp = request.headers.get("x-real-ip")?.trim();
|
|
76
|
-
if (realIp) {
|
|
77
|
-
return realIp;
|
|
78
|
-
}
|
|
79
|
-
return "unknown";
|
|
80
|
-
}
|
|
81
69
|
function normalizeLimiterIntegerInput(value, label) {
|
|
82
70
|
if (typeof value === "undefined") {
|
|
83
71
|
throw new TypeError(`[@holo-js/security] ${label} is required.`);
|
|
@@ -117,6 +105,7 @@ function defineSecurityRuntimeBindings(bindings) {
|
|
|
117
105
|
cors: normalizeCorsConfig(bindings.cors),
|
|
118
106
|
rateLimitStore: bindings.rateLimitStore,
|
|
119
107
|
csrfSigningKey: bindings.csrfSigningKey,
|
|
108
|
+
csrfSession: bindings.csrfSession,
|
|
120
109
|
defaultKeyResolver: bindings.defaultKeyResolver
|
|
121
110
|
});
|
|
122
111
|
}
|
|
@@ -144,7 +133,6 @@ export {
|
|
|
144
133
|
SecurityCsrfError,
|
|
145
134
|
SecurityRateLimitError,
|
|
146
135
|
limit,
|
|
147
|
-
ip,
|
|
148
136
|
defineRateLimiter,
|
|
149
137
|
defineSecurityRuntimeBindings,
|
|
150
138
|
createMemoryRateLimitStoreConfig,
|
package/dist/client.d.ts
CHANGED
package/dist/client.mjs
CHANGED
|
@@ -2,13 +2,13 @@ import {
|
|
|
2
2
|
getDefaultSecurityClientConfig,
|
|
3
3
|
readSecurityClientConfigFromCookies,
|
|
4
4
|
securityClientConfigInternals
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-EINZXLC2.mjs";
|
|
6
6
|
|
|
7
7
|
// src/client.ts
|
|
8
8
|
function getSecurityClientConfig() {
|
|
9
9
|
const runtime = globalThis;
|
|
10
10
|
const config = readSecurityClientConfigFromCookies(runtime.document?.cookie) ?? getDefaultSecurityClientConfig();
|
|
11
|
-
return Object.freeze({ csrf: Object.freeze({ field: config.csrf.field, cookie: config.csrf.cookie }) });
|
|
11
|
+
return Object.freeze({ csrf: Object.freeze({ field: config.csrf.field, header: config.csrf.header, cookie: config.csrf.cookie }) });
|
|
12
12
|
}
|
|
13
13
|
var securityClientInternals = {
|
|
14
14
|
getDefaultSecurityClientConfig,
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { NormalizedHoloRedisClusterNodeConfig, HoloRedisClusterNodeConfig, NormalizedHoloRedisConfig } from '@holo-js/kernel';
|
|
2
|
+
|
|
3
|
+
type SecurityTrustedProxies = readonly string[] | '*';
|
|
4
|
+
|
|
5
|
+
interface HoloCorsConfig {
|
|
6
|
+
readonly paths?: readonly string[];
|
|
7
|
+
readonly origins?: readonly string[];
|
|
8
|
+
readonly methods?: readonly string[];
|
|
9
|
+
readonly headers?: readonly string[];
|
|
10
|
+
readonly credentials?: boolean;
|
|
11
|
+
readonly maxAge?: number | string;
|
|
12
|
+
readonly statefulDomains?: readonly string[];
|
|
13
|
+
}
|
|
14
|
+
declare module '@holo-js/config' {
|
|
15
|
+
interface HoloConfigRegistry {
|
|
16
|
+
cors: NormalizedHoloCorsConfig;
|
|
17
|
+
security: NormalizedHoloSecurityConfig;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
interface NormalizedHoloCorsConfig {
|
|
21
|
+
readonly paths: readonly string[];
|
|
22
|
+
readonly origins: readonly string[];
|
|
23
|
+
readonly methods: readonly string[];
|
|
24
|
+
readonly headers: readonly string[];
|
|
25
|
+
readonly credentials: boolean;
|
|
26
|
+
readonly maxAge: number;
|
|
27
|
+
readonly statefulDomains: readonly string[];
|
|
28
|
+
}
|
|
29
|
+
type SecurityRateLimitDriver = 'memory' | 'file' | 'redis';
|
|
30
|
+
interface HoloSecurityCsrfConfig {
|
|
31
|
+
readonly enabled?: boolean;
|
|
32
|
+
readonly field?: string;
|
|
33
|
+
readonly header?: string;
|
|
34
|
+
readonly cookie?: string;
|
|
35
|
+
readonly except?: readonly string[];
|
|
36
|
+
}
|
|
37
|
+
interface NormalizedHoloSecurityCsrfConfig {
|
|
38
|
+
readonly enabled: boolean;
|
|
39
|
+
readonly field: string;
|
|
40
|
+
readonly header: string;
|
|
41
|
+
readonly cookie: string;
|
|
42
|
+
readonly except: readonly string[];
|
|
43
|
+
}
|
|
44
|
+
interface SecurityRateLimitContext<TValues extends Readonly<Record<string, unknown>> | undefined = Readonly<Record<string, unknown>> | undefined> {
|
|
45
|
+
readonly request: Request;
|
|
46
|
+
readonly values?: TValues;
|
|
47
|
+
}
|
|
48
|
+
interface SecurityRateLimitKeyResolver<TValues extends Readonly<Record<string, unknown>> | undefined = Readonly<Record<string, unknown>> | undefined> {
|
|
49
|
+
(context: SecurityRateLimitContext<TValues>): string | Promise<string>;
|
|
50
|
+
}
|
|
51
|
+
interface SecurityLimiterConfig<TValues extends Readonly<Record<string, unknown>> | undefined = Readonly<Record<string, unknown>> | undefined> {
|
|
52
|
+
readonly maxAttempts: number | string;
|
|
53
|
+
readonly decaySeconds: number | string;
|
|
54
|
+
readonly key?: SecurityRateLimitKeyResolver<TValues>;
|
|
55
|
+
}
|
|
56
|
+
interface NormalizedSecurityLimiterConfig<TValues extends Readonly<Record<string, unknown>> | undefined = Readonly<Record<string, unknown>> | undefined> {
|
|
57
|
+
readonly name: string;
|
|
58
|
+
readonly maxAttempts: number;
|
|
59
|
+
readonly decaySeconds: number;
|
|
60
|
+
readonly key?: SecurityRateLimitKeyResolver<TValues>;
|
|
61
|
+
}
|
|
62
|
+
interface SecurityRateLimitMemoryConfig {
|
|
63
|
+
readonly driver?: 'memory';
|
|
64
|
+
}
|
|
65
|
+
interface SecurityRateLimitFileConfig {
|
|
66
|
+
readonly path?: string;
|
|
67
|
+
}
|
|
68
|
+
interface SecurityRateLimitRedisConnectionConfig {
|
|
69
|
+
readonly url?: string;
|
|
70
|
+
readonly clusters?: readonly HoloRedisClusterNodeConfig[];
|
|
71
|
+
readonly host?: string;
|
|
72
|
+
readonly port?: number | string;
|
|
73
|
+
readonly password?: string;
|
|
74
|
+
readonly username?: string;
|
|
75
|
+
readonly db?: number | string;
|
|
76
|
+
}
|
|
77
|
+
interface SecurityRateLimitRedisConfig {
|
|
78
|
+
readonly connection?: string;
|
|
79
|
+
readonly prefix?: string;
|
|
80
|
+
}
|
|
81
|
+
interface HoloSecurityRateLimitConfig {
|
|
82
|
+
readonly driver?: SecurityRateLimitDriver;
|
|
83
|
+
readonly memory?: SecurityRateLimitMemoryConfig;
|
|
84
|
+
readonly file?: SecurityRateLimitFileConfig;
|
|
85
|
+
readonly redis?: SecurityRateLimitRedisConfig;
|
|
86
|
+
readonly limiters?: Readonly<Record<string, SecurityLimiterConfig>>;
|
|
87
|
+
}
|
|
88
|
+
interface NormalizedSecurityRateLimitMemoryConfig {
|
|
89
|
+
readonly driver: 'memory';
|
|
90
|
+
}
|
|
91
|
+
interface NormalizedSecurityRateLimitFileConfig {
|
|
92
|
+
readonly path: string;
|
|
93
|
+
}
|
|
94
|
+
interface NormalizedSecurityRateLimitRedisConfig {
|
|
95
|
+
readonly url?: string;
|
|
96
|
+
readonly clusters?: readonly NormalizedHoloRedisClusterNodeConfig[];
|
|
97
|
+
readonly host: string;
|
|
98
|
+
readonly port: number;
|
|
99
|
+
readonly password?: string;
|
|
100
|
+
readonly username?: string;
|
|
101
|
+
readonly db: number;
|
|
102
|
+
readonly connection: string;
|
|
103
|
+
readonly prefix: string;
|
|
104
|
+
}
|
|
105
|
+
interface NormalizedHoloSecurityRateLimitConfig {
|
|
106
|
+
readonly driver: SecurityRateLimitDriver;
|
|
107
|
+
readonly memory: NormalizedSecurityRateLimitMemoryConfig;
|
|
108
|
+
readonly file: NormalizedSecurityRateLimitFileConfig;
|
|
109
|
+
readonly redis: NormalizedSecurityRateLimitRedisConfig;
|
|
110
|
+
readonly limiters: Readonly<Record<string, NormalizedSecurityLimiterConfig>>;
|
|
111
|
+
}
|
|
112
|
+
interface HoloSecurityConfig {
|
|
113
|
+
readonly csrf?: boolean | HoloSecurityCsrfConfig;
|
|
114
|
+
readonly rateLimit?: HoloSecurityRateLimitConfig;
|
|
115
|
+
readonly trustedProxies?: SecurityTrustedProxies;
|
|
116
|
+
readonly maxBodyBytes?: number | string;
|
|
117
|
+
readonly maxUploadBodyBytes?: number | string;
|
|
118
|
+
}
|
|
119
|
+
interface NormalizedHoloSecurityConfig {
|
|
120
|
+
readonly csrf: NormalizedHoloSecurityCsrfConfig;
|
|
121
|
+
readonly rateLimit: NormalizedHoloSecurityRateLimitConfig;
|
|
122
|
+
readonly trustedProxies: SecurityTrustedProxies;
|
|
123
|
+
readonly maxBodyBytes: number;
|
|
124
|
+
readonly maxUploadBodyBytes: number;
|
|
125
|
+
}
|
|
126
|
+
declare const DEFAULT_CORS_PATHS: readonly ["/api/*"];
|
|
127
|
+
declare const DEFAULT_CORS_METHODS: readonly ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"];
|
|
128
|
+
declare const DEFAULT_CORS_HEADERS: readonly ["Content-Type", "Authorization", "X-CSRF-TOKEN", "X-XSRF-TOKEN", "X-Requested-With"];
|
|
129
|
+
declare const DEFAULT_CORS_MAX_AGE = 7200;
|
|
130
|
+
declare const holoCorsDefaults: Readonly<NormalizedHoloCorsConfig>;
|
|
131
|
+
declare const DEFAULT_SECURITY_CSRF_FIELD = "_token";
|
|
132
|
+
declare const DEFAULT_SECURITY_CSRF_HEADER = "X-CSRF-TOKEN";
|
|
133
|
+
declare const DEFAULT_SECURITY_CSRF_COOKIE = "XSRF-TOKEN";
|
|
134
|
+
declare const DEFAULT_SECURITY_RATE_LIMIT_DRIVER: SecurityRateLimitDriver;
|
|
135
|
+
declare const DEFAULT_SECURITY_RATE_LIMIT_FILE_PATH = "./storage/framework/rate-limits";
|
|
136
|
+
declare const DEFAULT_SECURITY_RATE_LIMIT_REDIS_CONNECTION = "default";
|
|
137
|
+
declare const DEFAULT_SECURITY_RATE_LIMIT_REDIS_PREFIX = "holo:rate-limit:";
|
|
138
|
+
declare const holoSecurityDefaults: Readonly<NormalizedHoloSecurityConfig>;
|
|
139
|
+
declare function normalizeCorsConfig(config?: HoloCorsConfig): NormalizedHoloCorsConfig;
|
|
140
|
+
declare function normalizeSecurityConfig(config?: HoloSecurityConfig, redisConfig?: NormalizedHoloRedisConfig): NormalizedHoloSecurityConfig;
|
|
141
|
+
declare function defineSecurityConfig<TConfig extends HoloSecurityConfig>(config: TConfig): Readonly<TConfig>;
|
|
142
|
+
declare function defineCorsConfig<TConfig extends HoloCorsConfig>(config: TConfig): Readonly<TConfig>;
|
|
143
|
+
|
|
144
|
+
export { DEFAULT_SECURITY_CSRF_HEADER as A, DEFAULT_SECURITY_RATE_LIMIT_DRIVER as B, DEFAULT_SECURITY_RATE_LIMIT_FILE_PATH as C, DEFAULT_CORS_HEADERS as D, DEFAULT_SECURITY_RATE_LIMIT_REDIS_CONNECTION as E, DEFAULT_SECURITY_RATE_LIMIT_REDIS_PREFIX as F, defineCorsConfig as p, defineSecurityConfig as q, holoCorsDefaults as r, holoSecurityDefaults as s, normalizeCorsConfig as t, normalizeSecurityConfig as u, DEFAULT_CORS_MAX_AGE as v, DEFAULT_CORS_METHODS as w, DEFAULT_CORS_PATHS as x, DEFAULT_SECURITY_CSRF_COOKIE as y, DEFAULT_SECURITY_CSRF_FIELD as z };
|
|
145
|
+
export type { NormalizedSecurityRateLimitFileConfig as G, HoloSecurityConfig as H, NormalizedSecurityRateLimitMemoryConfig as I, SecurityRateLimitRedisConnectionConfig as J, NormalizedSecurityRateLimitRedisConfig as N, SecurityRateLimitKeyResolver as S, NormalizedHoloCorsConfig as a, NormalizedSecurityLimiterConfig as b, NormalizedHoloSecurityConfig as c, SecurityLimiterConfig as d, HoloCorsConfig as e, HoloSecurityCsrfConfig as f, HoloSecurityRateLimitConfig as g, NormalizedHoloSecurityCsrfConfig as h, NormalizedHoloSecurityRateLimitConfig as i, SecurityRateLimitContext as j, SecurityRateLimitDriver as k, SecurityRateLimitFileConfig as l, SecurityRateLimitMemoryConfig as m, SecurityRateLimitRedisConfig as n, SecurityTrustedProxies as o };
|
package/dist/config.d.ts
CHANGED
|
@@ -1,136 +1,2 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
interface HoloCorsConfig {
|
|
4
|
-
readonly paths?: readonly string[];
|
|
5
|
-
readonly origins?: readonly string[];
|
|
6
|
-
readonly methods?: readonly string[];
|
|
7
|
-
readonly headers?: readonly string[];
|
|
8
|
-
readonly credentials?: boolean;
|
|
9
|
-
readonly maxAge?: number | string;
|
|
10
|
-
readonly statefulDomains?: readonly string[];
|
|
11
|
-
}
|
|
12
|
-
declare module '@holo-js/config' {
|
|
13
|
-
interface HoloConfigRegistry {
|
|
14
|
-
cors: NormalizedHoloCorsConfig;
|
|
15
|
-
security: NormalizedHoloSecurityConfig;
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
interface NormalizedHoloCorsConfig {
|
|
19
|
-
readonly paths: readonly string[];
|
|
20
|
-
readonly origins: readonly string[];
|
|
21
|
-
readonly methods: readonly string[];
|
|
22
|
-
readonly headers: readonly string[];
|
|
23
|
-
readonly credentials: boolean;
|
|
24
|
-
readonly maxAge: number;
|
|
25
|
-
readonly statefulDomains: readonly string[];
|
|
26
|
-
}
|
|
27
|
-
type SecurityRateLimitDriver = 'memory' | 'file' | 'redis';
|
|
28
|
-
interface HoloSecurityCsrfConfig {
|
|
29
|
-
readonly enabled?: boolean;
|
|
30
|
-
readonly field?: string;
|
|
31
|
-
readonly header?: string;
|
|
32
|
-
readonly cookie?: string;
|
|
33
|
-
readonly except?: readonly string[];
|
|
34
|
-
}
|
|
35
|
-
interface NormalizedHoloSecurityCsrfConfig {
|
|
36
|
-
readonly enabled: boolean;
|
|
37
|
-
readonly field: string;
|
|
38
|
-
readonly header: string;
|
|
39
|
-
readonly cookie: string;
|
|
40
|
-
readonly except: readonly string[];
|
|
41
|
-
}
|
|
42
|
-
interface SecurityRateLimitContext<TValues extends Readonly<Record<string, unknown>> | undefined = Readonly<Record<string, unknown>> | undefined> {
|
|
43
|
-
readonly request: Request;
|
|
44
|
-
readonly values?: TValues;
|
|
45
|
-
}
|
|
46
|
-
interface SecurityRateLimitKeyResolver<TValues extends Readonly<Record<string, unknown>> | undefined = Readonly<Record<string, unknown>> | undefined> {
|
|
47
|
-
(context: SecurityRateLimitContext<TValues>): string | Promise<string>;
|
|
48
|
-
}
|
|
49
|
-
interface SecurityLimiterConfig<TValues extends Readonly<Record<string, unknown>> | undefined = Readonly<Record<string, unknown>> | undefined> {
|
|
50
|
-
readonly maxAttempts: number | string;
|
|
51
|
-
readonly decaySeconds: number | string;
|
|
52
|
-
readonly key?: SecurityRateLimitKeyResolver<TValues>;
|
|
53
|
-
}
|
|
54
|
-
interface NormalizedSecurityLimiterConfig<TValues extends Readonly<Record<string, unknown>> | undefined = Readonly<Record<string, unknown>> | undefined> {
|
|
55
|
-
readonly name: string;
|
|
56
|
-
readonly maxAttempts: number;
|
|
57
|
-
readonly decaySeconds: number;
|
|
58
|
-
readonly key?: SecurityRateLimitKeyResolver<TValues>;
|
|
59
|
-
}
|
|
60
|
-
interface SecurityRateLimitMemoryConfig {
|
|
61
|
-
readonly driver?: 'memory';
|
|
62
|
-
}
|
|
63
|
-
interface SecurityRateLimitFileConfig {
|
|
64
|
-
readonly path?: string;
|
|
65
|
-
}
|
|
66
|
-
interface SecurityRateLimitRedisConnectionConfig {
|
|
67
|
-
readonly url?: string;
|
|
68
|
-
readonly clusters?: readonly HoloRedisClusterNodeConfig[];
|
|
69
|
-
readonly host?: string;
|
|
70
|
-
readonly port?: number | string;
|
|
71
|
-
readonly password?: string;
|
|
72
|
-
readonly username?: string;
|
|
73
|
-
readonly db?: number | string;
|
|
74
|
-
}
|
|
75
|
-
interface SecurityRateLimitRedisConfig {
|
|
76
|
-
readonly connection?: string;
|
|
77
|
-
readonly prefix?: string;
|
|
78
|
-
}
|
|
79
|
-
interface HoloSecurityRateLimitConfig {
|
|
80
|
-
readonly driver?: SecurityRateLimitDriver;
|
|
81
|
-
readonly memory?: SecurityRateLimitMemoryConfig;
|
|
82
|
-
readonly file?: SecurityRateLimitFileConfig;
|
|
83
|
-
readonly redis?: SecurityRateLimitRedisConfig;
|
|
84
|
-
readonly limiters?: Readonly<Record<string, SecurityLimiterConfig>>;
|
|
85
|
-
}
|
|
86
|
-
interface NormalizedSecurityRateLimitMemoryConfig {
|
|
87
|
-
readonly driver: 'memory';
|
|
88
|
-
}
|
|
89
|
-
interface NormalizedSecurityRateLimitFileConfig {
|
|
90
|
-
readonly path: string;
|
|
91
|
-
}
|
|
92
|
-
interface NormalizedSecurityRateLimitRedisConfig {
|
|
93
|
-
readonly url?: string;
|
|
94
|
-
readonly clusters?: readonly NormalizedHoloRedisClusterNodeConfig[];
|
|
95
|
-
readonly host: string;
|
|
96
|
-
readonly port: number;
|
|
97
|
-
readonly password?: string;
|
|
98
|
-
readonly username?: string;
|
|
99
|
-
readonly db: number;
|
|
100
|
-
readonly connection: string;
|
|
101
|
-
readonly prefix: string;
|
|
102
|
-
}
|
|
103
|
-
interface NormalizedHoloSecurityRateLimitConfig {
|
|
104
|
-
readonly driver: SecurityRateLimitDriver;
|
|
105
|
-
readonly memory: NormalizedSecurityRateLimitMemoryConfig;
|
|
106
|
-
readonly file: NormalizedSecurityRateLimitFileConfig;
|
|
107
|
-
readonly redis: NormalizedSecurityRateLimitRedisConfig;
|
|
108
|
-
readonly limiters: Readonly<Record<string, NormalizedSecurityLimiterConfig>>;
|
|
109
|
-
}
|
|
110
|
-
interface HoloSecurityConfig {
|
|
111
|
-
readonly csrf?: boolean | HoloSecurityCsrfConfig;
|
|
112
|
-
readonly rateLimit?: HoloSecurityRateLimitConfig;
|
|
113
|
-
}
|
|
114
|
-
interface NormalizedHoloSecurityConfig {
|
|
115
|
-
readonly csrf: NormalizedHoloSecurityCsrfConfig;
|
|
116
|
-
readonly rateLimit: NormalizedHoloSecurityRateLimitConfig;
|
|
117
|
-
}
|
|
118
|
-
declare const DEFAULT_CORS_PATHS: readonly ["/api/*"];
|
|
119
|
-
declare const DEFAULT_CORS_METHODS: readonly ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"];
|
|
120
|
-
declare const DEFAULT_CORS_HEADERS: readonly ["Content-Type", "Authorization", "X-CSRF-TOKEN", "X-Requested-With"];
|
|
121
|
-
declare const DEFAULT_CORS_MAX_AGE = 7200;
|
|
122
|
-
declare const holoCorsDefaults: Readonly<NormalizedHoloCorsConfig>;
|
|
123
|
-
declare const DEFAULT_SECURITY_CSRF_FIELD = "_token";
|
|
124
|
-
declare const DEFAULT_SECURITY_CSRF_HEADER = "X-CSRF-TOKEN";
|
|
125
|
-
declare const DEFAULT_SECURITY_CSRF_COOKIE = "XSRF-TOKEN";
|
|
126
|
-
declare const DEFAULT_SECURITY_RATE_LIMIT_DRIVER: SecurityRateLimitDriver;
|
|
127
|
-
declare const DEFAULT_SECURITY_RATE_LIMIT_FILE_PATH = "./storage/framework/rate-limits";
|
|
128
|
-
declare const DEFAULT_SECURITY_RATE_LIMIT_REDIS_CONNECTION = "default";
|
|
129
|
-
declare const DEFAULT_SECURITY_RATE_LIMIT_REDIS_PREFIX = "holo:rate-limit:";
|
|
130
|
-
declare const holoSecurityDefaults: Readonly<NormalizedHoloSecurityConfig>;
|
|
131
|
-
declare function normalizeCorsConfig(config?: HoloCorsConfig): NormalizedHoloCorsConfig;
|
|
132
|
-
declare function normalizeSecurityConfig(config?: HoloSecurityConfig, redisConfig?: NormalizedHoloRedisConfig): NormalizedHoloSecurityConfig;
|
|
133
|
-
declare function defineSecurityConfig<TConfig extends HoloSecurityConfig>(config: TConfig): Readonly<TConfig>;
|
|
134
|
-
declare function defineCorsConfig<TConfig extends HoloCorsConfig>(config: TConfig): Readonly<TConfig>;
|
|
135
|
-
|
|
136
|
-
export { DEFAULT_CORS_HEADERS, DEFAULT_CORS_MAX_AGE, DEFAULT_CORS_METHODS, DEFAULT_CORS_PATHS, DEFAULT_SECURITY_CSRF_COOKIE, DEFAULT_SECURITY_CSRF_FIELD, DEFAULT_SECURITY_CSRF_HEADER, DEFAULT_SECURITY_RATE_LIMIT_DRIVER, DEFAULT_SECURITY_RATE_LIMIT_FILE_PATH, DEFAULT_SECURITY_RATE_LIMIT_REDIS_CONNECTION, DEFAULT_SECURITY_RATE_LIMIT_REDIS_PREFIX, type HoloCorsConfig, type HoloSecurityConfig, type HoloSecurityCsrfConfig, type HoloSecurityRateLimitConfig, type NormalizedHoloCorsConfig, type NormalizedHoloSecurityConfig, type NormalizedHoloSecurityCsrfConfig, type NormalizedHoloSecurityRateLimitConfig, type NormalizedSecurityLimiterConfig, type NormalizedSecurityRateLimitFileConfig, type NormalizedSecurityRateLimitMemoryConfig, type NormalizedSecurityRateLimitRedisConfig, type SecurityLimiterConfig, type SecurityRateLimitContext, type SecurityRateLimitDriver, type SecurityRateLimitFileConfig, type SecurityRateLimitKeyResolver, type SecurityRateLimitMemoryConfig, type SecurityRateLimitRedisConfig, type SecurityRateLimitRedisConnectionConfig, defineCorsConfig, defineSecurityConfig, holoCorsDefaults, holoSecurityDefaults, normalizeCorsConfig, normalizeSecurityConfig };
|
|
1
|
+
import '@holo-js/kernel';
|
|
2
|
+
export { D as DEFAULT_CORS_HEADERS, v as DEFAULT_CORS_MAX_AGE, w as DEFAULT_CORS_METHODS, x as DEFAULT_CORS_PATHS, y as DEFAULT_SECURITY_CSRF_COOKIE, z as DEFAULT_SECURITY_CSRF_FIELD, A as DEFAULT_SECURITY_CSRF_HEADER, B as DEFAULT_SECURITY_RATE_LIMIT_DRIVER, C as DEFAULT_SECURITY_RATE_LIMIT_FILE_PATH, E as DEFAULT_SECURITY_RATE_LIMIT_REDIS_CONNECTION, F as DEFAULT_SECURITY_RATE_LIMIT_REDIS_PREFIX, e as HoloCorsConfig, H as HoloSecurityConfig, f as HoloSecurityCsrfConfig, g as HoloSecurityRateLimitConfig, a as NormalizedHoloCorsConfig, c as NormalizedHoloSecurityConfig, h as NormalizedHoloSecurityCsrfConfig, i as NormalizedHoloSecurityRateLimitConfig, b as NormalizedSecurityLimiterConfig, G as NormalizedSecurityRateLimitFileConfig, I as NormalizedSecurityRateLimitMemoryConfig, N as NormalizedSecurityRateLimitRedisConfig, d as SecurityLimiterConfig, j as SecurityRateLimitContext, k as SecurityRateLimitDriver, l as SecurityRateLimitFileConfig, S as SecurityRateLimitKeyResolver, m as SecurityRateLimitMemoryConfig, n as SecurityRateLimitRedisConfig, J as SecurityRateLimitRedisConnectionConfig, p as defineCorsConfig, q as defineSecurityConfig, r as holoCorsDefaults, s as holoSecurityDefaults, t as normalizeCorsConfig, u as normalizeSecurityConfig } from './config-BK6q6Yh9.js';
|
package/dist/config.mjs
CHANGED
package/dist/contracts.d.ts
CHANGED
|
@@ -1,12 +1,20 @@
|
|
|
1
|
-
import { HoloSecurityConfig, NormalizedHoloSecurityConfig, HoloCorsConfig, NormalizedHoloCorsConfig, SecurityRateLimitFileConfig, SecurityRateLimitMemoryConfig, SecurityRateLimitRedisConfig, SecurityLimiterConfig, SecurityRateLimitKeyResolver } from './config.js';
|
|
2
|
-
export { HoloSecurityCsrfConfig, HoloSecurityRateLimitConfig, NormalizedHoloSecurityCsrfConfig, NormalizedHoloSecurityRateLimitConfig, NormalizedSecurityLimiterConfig, SecurityRateLimitContext, SecurityRateLimitDriver } from './config.js';
|
|
1
|
+
import { H as HoloSecurityConfig, c as NormalizedHoloSecurityConfig, e as HoloCorsConfig, a as NormalizedHoloCorsConfig, l as SecurityRateLimitFileConfig, m as SecurityRateLimitMemoryConfig, n as SecurityRateLimitRedisConfig, d as SecurityLimiterConfig, S as SecurityRateLimitKeyResolver } from './config-BK6q6Yh9.js';
|
|
2
|
+
export { f as HoloSecurityCsrfConfig, g as HoloSecurityRateLimitConfig, h as NormalizedHoloSecurityCsrfConfig, i as NormalizedHoloSecurityRateLimitConfig, b as NormalizedSecurityLimiterConfig, j as SecurityRateLimitContext, k as SecurityRateLimitDriver } from './config-BK6q6Yh9.js';
|
|
3
3
|
import '@holo-js/kernel';
|
|
4
4
|
|
|
5
|
+
interface SecurityCsrfSessionAccessor {
|
|
6
|
+
read(request: Request): Promise<string | undefined>;
|
|
7
|
+
write(request: Request, token: string): Promise<{
|
|
8
|
+
readonly token: string;
|
|
9
|
+
readonly cookies: readonly string[];
|
|
10
|
+
}>;
|
|
11
|
+
}
|
|
5
12
|
interface SecurityRuntimeBindings {
|
|
6
13
|
readonly config: HoloSecurityConfig | NormalizedHoloSecurityConfig;
|
|
7
14
|
readonly cors?: HoloCorsConfig | NormalizedHoloCorsConfig;
|
|
8
15
|
readonly rateLimitStore?: SecurityRateLimitStore;
|
|
9
16
|
readonly csrfSigningKey?: string;
|
|
17
|
+
readonly csrfSession?: SecurityCsrfSessionAccessor;
|
|
10
18
|
readonly defaultKeyResolver?: SecurityDefaultRateLimitKeyResolver;
|
|
11
19
|
}
|
|
12
20
|
interface SecurityRuntimeFacade {
|
|
@@ -14,11 +22,13 @@ interface SecurityRuntimeFacade {
|
|
|
14
22
|
readonly cors: NormalizedHoloCorsConfig;
|
|
15
23
|
readonly rateLimitStore?: SecurityRateLimitStore;
|
|
16
24
|
readonly csrfSigningKey?: string;
|
|
25
|
+
readonly csrfSession?: SecurityCsrfSessionAccessor;
|
|
17
26
|
readonly defaultKeyResolver?: SecurityDefaultRateLimitKeyResolver;
|
|
18
27
|
}
|
|
19
28
|
interface SecurityClientConfig {
|
|
20
29
|
readonly csrf: {
|
|
21
30
|
readonly field: string;
|
|
31
|
+
readonly header: string;
|
|
22
32
|
readonly cookie: string;
|
|
23
33
|
};
|
|
24
34
|
}
|
|
@@ -125,13 +135,13 @@ declare const limit: Readonly<{
|
|
|
125
135
|
perMinute(maxAttempts: number): PendingSecurityLimiterDefinition<Readonly<Record<string, unknown>> | undefined>;
|
|
126
136
|
perHour(maxAttempts: number): PendingSecurityLimiterDefinition<Readonly<Record<string, unknown>> | undefined>;
|
|
127
137
|
}>;
|
|
128
|
-
declare function ip(request: Request, trustedProxy?: boolean): string;
|
|
129
138
|
declare function defineRateLimiter<TValues extends Readonly<Record<string, unknown>> | undefined = Readonly<Record<string, unknown>> | undefined>(definition: SecurityLimiterConfig<TValues>): SecurityLimiterConfig<TValues>;
|
|
130
139
|
declare function defineSecurityRuntimeBindings(bindings: SecurityRuntimeBindings): Readonly<{
|
|
131
140
|
config: NormalizedHoloSecurityConfig;
|
|
132
141
|
cors: NormalizedHoloCorsConfig;
|
|
133
142
|
rateLimitStore?: SecurityRateLimitStore;
|
|
134
143
|
csrfSigningKey?: string;
|
|
144
|
+
csrfSession?: SecurityCsrfSessionAccessor;
|
|
135
145
|
defaultKeyResolver?: SecurityDefaultRateLimitKeyResolver;
|
|
136
146
|
}>;
|
|
137
147
|
declare function createMemoryRateLimitStoreConfig(config?: SecurityRateLimitMemoryConfig): Readonly<SecurityRateLimitMemoryConfig>;
|
|
@@ -142,4 +152,5 @@ declare const securityInternals: {
|
|
|
142
152
|
normalizeLimiterAttempts: typeof normalizeLimiterAttempts;
|
|
143
153
|
};
|
|
144
154
|
|
|
145
|
-
export { HoloCorsConfig, HoloSecurityConfig, NormalizedHoloCorsConfig, NormalizedHoloSecurityConfig,
|
|
155
|
+
export { HoloCorsConfig, HoloSecurityConfig, NormalizedHoloCorsConfig, NormalizedHoloSecurityConfig, SecurityCsrfError, SecurityLimiterConfig, SecurityRateLimitError, SecurityRateLimitFileConfig, SecurityRateLimitKeyResolver, SecurityRateLimitMemoryConfig, SecurityRateLimitRedisConfig, createFileRateLimitStoreConfig, createMemoryRateLimitStoreConfig, createRedisRateLimitStoreConfig, defineRateLimiter, defineSecurityRuntimeBindings, limit, securityInternals };
|
|
156
|
+
export type { SecurityClearRateLimitOptions, SecurityClientConfig, SecurityCorsFacade, SecurityCsrfFacade, SecurityCsrfField, SecurityCsrfInput, SecurityCsrfSessionAccessor, SecurityDefaultRateLimitKeyResolver, SecurityProtectOptions, SecurityRateLimitBucketSnapshot, SecurityRateLimitCallOptions, SecurityRateLimitHitResult, SecurityRateLimitRedisDriverAdapter, SecurityRateLimitStore, SecurityRateLimitStoreFactoryOptions, SecurityRuntimeBindings, SecurityRuntimeFacade };
|
package/dist/contracts.mjs
CHANGED
|
@@ -6,11 +6,10 @@ import {
|
|
|
6
6
|
createRedisRateLimitStoreConfig,
|
|
7
7
|
defineRateLimiter,
|
|
8
8
|
defineSecurityRuntimeBindings,
|
|
9
|
-
ip,
|
|
10
9
|
limit,
|
|
11
10
|
securityInternals
|
|
12
|
-
} from "./chunk-
|
|
13
|
-
import "./chunk-
|
|
11
|
+
} from "./chunk-U25HSHLJ.mjs";
|
|
12
|
+
import "./chunk-D3ZN5MLJ.mjs";
|
|
14
13
|
export {
|
|
15
14
|
SecurityCsrfError,
|
|
16
15
|
SecurityRateLimitError,
|
|
@@ -19,7 +18,6 @@ export {
|
|
|
19
18
|
createRedisRateLimitStoreConfig,
|
|
20
19
|
defineRateLimiter,
|
|
21
20
|
defineSecurityRuntimeBindings,
|
|
22
|
-
ip,
|
|
23
21
|
limit,
|
|
24
22
|
securityInternals
|
|
25
23
|
};
|
|
@@ -2,9 +2,6 @@ import { NormalizedSecurityRateLimitRedisConfig } from '../config.js';
|
|
|
2
2
|
import { SecurityRateLimitRedisDriverAdapter } from '../contracts.js';
|
|
3
3
|
import '@holo-js/kernel';
|
|
4
4
|
|
|
5
|
-
interface SecurityRedisAdapterOptions {
|
|
6
|
-
readonly now?: () => Date;
|
|
7
|
-
}
|
|
8
5
|
type RedisClientOptions = {
|
|
9
6
|
readonly host?: string;
|
|
10
7
|
readonly port?: number;
|
|
@@ -35,16 +32,14 @@ declare function resolveClusterStartupNodes(config: NormalizedSecurityRateLimitR
|
|
|
35
32
|
declare function createRedisClusterOptions(config: NormalizedSecurityRateLimitRedisConfig): RedisClusterOptions;
|
|
36
33
|
declare class RedisSecurityAdapter implements SecurityRateLimitRedisDriverAdapter {
|
|
37
34
|
private readonly client;
|
|
38
|
-
private readonly now;
|
|
39
35
|
private readonly prefix;
|
|
40
|
-
constructor(config: NormalizedSecurityRateLimitRedisConfig
|
|
36
|
+
constructor(config: NormalizedSecurityRateLimitRedisConfig);
|
|
41
37
|
private qualifyKey;
|
|
42
38
|
private qualifyPattern;
|
|
43
39
|
private normalizeScanResponse;
|
|
44
40
|
private clearMatchingKeysForClient;
|
|
45
41
|
private clearMatchingKeysForCluster;
|
|
46
42
|
private clearMatchingKeys;
|
|
47
|
-
private parseOldestScore;
|
|
48
43
|
private getCommandValue;
|
|
49
44
|
connect(): Promise<void>;
|
|
50
45
|
increment(key: string, options: {
|
|
@@ -58,7 +53,7 @@ declare class RedisSecurityAdapter implements SecurityRateLimitRedisDriverAdapte
|
|
|
58
53
|
clearAll(): Promise<number>;
|
|
59
54
|
close(): Promise<void>;
|
|
60
55
|
}
|
|
61
|
-
declare function createSecurityRedisAdapter(config: NormalizedSecurityRateLimitRedisConfig
|
|
56
|
+
declare function createSecurityRedisAdapter(config: NormalizedSecurityRateLimitRedisConfig): RedisSecurityAdapter;
|
|
62
57
|
declare const securityRedisAdapterInternals: {
|
|
63
58
|
REDIS_SCAN_COUNT: number;
|
|
64
59
|
RedisSecurityAdapter: typeof RedisSecurityAdapter;
|
|
@@ -72,4 +67,4 @@ declare const securityRedisAdapterInternals: {
|
|
|
72
67
|
toRedisSocketPath: typeof toRedisSocketPath;
|
|
73
68
|
};
|
|
74
69
|
|
|
75
|
-
export { RedisSecurityAdapter,
|
|
70
|
+
export { RedisSecurityAdapter, createSecurityRedisAdapter, securityRedisAdapterInternals };
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
// src/drivers/redis-adapter.ts
|
|
2
|
-
import { randomUUID } from "crypto";
|
|
3
2
|
import Redis from "ioredis";
|
|
4
3
|
var REDIS_SCAN_COUNT = 100;
|
|
5
4
|
function isRedisClusterClientLike(client) {
|
|
@@ -46,7 +45,7 @@ function parseClusterNodeUrl(url, label) {
|
|
|
46
45
|
...parsed.protocol === "rediss:" ? { tls: {} } : {}
|
|
47
46
|
};
|
|
48
47
|
} catch (error) {
|
|
49
|
-
throw new Error(`[@holo-js/security] ${label} is invalid: ${error instanceof Error ? error.message : String(error)}
|
|
48
|
+
throw new Error(`[@holo-js/security] ${label} is invalid: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
50
49
|
}
|
|
51
50
|
}
|
|
52
51
|
function resolveClusterStartupNodes(config) {
|
|
@@ -82,11 +81,9 @@ function createRedisClusterOptions(config) {
|
|
|
82
81
|
}
|
|
83
82
|
var RedisSecurityAdapter = class {
|
|
84
83
|
client;
|
|
85
|
-
now;
|
|
86
84
|
prefix;
|
|
87
|
-
constructor(config
|
|
85
|
+
constructor(config) {
|
|
88
86
|
this.prefix = config.prefix;
|
|
89
|
-
this.now = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
90
87
|
const clientOptions = createRedisClientOptions(config);
|
|
91
88
|
const RedisConstructor = Redis;
|
|
92
89
|
this.client = typeof config.url === "string" ? new RedisConstructor(config.url, clientOptions) : config.clusters && config.clusters.length > 0 ? new RedisConstructor.Cluster(resolveClusterStartupNodes(config), createRedisClusterOptions(config)) : isRedisConnectionTarget(config.connection) ? new RedisConstructor(config.connection, clientOptions) : new RedisConstructor(clientOptions);
|
|
@@ -154,17 +151,6 @@ var RedisSecurityAdapter = class {
|
|
|
154
151
|
}
|
|
155
152
|
return await this.clearMatchingKeysForClient(this.client, pattern);
|
|
156
153
|
}
|
|
157
|
-
parseOldestScore(result) {
|
|
158
|
-
if (!Array.isArray(result) || result.length < 2) {
|
|
159
|
-
throw new Error("[@holo-js/security] Redis transaction failed to return the oldest rate-limit hit.");
|
|
160
|
-
}
|
|
161
|
-
const value = result[1];
|
|
162
|
-
const score = typeof value === "number" ? value : typeof value === "string" ? Number.parseInt(value, 10) : Number.NaN;
|
|
163
|
-
if (!Number.isFinite(score)) {
|
|
164
|
-
throw new Error("[@holo-js/security] Redis transaction returned an invalid oldest-hit score.");
|
|
165
|
-
}
|
|
166
|
-
return score;
|
|
167
|
-
}
|
|
168
154
|
getCommandValue(result, index, operation) {
|
|
169
155
|
const entry = result[index];
|
|
170
156
|
if (!entry) {
|
|
@@ -183,23 +169,21 @@ var RedisSecurityAdapter = class {
|
|
|
183
169
|
await this.client.connect();
|
|
184
170
|
}
|
|
185
171
|
async increment(key, options) {
|
|
186
|
-
const now = this.now().getTime();
|
|
187
|
-
const ttlMs = options.decaySeconds * 1e3;
|
|
188
172
|
const qualifiedKey = this.qualifyKey(key);
|
|
189
|
-
const
|
|
190
|
-
const result = await this.client.multi().zadd(qualifiedKey, now, member).zremrangebyscore(qualifiedKey, "-inf", now - ttlMs).zcard(qualifiedKey).zrange(qualifiedKey, 0, 0, "WITHSCORES").exec();
|
|
173
|
+
const result = await this.client.multi().set(qualifiedKey, 0, "PX", options.decaySeconds * 1e3, "NX").incr(qualifiedKey).pttl(qualifiedKey).exec();
|
|
191
174
|
if (!result) {
|
|
192
175
|
throw new Error("[@holo-js/security] Redis transaction failed for increment.");
|
|
193
176
|
}
|
|
194
|
-
const
|
|
195
|
-
|
|
177
|
+
const replies = result;
|
|
178
|
+
const attempts = this.getCommandValue(replies, 1, "attempt count");
|
|
179
|
+
if (typeof attempts !== "number") {
|
|
196
180
|
throw new Error("[@holo-js/security] Redis transaction returned an invalid attempt count.");
|
|
197
181
|
}
|
|
198
|
-
const
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
return { attempts, ttlSeconds };
|
|
182
|
+
const remainingMs = this.getCommandValue(replies, 2, "window time to live");
|
|
183
|
+
if (typeof remainingMs !== "number" || remainingMs < 0) {
|
|
184
|
+
throw new Error("[@holo-js/security] Redis transaction returned an invalid window time to live.");
|
|
185
|
+
}
|
|
186
|
+
return { attempts, ttlSeconds: Math.ceil(remainingMs / 1e3) };
|
|
203
187
|
}
|
|
204
188
|
async del(key) {
|
|
205
189
|
return this.client.del(this.qualifyKey(key));
|
|
@@ -220,8 +204,8 @@ var RedisSecurityAdapter = class {
|
|
|
220
204
|
}
|
|
221
205
|
}
|
|
222
206
|
};
|
|
223
|
-
function createSecurityRedisAdapter(config
|
|
224
|
-
return new RedisSecurityAdapter(config
|
|
207
|
+
function createSecurityRedisAdapter(config) {
|
|
208
|
+
return new RedisSecurityAdapter(config);
|
|
225
209
|
}
|
|
226
210
|
var securityRedisAdapterInternals = {
|
|
227
211
|
REDIS_SCAN_COUNT,
|