@secfn/core 0.0.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/README.md +334 -0
- package/dist/access-control/rbac.d.ts +48 -0
- package/dist/access-control/rbac.d.ts.map +1 -0
- package/dist/access-control/rbac.js +203 -0
- package/dist/access-control/rbac.js.map +1 -0
- package/dist/core/secfn.d.ts +28 -0
- package/dist/core/secfn.d.ts.map +1 -0
- package/dist/core/secfn.js +73 -0
- package/dist/core/secfn.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/dist/monitoring/security-monitor.d.ts +46 -0
- package/dist/monitoring/security-monitor.d.ts.map +1 -0
- package/dist/monitoring/security-monitor.js +172 -0
- package/dist/monitoring/security-monitor.js.map +1 -0
- package/dist/rate-limiting/rate-limiter.d.ts +31 -0
- package/dist/rate-limiting/rate-limiter.d.ts.map +1 -0
- package/dist/rate-limiting/rate-limiter.js +140 -0
- package/dist/rate-limiting/rate-limiter.js.map +1 -0
- package/dist/scanning/patterns.d.ts +3 -0
- package/dist/scanning/patterns.d.ts.map +1 -0
- package/dist/scanning/patterns.js +139 -0
- package/dist/scanning/patterns.js.map +1 -0
- package/dist/scanning/scanner.d.ts +36 -0
- package/dist/scanning/scanner.d.ts.map +1 -0
- package/dist/scanning/scanner.js +152 -0
- package/dist/scanning/scanner.js.map +1 -0
- package/dist/secrets/encryption.d.ts +17 -0
- package/dist/secrets/encryption.d.ts.map +1 -0
- package/dist/secrets/encryption.js +47 -0
- package/dist/secrets/encryption.js.map +1 -0
- package/dist/secrets/vault.d.ts +43 -0
- package/dist/secrets/vault.d.ts.map +1 -0
- package/dist/secrets/vault.js +126 -0
- package/dist/secrets/vault.js.map +1 -0
- package/dist/storage/indexeddb-storage.d.ts +37 -0
- package/dist/storage/indexeddb-storage.d.ts.map +1 -0
- package/dist/storage/indexeddb-storage.js +284 -0
- package/dist/storage/indexeddb-storage.js.map +1 -0
- package/dist/types/index.d.ts +189 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +2 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/helpers.d.ts +8 -0
- package/dist/utils/helpers.d.ts.map +1 -0
- package/dist/utils/helpers.js +73 -0
- package/dist/utils/helpers.js.map +1 -0
- package/examples/basic-usage.ts +143 -0
- package/package.json +68 -0
- package/src/access-control/rbac.ts +271 -0
- package/src/core/secfn.ts +90 -0
- package/src/index.ts +27 -0
- package/src/monitoring/security-monitor.ts +245 -0
- package/src/rate-limiting/rate-limiter.ts +181 -0
- package/src/scanning/patterns.ts +140 -0
- package/src/scanning/scanner.ts +199 -0
- package/src/secrets/encryption.ts +74 -0
- package/src/secrets/vault.ts +187 -0
- package/src/storage/indexeddb-storage.ts +355 -0
- package/src/types/index.ts +213 -0
- package/src/utils/helpers.ts +84 -0
- package/tsconfig.json +26 -0
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { createSecFn } from '../src/index.js';
|
|
2
|
+
|
|
3
|
+
async function main() {
|
|
4
|
+
const secFn = createSecFn({
|
|
5
|
+
namespace: 'my-app-security',
|
|
6
|
+
masterKey: process.env.MASTER_KEY || 'dev-master-key-change-in-production',
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
await secFn.initialize();
|
|
10
|
+
console.log('ā
SecFn initialized');
|
|
11
|
+
|
|
12
|
+
const vault = secFn.createSecretsVault();
|
|
13
|
+
const scanner = secFn.createSecretScanner();
|
|
14
|
+
const access = secFn.createAccessControl();
|
|
15
|
+
const limiter = secFn.createRateLimiter({
|
|
16
|
+
rules: {
|
|
17
|
+
global: { requests: 1000, window: 60000 },
|
|
18
|
+
perUser: { requests: 100, window: 60000 },
|
|
19
|
+
perIP: { requests: 50, window: 60000 },
|
|
20
|
+
perEndpoint: {
|
|
21
|
+
'/api/login': { requests: 5, window: 300000 },
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
const monitoring = secFn.createMonitoring({
|
|
26
|
+
anomalyDetection: {
|
|
27
|
+
enabled: true,
|
|
28
|
+
sensitivity: 0.7,
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
console.log('\nš¦ Testing Secrets Vault...');
|
|
33
|
+
await vault.set('database_url', 'postgres://user:pass@localhost:5432/db', {
|
|
34
|
+
tags: ['database', 'production'],
|
|
35
|
+
environment: 'production',
|
|
36
|
+
createdBy: 'admin',
|
|
37
|
+
});
|
|
38
|
+
console.log('ā Secret stored');
|
|
39
|
+
|
|
40
|
+
const dbUrl = await vault.get('database_url');
|
|
41
|
+
console.log('ā Secret retrieved:', dbUrl?.substring(0, 20) + '...');
|
|
42
|
+
|
|
43
|
+
const secrets = await vault.list({ environment: 'production' });
|
|
44
|
+
console.log(`ā Found ${secrets.length} production secrets`);
|
|
45
|
+
|
|
46
|
+
console.log('\nš Testing Secret Scanner...');
|
|
47
|
+
const scanResults = scanner.scanContent(
|
|
48
|
+
`
|
|
49
|
+
const apiKey = 'sk-ant-1234567890abcdef';
|
|
50
|
+
const awsKey = 'AKIAIOSFODNN7EXAMPLE';
|
|
51
|
+
const password = 'super-secret-password';
|
|
52
|
+
`,
|
|
53
|
+
'test.ts'
|
|
54
|
+
);
|
|
55
|
+
console.log(`ā Found ${scanResults.length} secrets in code:`);
|
|
56
|
+
scanResults.forEach(r => {
|
|
57
|
+
console.log(` - ${r.pattern} at line ${r.line}: ${r.redactedMatch}`);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
console.log('\nš Testing Access Control...');
|
|
61
|
+
const adminRole = await access.createRole({
|
|
62
|
+
name: 'admin',
|
|
63
|
+
description: 'Full system access',
|
|
64
|
+
permissions: ['*:*'],
|
|
65
|
+
});
|
|
66
|
+
console.log('ā Admin role created');
|
|
67
|
+
|
|
68
|
+
const editorRole = await access.createRole({
|
|
69
|
+
name: 'editor',
|
|
70
|
+
description: 'Can edit projects',
|
|
71
|
+
permissions: ['project:read', 'project:write', 'file:read', 'file:write'],
|
|
72
|
+
});
|
|
73
|
+
console.log('ā Editor role created');
|
|
74
|
+
|
|
75
|
+
await access.assignRole('user_123', editorRole);
|
|
76
|
+
console.log('ā Role assigned to user');
|
|
77
|
+
|
|
78
|
+
const canEdit = await access.check({
|
|
79
|
+
userId: 'user_123',
|
|
80
|
+
action: 'project:write',
|
|
81
|
+
});
|
|
82
|
+
console.log(`ā User can edit projects: ${canEdit}`);
|
|
83
|
+
|
|
84
|
+
const canDelete = await access.check({
|
|
85
|
+
userId: 'user_123',
|
|
86
|
+
action: 'project:delete',
|
|
87
|
+
});
|
|
88
|
+
console.log(`ā User can delete projects: ${canDelete}`);
|
|
89
|
+
|
|
90
|
+
console.log('\nā±ļø Testing Rate Limiting...');
|
|
91
|
+
for (let i = 0; i < 3; i++) {
|
|
92
|
+
const result = await limiter.check({
|
|
93
|
+
userId: 'user_456',
|
|
94
|
+
ip: '192.168.1.1',
|
|
95
|
+
endpoint: '/api/search',
|
|
96
|
+
});
|
|
97
|
+
console.log(` Request ${i + 1}: ${result.allowed ? 'ā Allowed' : 'ā Blocked'} (remaining: ${result.remaining})`);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
console.log('\nš Testing Security Monitoring...');
|
|
101
|
+
await monitoring.logEvent({
|
|
102
|
+
type: 'auth_failure',
|
|
103
|
+
severity: 'medium',
|
|
104
|
+
userId: 'user_789',
|
|
105
|
+
ip: '192.168.1.100',
|
|
106
|
+
resource: '/api/login',
|
|
107
|
+
action: 'login',
|
|
108
|
+
metadata: {
|
|
109
|
+
reason: 'invalid_password',
|
|
110
|
+
attemptCount: 3,
|
|
111
|
+
},
|
|
112
|
+
});
|
|
113
|
+
console.log('ā Security event logged');
|
|
114
|
+
|
|
115
|
+
await monitoring.logEvent({
|
|
116
|
+
type: 'permission_denied',
|
|
117
|
+
severity: 'low',
|
|
118
|
+
userId: 'user_789',
|
|
119
|
+
ip: '192.168.1.100',
|
|
120
|
+
resource: '/api/admin',
|
|
121
|
+
action: 'access',
|
|
122
|
+
});
|
|
123
|
+
console.log('ā Another security event logged');
|
|
124
|
+
|
|
125
|
+
const recentEvents = await monitoring.getRecentEvents(10);
|
|
126
|
+
console.log(`ā Retrieved ${recentEvents.length} recent events`);
|
|
127
|
+
|
|
128
|
+
const metrics = await monitoring.getMetrics({
|
|
129
|
+
start: Date.now() - 24 * 60 * 60 * 1000,
|
|
130
|
+
end: Date.now(),
|
|
131
|
+
});
|
|
132
|
+
console.log('ā Metrics calculated:');
|
|
133
|
+
console.log(` - Total events: ${metrics.totalEvents}`);
|
|
134
|
+
console.log(` - Events by type:`, metrics.eventsByType);
|
|
135
|
+
console.log(` - Events by severity:`, metrics.eventsBySeverity);
|
|
136
|
+
|
|
137
|
+
console.log('\n⨠All tests completed successfully!');
|
|
138
|
+
|
|
139
|
+
await secFn.destroy();
|
|
140
|
+
console.log('š§¹ Cleanup complete');
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
main().catch(console.error);
|
package/package.json
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@secfn/core",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Comprehensive self-hosted security platform for developers",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"scripts": {
|
|
9
|
+
"build": "tsc",
|
|
10
|
+
"build:watch": "tsc --watch",
|
|
11
|
+
"test": "vitest",
|
|
12
|
+
"test:watch": "vitest --watch",
|
|
13
|
+
"test:coverage": "vitest --coverage",
|
|
14
|
+
"lint": "eslint src/**/*.ts",
|
|
15
|
+
"typecheck": "tsc --noEmit"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"security",
|
|
19
|
+
"secrets",
|
|
20
|
+
"scanning",
|
|
21
|
+
"rbac",
|
|
22
|
+
"rate-limiting",
|
|
23
|
+
"access-control",
|
|
24
|
+
"vault",
|
|
25
|
+
"monitoring",
|
|
26
|
+
"self-hosted"
|
|
27
|
+
],
|
|
28
|
+
"author": "21n",
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"glob": "^11.0.0",
|
|
32
|
+
"simple-git": "^3.27.0"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@types/node": "^20.0.0",
|
|
36
|
+
"typescript": "^5.6.0",
|
|
37
|
+
"vitest": "^3.2.4",
|
|
38
|
+
"fake-indexeddb": "^6.0.0",
|
|
39
|
+
"@vitest/coverage-v8": "^3.2.4",
|
|
40
|
+
"eslint": "^9.0.0",
|
|
41
|
+
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
|
42
|
+
"@typescript-eslint/parser": "^8.0.0"
|
|
43
|
+
},
|
|
44
|
+
"peerDependencies": {
|
|
45
|
+
"@superfunctions/db": "^0.1.0"
|
|
46
|
+
},
|
|
47
|
+
"peerDependenciesMeta": {
|
|
48
|
+
"@superfunctions/db": {
|
|
49
|
+
"optional": true
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
"bugs": {
|
|
53
|
+
"url": "https://github.com/21nCo/super-functions/issues"
|
|
54
|
+
},
|
|
55
|
+
"repository": {
|
|
56
|
+
"type": "git",
|
|
57
|
+
"url": "git+https://github.com/21nCo/super-functions.git",
|
|
58
|
+
"directory": "secfn/typescript"
|
|
59
|
+
},
|
|
60
|
+
"publishConfig": {
|
|
61
|
+
"access": "public"
|
|
62
|
+
},
|
|
63
|
+
"superfunctions": {
|
|
64
|
+
"initFunction": "secFn",
|
|
65
|
+
"schemaVersion": 1,
|
|
66
|
+
"namespace": "secfn"
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
import type { Role, UserRole, AccessRequest } from '../types/index.js';
|
|
2
|
+
import type { IndexedDBStorage } from '../storage/indexeddb-storage.js';
|
|
3
|
+
import { generateId } from '../utils/helpers.js';
|
|
4
|
+
|
|
5
|
+
export interface AccessControlConfig {
|
|
6
|
+
storage: IndexedDBStorage;
|
|
7
|
+
cache?: {
|
|
8
|
+
ttl: number;
|
|
9
|
+
maxSize: number;
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface CreateRoleOptions {
|
|
14
|
+
name: string;
|
|
15
|
+
description: string;
|
|
16
|
+
permissions: string[];
|
|
17
|
+
inherits?: string[];
|
|
18
|
+
metadata?: Record<string, unknown>;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface AssignRoleOptions {
|
|
22
|
+
resourceIds?: string[];
|
|
23
|
+
expiresAt?: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface CacheEntry {
|
|
27
|
+
allowed: boolean;
|
|
28
|
+
timestamp: number;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export class AccessControl {
|
|
32
|
+
private storage: IndexedDBStorage;
|
|
33
|
+
private cache: Map<string, CacheEntry>;
|
|
34
|
+
private cacheTTL: number;
|
|
35
|
+
private maxCacheSize: number;
|
|
36
|
+
|
|
37
|
+
constructor(config: AccessControlConfig) {
|
|
38
|
+
this.storage = config.storage;
|
|
39
|
+
this.cache = new Map();
|
|
40
|
+
this.cacheTTL = config.cache?.ttl || 300000;
|
|
41
|
+
this.maxCacheSize = config.cache?.maxSize || 1000;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async createRole(options: CreateRoleOptions): Promise<string> {
|
|
45
|
+
const role: Role = {
|
|
46
|
+
id: generateId('role'),
|
|
47
|
+
name: options.name,
|
|
48
|
+
description: options.description,
|
|
49
|
+
permissions: options.permissions,
|
|
50
|
+
inherits: options.inherits,
|
|
51
|
+
metadata: options.metadata,
|
|
52
|
+
createdAt: Date.now(),
|
|
53
|
+
updatedAt: Date.now(),
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
await this.storage.saveRole(role);
|
|
57
|
+
return role.id;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async getRole(id: string): Promise<Role | null> {
|
|
61
|
+
return await this.storage.getRole(id);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async getRoleByName(name: string): Promise<Role | null> {
|
|
65
|
+
const roles = await this.storage.listRoles();
|
|
66
|
+
return roles.find(r => r.name === name) || null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async listRoles(): Promise<Role[]> {
|
|
70
|
+
return await this.storage.listRoles();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async updateRole(id: string, updates: Partial<CreateRoleOptions>): Promise<void> {
|
|
74
|
+
const role = await this.storage.getRole(id);
|
|
75
|
+
if (!role) {
|
|
76
|
+
throw new Error(`Role ${id} not found`);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (updates.name) role.name = updates.name;
|
|
80
|
+
if (updates.description) role.description = updates.description;
|
|
81
|
+
if (updates.permissions) role.permissions = updates.permissions;
|
|
82
|
+
if (updates.inherits) role.inherits = updates.inherits;
|
|
83
|
+
if (updates.metadata) role.metadata = updates.metadata;
|
|
84
|
+
role.updatedAt = Date.now();
|
|
85
|
+
|
|
86
|
+
await this.storage.saveRole(role);
|
|
87
|
+
this.invalidateCache();
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async deleteRole(id: string): Promise<void> {
|
|
91
|
+
await this.storage.deleteRole(id);
|
|
92
|
+
this.invalidateCache();
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async assignRole(userId: string, roleId: string, options: AssignRoleOptions = {}): Promise<void> {
|
|
96
|
+
const role = await this.storage.getRole(roleId);
|
|
97
|
+
if (!role) {
|
|
98
|
+
throw new Error(`Role ${roleId} not found`);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const userRole: UserRole = {
|
|
102
|
+
userId,
|
|
103
|
+
roleId,
|
|
104
|
+
resourceIds: options.resourceIds,
|
|
105
|
+
expiresAt: options.expiresAt,
|
|
106
|
+
assignedAt: Date.now(),
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
await this.storage.assignUserRole(userRole);
|
|
110
|
+
this.invalidateCacheForUser(userId);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async removeRole(userId: string, roleId: string): Promise<void> {
|
|
114
|
+
await this.storage.removeUserRole(userId, roleId);
|
|
115
|
+
this.invalidateCacheForUser(userId);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async getUserRoles(userId: string): Promise<Role[]> {
|
|
119
|
+
const userRoles = await this.storage.getUserRoles(userId);
|
|
120
|
+
const roles: Role[] = [];
|
|
121
|
+
|
|
122
|
+
for (const userRole of userRoles) {
|
|
123
|
+
if (userRole.expiresAt && Date.now() > userRole.expiresAt) {
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const role = await this.storage.getRole(userRole.roleId);
|
|
128
|
+
if (role) {
|
|
129
|
+
roles.push(role);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return roles;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
async getUserPermissions(userId: string): Promise<string[]> {
|
|
137
|
+
const roles = await this.getUserRoles(userId);
|
|
138
|
+
const permissions = new Set<string>();
|
|
139
|
+
|
|
140
|
+
for (const role of roles) {
|
|
141
|
+
await this.collectPermissions(role, permissions);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return Array.from(permissions);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
async check(request: AccessRequest): Promise<boolean> {
|
|
148
|
+
const cacheKey = this.getCacheKey(request);
|
|
149
|
+
const cached = this.getCached(cacheKey);
|
|
150
|
+
|
|
151
|
+
if (cached !== null) {
|
|
152
|
+
return cached;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const allowed = await this.evaluateAccess(request);
|
|
156
|
+
this.setCached(cacheKey, allowed);
|
|
157
|
+
|
|
158
|
+
return allowed;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
private async evaluateAccess(request: AccessRequest): Promise<boolean> {
|
|
162
|
+
const userRoles = await this.storage.getUserRoles(request.userId);
|
|
163
|
+
|
|
164
|
+
for (const userRole of userRoles) {
|
|
165
|
+
if (userRole.expiresAt && Date.now() > userRole.expiresAt) {
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (userRole.resourceIds && request.resourceId) {
|
|
170
|
+
if (!userRole.resourceIds.includes(request.resourceId)) {
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const role = await this.storage.getRole(userRole.roleId);
|
|
176
|
+
if (!role) continue;
|
|
177
|
+
|
|
178
|
+
const hasPermission = await this.checkRolePermission(role, request);
|
|
179
|
+
if (hasPermission) {
|
|
180
|
+
return true;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return false;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
private async checkRolePermission(role: Role, request: AccessRequest): Promise<boolean> {
|
|
188
|
+
const allPermissions = new Set<string>();
|
|
189
|
+
await this.collectPermissions(role, allPermissions);
|
|
190
|
+
|
|
191
|
+
for (const permission of allPermissions) {
|
|
192
|
+
if (this.matchesPermission(permission, request.action)) {
|
|
193
|
+
return true;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return false;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
private async collectPermissions(role: Role, collected: Set<string>): Promise<void> {
|
|
201
|
+
for (const permission of role.permissions) {
|
|
202
|
+
collected.add(permission);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (role.inherits) {
|
|
206
|
+
for (const inheritedRoleId of role.inherits) {
|
|
207
|
+
const inheritedRole = await this.storage.getRole(inheritedRoleId);
|
|
208
|
+
if (inheritedRole) {
|
|
209
|
+
await this.collectPermissions(inheritedRole, collected);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
private matchesPermission(permission: string, action: string): boolean {
|
|
216
|
+
if (permission === '*:*') return true;
|
|
217
|
+
|
|
218
|
+
const [permResource, permAction] = permission.split(':');
|
|
219
|
+
const [reqResource, reqAction] = action.split(':');
|
|
220
|
+
|
|
221
|
+
if (!permResource || !permAction || !reqResource || !reqAction) {
|
|
222
|
+
return false;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const resourceMatch = permResource === '*' || permResource === reqResource;
|
|
226
|
+
const actionMatch = permAction === '*' || permAction === reqAction;
|
|
227
|
+
|
|
228
|
+
return resourceMatch && actionMatch;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
private getCacheKey(request: AccessRequest): string {
|
|
232
|
+
return `${request.userId}:${request.action}:${request.resourceId || '*'}`;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
private getCached(key: string): boolean | null {
|
|
236
|
+
const entry = this.cache.get(key);
|
|
237
|
+
if (!entry) return null;
|
|
238
|
+
|
|
239
|
+
const age = Date.now() - entry.timestamp;
|
|
240
|
+
if (age > this.cacheTTL) {
|
|
241
|
+
this.cache.delete(key);
|
|
242
|
+
return null;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
return entry.allowed;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
private setCached(key: string, allowed: boolean): void {
|
|
249
|
+
if (this.cache.size >= this.maxCacheSize) {
|
|
250
|
+
const firstKey = this.cache.keys().next().value;
|
|
251
|
+
if (firstKey) this.cache.delete(firstKey);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
this.cache.set(key, {
|
|
255
|
+
allowed,
|
|
256
|
+
timestamp: Date.now(),
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
private invalidateCache(): void {
|
|
261
|
+
this.cache.clear();
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
private invalidateCacheForUser(userId: string): void {
|
|
265
|
+
for (const [key] of this.cache) {
|
|
266
|
+
if (key.startsWith(`${userId}:`)) {
|
|
267
|
+
this.cache.delete(key);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { IndexedDBStorage } from '../storage/indexeddb-storage.js';
|
|
2
|
+
import { SecretsVault, type SecretsVaultConfig } from '../secrets/vault.js';
|
|
3
|
+
import { SecretScanner, type SecretScannerConfig } from '../scanning/scanner.js';
|
|
4
|
+
import { AccessControl, type AccessControlConfig } from '../access-control/rbac.js';
|
|
5
|
+
import { RateLimiter, type RateLimiterConfig } from '../rate-limiting/rate-limiter.js';
|
|
6
|
+
import { SecurityMonitor, type MonitoringConfig } from '../monitoring/security-monitor.js';
|
|
7
|
+
import type { StorageConfig } from '../types/index.js';
|
|
8
|
+
|
|
9
|
+
export interface SecFnConfig {
|
|
10
|
+
storage?: StorageConfig;
|
|
11
|
+
masterKey?: string;
|
|
12
|
+
namespace?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export class SecFn {
|
|
16
|
+
private storage: IndexedDBStorage;
|
|
17
|
+
private masterKey?: string;
|
|
18
|
+
private initialized: boolean = false;
|
|
19
|
+
|
|
20
|
+
constructor(config: SecFnConfig = {}) {
|
|
21
|
+
const dbName = config.storage?.dbName || config.namespace || 'secfn';
|
|
22
|
+
this.storage = new IndexedDBStorage(dbName);
|
|
23
|
+
this.masterKey = config.masterKey;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async initialize(): Promise<void> {
|
|
27
|
+
if (this.initialized) return;
|
|
28
|
+
await this.storage.initialize();
|
|
29
|
+
this.initialized = true;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
createSecretsVault(config?: Partial<Omit<SecretsVaultConfig, 'storage'>>): SecretsVault {
|
|
33
|
+
if (!this.masterKey && !config?.encryption?.masterKey) {
|
|
34
|
+
throw new Error('Master key is required for secrets vault');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return new SecretsVault({
|
|
38
|
+
storage: this.storage,
|
|
39
|
+
encryption: {
|
|
40
|
+
algorithm: 'AES-256-GCM',
|
|
41
|
+
masterKey: config?.encryption?.masterKey || this.masterKey!,
|
|
42
|
+
...config?.encryption,
|
|
43
|
+
},
|
|
44
|
+
rotation: config?.rotation,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
createSecretScanner(config?: Partial<Omit<SecretScannerConfig, 'storage'>>): SecretScanner {
|
|
49
|
+
return new SecretScanner({
|
|
50
|
+
...config,
|
|
51
|
+
storage: this.storage,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
createAccessControl(config?: Partial<Omit<AccessControlConfig, 'storage'>>): AccessControl {
|
|
56
|
+
return new AccessControl({
|
|
57
|
+
storage: this.storage,
|
|
58
|
+
cache: config?.cache,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
createRateLimiter(config: Omit<RateLimiterConfig, 'storage'>): RateLimiter {
|
|
63
|
+
return new RateLimiter({
|
|
64
|
+
storage: this.storage,
|
|
65
|
+
rules: config.rules,
|
|
66
|
+
onLimitExceeded: config.onLimitExceeded,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
createMonitoring(config?: Partial<Omit<MonitoringConfig, 'storage'>>): SecurityMonitor {
|
|
71
|
+
return new SecurityMonitor({
|
|
72
|
+
storage: this.storage,
|
|
73
|
+
retention: config?.retention,
|
|
74
|
+
anomalyDetection: config?.anomalyDetection,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async destroy(): Promise<void> {
|
|
79
|
+
await this.storage.destroy();
|
|
80
|
+
this.initialized = false;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
getStorage(): IndexedDBStorage {
|
|
84
|
+
return this.storage;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function createSecFn(config?: SecFnConfig): SecFn {
|
|
89
|
+
return new SecFn(config);
|
|
90
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export { SecFn, createSecFn, type SecFnConfig } from './core/secfn.js';
|
|
2
|
+
|
|
3
|
+
export { SecretsVault, type SecretsVaultConfig, type SetSecretOptions } from './secrets/vault.js';
|
|
4
|
+
export { SecretEncryption } from './secrets/encryption.js';
|
|
5
|
+
|
|
6
|
+
export { SecretScanner, type SecretScannerConfig, type ScanOptions, type GitScanOptions } from './scanning/scanner.js';
|
|
7
|
+
export { DEFAULT_PATTERNS } from './scanning/patterns.js';
|
|
8
|
+
|
|
9
|
+
export { AccessControl, type AccessControlConfig, type CreateRoleOptions, type AssignRoleOptions } from './access-control/rbac.js';
|
|
10
|
+
|
|
11
|
+
export { RateLimiter, type RateLimiterConfig, type RateLimitCheckRequest } from './rate-limiting/rate-limiter.js';
|
|
12
|
+
|
|
13
|
+
export { SecurityMonitor, type MonitoringConfig, type LogEventOptions, type ResolveEventOptions } from './monitoring/security-monitor.js';
|
|
14
|
+
|
|
15
|
+
export { IndexedDBStorage } from './storage/indexeddb-storage.js';
|
|
16
|
+
|
|
17
|
+
export * from './types/index.js';
|
|
18
|
+
|
|
19
|
+
export {
|
|
20
|
+
generateId,
|
|
21
|
+
calculateEntropy,
|
|
22
|
+
redactSecret,
|
|
23
|
+
isExpired,
|
|
24
|
+
delay,
|
|
25
|
+
sanitizeForLog,
|
|
26
|
+
parseTimeWindow,
|
|
27
|
+
} from './utils/helpers.js';
|