@qwickapps/server 1.5.1 → 1.6.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/CHANGELOG.md +43 -0
- package/dist/core/control-panel.d.ts.map +1 -1
- package/dist/core/control-panel.js +41 -0
- package/dist/core/control-panel.js.map +1 -1
- package/dist/core/guards.d.ts.map +1 -1
- package/dist/core/guards.js +77 -0
- package/dist/core/guards.js.map +1 -1
- package/dist/core/health-manager.d.ts +4 -0
- package/dist/core/health-manager.d.ts.map +1 -1
- package/dist/core/health-manager.js +6 -1
- package/dist/core/health-manager.js.map +1 -1
- package/dist/core/plugin-registry.d.ts +55 -5
- package/dist/core/plugin-registry.d.ts.map +1 -1
- package/dist/core/plugin-registry.js +57 -19
- package/dist/core/plugin-registry.js.map +1 -1
- package/dist/core/types.d.ts +2 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/plugins/api-keys/api-keys-plugin.d.ts +46 -0
- package/dist/plugins/api-keys/api-keys-plugin.d.ts.map +1 -0
- package/dist/plugins/api-keys/api-keys-plugin.js +329 -0
- package/dist/plugins/api-keys/api-keys-plugin.js.map +1 -0
- package/dist/plugins/api-keys/index.d.ts +14 -0
- package/dist/plugins/api-keys/index.d.ts.map +1 -0
- package/dist/plugins/api-keys/index.js +17 -0
- package/dist/plugins/api-keys/index.js.map +1 -0
- package/dist/plugins/api-keys/middleware/bearer-token-auth.d.ts +74 -0
- package/dist/plugins/api-keys/middleware/bearer-token-auth.d.ts.map +1 -0
- package/dist/plugins/api-keys/middleware/bearer-token-auth.js +201 -0
- package/dist/plugins/api-keys/middleware/bearer-token-auth.js.map +1 -0
- package/dist/plugins/api-keys/middleware/index.d.ts +7 -0
- package/dist/plugins/api-keys/middleware/index.d.ts.map +1 -0
- package/dist/plugins/api-keys/middleware/index.js +7 -0
- package/dist/plugins/api-keys/middleware/index.js.map +1 -0
- package/dist/plugins/api-keys/stores/index.d.ts +7 -0
- package/dist/plugins/api-keys/stores/index.d.ts.map +1 -0
- package/dist/plugins/api-keys/stores/index.js +7 -0
- package/dist/plugins/api-keys/stores/index.js.map +1 -0
- package/dist/plugins/api-keys/stores/postgres-store.d.ts +34 -0
- package/dist/plugins/api-keys/stores/postgres-store.d.ts.map +1 -0
- package/dist/plugins/api-keys/stores/postgres-store.js +360 -0
- package/dist/plugins/api-keys/stores/postgres-store.js.map +1 -0
- package/dist/plugins/api-keys/types.d.ts +268 -0
- package/dist/plugins/api-keys/types.d.ts.map +1 -0
- package/dist/plugins/api-keys/types.js +56 -0
- package/dist/plugins/api-keys/types.js.map +1 -0
- package/dist/plugins/auth/auth-plugin.d.ts.map +1 -1
- package/dist/plugins/auth/auth-plugin.js +17 -1
- package/dist/plugins/auth/auth-plugin.js.map +1 -1
- package/dist/plugins/auth/auth-plugin.test.js +133 -0
- package/dist/plugins/auth/auth-plugin.test.js.map +1 -1
- package/dist/plugins/auth/env-config.d.ts.map +1 -1
- package/dist/plugins/auth/env-config.js +6 -2
- package/dist/plugins/auth/env-config.js.map +1 -1
- package/dist/plugins/auth/types.d.ts +10 -0
- package/dist/plugins/auth/types.d.ts.map +1 -1
- package/dist/plugins/auth/types.js.map +1 -1
- package/dist/plugins/devices/__tests__/token-utils.test.js +4 -2
- package/dist/plugins/devices/__tests__/token-utils.test.js.map +1 -1
- package/dist/plugins/frontend-app-plugin.d.ts.map +1 -1
- package/dist/plugins/frontend-app-plugin.js +21 -4
- package/dist/plugins/frontend-app-plugin.js.map +1 -1
- package/dist/plugins/index.d.ts +2 -0
- package/dist/plugins/index.d.ts.map +1 -1
- package/dist/plugins/index.js +2 -0
- package/dist/plugins/index.js.map +1 -1
- package/dist/plugins/qwickbrain/index.d.ts +25 -0
- package/dist/plugins/qwickbrain/index.d.ts.map +1 -0
- package/dist/plugins/qwickbrain/index.js +24 -0
- package/dist/plugins/qwickbrain/index.js.map +1 -0
- package/dist/plugins/qwickbrain/qwickbrain-plugin.d.ts +23 -0
- package/dist/plugins/qwickbrain/qwickbrain-plugin.d.ts.map +1 -0
- package/dist/plugins/qwickbrain/qwickbrain-plugin.js +528 -0
- package/dist/plugins/qwickbrain/qwickbrain-plugin.js.map +1 -0
- package/dist/plugins/qwickbrain/types.d.ts +131 -0
- package/dist/plugins/qwickbrain/types.d.ts.map +1 -0
- package/dist/plugins/qwickbrain/types.js +9 -0
- package/dist/plugins/qwickbrain/types.js.map +1 -0
- package/dist/plugins/users/__tests__/postgres-store.test.js +1 -0
- package/dist/plugins/users/__tests__/postgres-store.test.js.map +1 -1
- package/dist/plugins/users/__tests__/users-plugin.test.js +3 -0
- package/dist/plugins/users/__tests__/users-plugin.test.js.map +1 -1
- package/dist/plugins/users/stores/postgres-store.d.ts.map +1 -1
- package/dist/plugins/users/stores/postgres-store.js +59 -1
- package/dist/plugins/users/stores/postgres-store.js.map +1 -1
- package/dist/plugins/users/types.d.ts +22 -0
- package/dist/plugins/users/types.d.ts.map +1 -1
- package/dist-ui/assets/index-5nX8fM1a.js +469 -0
- package/dist-ui/assets/index-5nX8fM1a.js.map +1 -0
- package/dist-ui/index.html +1 -1
- package/dist-ui-lib/api/controlPanelApi.d.ts +68 -0
- package/dist-ui-lib/components/index.d.ts +2 -1
- package/dist-ui-lib/index.js +2642 -2281
- package/dist-ui-lib/index.js.map +1 -1
- package/dist-ui-lib/pages/APIKeysPage.d.ts +13 -0
- package/dist-ui-lib/pages/AcceptInvitationPage.d.ts +28 -0
- package/package.json +3 -2
- package/src/core/control-panel.ts +47 -0
- package/src/core/guards.ts +89 -0
- package/src/core/health-manager.ts +6 -1
- package/src/core/plugin-registry.ts +123 -25
- package/src/core/types.ts +2 -0
- package/src/index.ts +11 -0
- package/src/plugins/api-keys/api-keys-plugin.ts +397 -0
- package/src/plugins/api-keys/index.ts +49 -0
- package/src/plugins/api-keys/middleware/bearer-token-auth.ts +250 -0
- package/src/plugins/api-keys/middleware/index.ts +12 -0
- package/src/plugins/api-keys/stores/index.ts +7 -0
- package/src/plugins/api-keys/stores/postgres-store.ts +487 -0
- package/src/plugins/api-keys/types.ts +243 -0
- package/src/plugins/auth/auth-plugin.test.ts +167 -0
- package/src/plugins/auth/auth-plugin.ts +17 -1
- package/src/plugins/auth/env-config.ts +6 -2
- package/src/plugins/auth/types.ts +10 -0
- package/src/plugins/devices/__tests__/token-utils.test.ts +4 -2
- package/src/plugins/frontend-app-plugin.ts +24 -4
- package/src/plugins/index.ts +15 -0
- package/src/plugins/qwickbrain/index.ts +33 -0
- package/src/plugins/qwickbrain/qwickbrain-plugin.ts +642 -0
- package/src/plugins/qwickbrain/types.ts +146 -0
- package/src/plugins/users/__tests__/postgres-store.test.ts +1 -0
- package/src/plugins/users/__tests__/users-plugin.test.ts +3 -0
- package/src/plugins/users/stores/postgres-store.ts +69 -0
- package/src/plugins/users/types.ts +25 -0
- package/ui/src/App.tsx +6 -1
- package/ui/src/api/controlPanelApi.ts +206 -37
- package/ui/src/components/index.ts +6 -0
- package/ui/src/pages/APIKeysPage.tsx +661 -0
- package/ui/src/pages/AcceptInvitationPage.tsx +169 -0
- package/ui/src/pages/UsersPage.tsx +225 -2
- package/dist-ui/assets/index-CynOqPkb.js +0 -469
- package/dist-ui/assets/index-CynOqPkb.js.map +0 -1
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PostgreSQL API Keys Store
|
|
3
|
+
*
|
|
4
|
+
* API key storage implementation using PostgreSQL with Row-Level Security (RLS).
|
|
5
|
+
* Uses SHA-256 for token hashing (high-entropy keys don't need bcrypt's slowness).
|
|
6
|
+
*
|
|
7
|
+
* RLS Context Pattern:
|
|
8
|
+
* Each operation uses an explicit transaction and sets `app.current_user_id`
|
|
9
|
+
* as a transaction-local configuration variable. The RLS policy checks this
|
|
10
|
+
* variable to enforce that users can only access their own API keys.
|
|
11
|
+
*
|
|
12
|
+
* Copyright (c) 2025 QwickApps.com. All rights reserved.
|
|
13
|
+
*/
|
|
14
|
+
import crypto from 'crypto';
|
|
15
|
+
import { getLogger } from '@qwickapps/logging';
|
|
16
|
+
// ============================================================================
|
|
17
|
+
// Logging
|
|
18
|
+
// ============================================================================
|
|
19
|
+
const logger = getLogger('api-keys');
|
|
20
|
+
// ============================================================================
|
|
21
|
+
// Token Generation and Hashing
|
|
22
|
+
// ============================================================================
|
|
23
|
+
/**
|
|
24
|
+
* Generate a cryptographically secure API key
|
|
25
|
+
*
|
|
26
|
+
* Token format: `qk_<env>_<32 bytes base64url>`
|
|
27
|
+
* - qk = QwickApps
|
|
28
|
+
* - env = live or test
|
|
29
|
+
* - 32 random bytes = high entropy secret
|
|
30
|
+
*
|
|
31
|
+
* @param isTest Whether this is a test key (default: false)
|
|
32
|
+
* @returns Object with plaintext key, hash, and prefix
|
|
33
|
+
*/
|
|
34
|
+
function generateApiKey(isTest = false) {
|
|
35
|
+
const env = isTest ? 'test' : 'live';
|
|
36
|
+
const randomBytes = crypto.randomBytes(32);
|
|
37
|
+
const secret = randomBytes.toString('base64url');
|
|
38
|
+
const key = `qk_${env}_${secret}`;
|
|
39
|
+
// Hash the key for storage (SHA-256 is appropriate for high-entropy tokens)
|
|
40
|
+
const hash = crypto.createHash('sha256').update(key).digest('hex');
|
|
41
|
+
// Prefix for identification (first 12 characters)
|
|
42
|
+
const prefix = key.substring(0, 12);
|
|
43
|
+
return { key, hash, prefix };
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Hash an API key using SHA-256
|
|
47
|
+
*
|
|
48
|
+
* We use SHA-256 instead of bcrypt because:
|
|
49
|
+
* 1. API keys are high-entropy (32 random bytes)
|
|
50
|
+
* 2. No need for slow hashing (not user passwords)
|
|
51
|
+
* 3. Faster verification for high-throughput API calls
|
|
52
|
+
*
|
|
53
|
+
* @param key Plaintext API key
|
|
54
|
+
* @returns Hex-encoded SHA-256 hash
|
|
55
|
+
*/
|
|
56
|
+
function hashApiKey(key) {
|
|
57
|
+
return crypto.createHash('sha256').update(key).digest('hex');
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Verify an API key against its stored hash
|
|
61
|
+
*
|
|
62
|
+
* Uses constant-time comparison to prevent timing attacks.
|
|
63
|
+
*
|
|
64
|
+
* @param key Plaintext API key
|
|
65
|
+
* @param storedHash Hash from database
|
|
66
|
+
* @returns True if key matches hash
|
|
67
|
+
*/
|
|
68
|
+
function verifyApiKey(key, storedHash) {
|
|
69
|
+
const keyHash = hashApiKey(key);
|
|
70
|
+
// Constant-time comparison
|
|
71
|
+
return crypto.timingSafeEqual(Buffer.from(keyHash, 'hex'), Buffer.from(storedHash, 'hex'));
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Extract prefix from API key
|
|
75
|
+
*
|
|
76
|
+
* @param key Plaintext API key
|
|
77
|
+
* @returns Key prefix (first 12 characters)
|
|
78
|
+
*/
|
|
79
|
+
function getKeyPrefix(key) {
|
|
80
|
+
return key.substring(0, 12);
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Validate API key format
|
|
84
|
+
*
|
|
85
|
+
* @param key Key to validate
|
|
86
|
+
* @returns True if format is valid
|
|
87
|
+
*/
|
|
88
|
+
function isValidKeyFormat(key) {
|
|
89
|
+
// Must start with qk_live_ or qk_test_
|
|
90
|
+
if (!key.startsWith('qk_live_') && !key.startsWith('qk_test_')) {
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
// Extract secret part
|
|
94
|
+
const parts = key.split('_');
|
|
95
|
+
if (parts.length !== 3) {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
const secret = parts[2];
|
|
99
|
+
// Validate length (32 bytes base64url = 43 characters)
|
|
100
|
+
if (secret.length !== 43) {
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
// Validate characters (base64url: A-Za-z0-9_-)
|
|
104
|
+
const base64urlPattern = /^[A-Za-z0-9_-]+$/;
|
|
105
|
+
return base64urlPattern.test(secret);
|
|
106
|
+
}
|
|
107
|
+
// ============================================================================
|
|
108
|
+
// RLS Helper
|
|
109
|
+
// ============================================================================
|
|
110
|
+
/**
|
|
111
|
+
* Execute a function within an RLS-protected transaction
|
|
112
|
+
*
|
|
113
|
+
* This helper ensures that:
|
|
114
|
+
* 1. All queries run within the same transaction
|
|
115
|
+
* 2. The RLS context is set before any data access
|
|
116
|
+
* 3. The transaction is properly committed or rolled back
|
|
117
|
+
*
|
|
118
|
+
* @param pool PostgreSQL pool
|
|
119
|
+
* @param userId User ID to set as the RLS context
|
|
120
|
+
* @param callback Function to execute within the transaction
|
|
121
|
+
*/
|
|
122
|
+
async function withRLSContext(pool, userId, callback) {
|
|
123
|
+
const client = await pool.connect();
|
|
124
|
+
try {
|
|
125
|
+
await client.query('BEGIN');
|
|
126
|
+
// Set transaction-local user context for RLS
|
|
127
|
+
await client.query("SELECT set_config('app.current_user_id', $1, true)", [userId]);
|
|
128
|
+
const result = await callback(client);
|
|
129
|
+
await client.query('COMMIT');
|
|
130
|
+
return result;
|
|
131
|
+
}
|
|
132
|
+
catch (error) {
|
|
133
|
+
await client.query('ROLLBACK');
|
|
134
|
+
throw error;
|
|
135
|
+
}
|
|
136
|
+
finally {
|
|
137
|
+
client.release();
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
// ============================================================================
|
|
141
|
+
// PostgreSQL Store Implementation
|
|
142
|
+
// ============================================================================
|
|
143
|
+
/**
|
|
144
|
+
* Create a PostgreSQL API keys store with RLS
|
|
145
|
+
*
|
|
146
|
+
* @param config Configuration including a pg Pool instance
|
|
147
|
+
* @returns ApiKeyStore implementation
|
|
148
|
+
*
|
|
149
|
+
* @example
|
|
150
|
+
* ```ts
|
|
151
|
+
* import { Pool } from 'pg';
|
|
152
|
+
* import { postgresApiKeyStore } from '@qwickapps/server';
|
|
153
|
+
*
|
|
154
|
+
* const pool = new Pool({ connectionString: process.env.DATABASE_URL });
|
|
155
|
+
* const store = postgresApiKeyStore({ pool });
|
|
156
|
+
*
|
|
157
|
+
* // Or with lazy initialization:
|
|
158
|
+
* const store = postgresApiKeyStore({ pool: () => getPostgres().getPool() });
|
|
159
|
+
* ```
|
|
160
|
+
*/
|
|
161
|
+
export function postgresApiKeyStore(config) {
|
|
162
|
+
const { pool: poolOrFn, tableName = 'api_keys', schema = 'public', autoCreateTables = true, enableRLS = true, defaultExpirationDays = 90, environment = process.env.NODE_ENV === 'production' ? 'live' : 'test', } = config;
|
|
163
|
+
// Validate environment configuration
|
|
164
|
+
if (environment !== 'test' && environment !== 'live') {
|
|
165
|
+
throw new Error(`Invalid environment: "${environment}". Must be "test" or "live". ` +
|
|
166
|
+
`Check your PostgresApiKeyStoreConfig.environment setting.`);
|
|
167
|
+
}
|
|
168
|
+
// Helper to get pool (supports lazy initialization via function)
|
|
169
|
+
const getPool = () => {
|
|
170
|
+
const pool = typeof poolOrFn === 'function' ? poolOrFn() : poolOrFn;
|
|
171
|
+
if (!pool || typeof pool.query !== 'function') {
|
|
172
|
+
throw new Error('Invalid pool: must have query method');
|
|
173
|
+
}
|
|
174
|
+
return pool;
|
|
175
|
+
};
|
|
176
|
+
const tableFullName = `"${schema}"."${tableName}"`;
|
|
177
|
+
return {
|
|
178
|
+
name: 'postgres',
|
|
179
|
+
async initialize() {
|
|
180
|
+
if (!autoCreateTables)
|
|
181
|
+
return;
|
|
182
|
+
const pool = getPool();
|
|
183
|
+
// Create table with foreign key to users
|
|
184
|
+
await pool.query(`
|
|
185
|
+
CREATE TABLE IF NOT EXISTS ${tableFullName} (
|
|
186
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
187
|
+
user_id UUID NOT NULL REFERENCES "public"."users"(id) ON DELETE CASCADE,
|
|
188
|
+
name VARCHAR(255) NOT NULL,
|
|
189
|
+
key_hash VARCHAR(64) NOT NULL,
|
|
190
|
+
key_prefix VARCHAR(12) NOT NULL,
|
|
191
|
+
key_type VARCHAR(10) NOT NULL CHECK (key_type IN ('m2m', 'pat')),
|
|
192
|
+
scopes TEXT[] NOT NULL DEFAULT '{}',
|
|
193
|
+
last_used_at TIMESTAMPTZ,
|
|
194
|
+
expires_at TIMESTAMPTZ,
|
|
195
|
+
is_active BOOLEAN NOT NULL DEFAULT true,
|
|
196
|
+
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
197
|
+
updated_at TIMESTAMPTZ DEFAULT NOW()
|
|
198
|
+
);
|
|
199
|
+
|
|
200
|
+
CREATE INDEX IF NOT EXISTS idx_${tableName}_user_id ON ${tableFullName}(user_id);
|
|
201
|
+
CREATE INDEX IF NOT EXISTS idx_${tableName}_key_prefix ON ${tableFullName}(key_prefix);
|
|
202
|
+
CREATE INDEX IF NOT EXISTS idx_${tableName}_key_hash ON ${tableFullName}(key_hash);
|
|
203
|
+
`);
|
|
204
|
+
// Enable RLS if configured
|
|
205
|
+
if (enableRLS) {
|
|
206
|
+
await pool.query(`
|
|
207
|
+
ALTER TABLE ${tableFullName} ENABLE ROW LEVEL SECURITY;
|
|
208
|
+
ALTER TABLE ${tableFullName} FORCE ROW LEVEL SECURITY;
|
|
209
|
+
`);
|
|
210
|
+
// Create or replace the RLS policy
|
|
211
|
+
await pool.query(`
|
|
212
|
+
DROP POLICY IF EXISTS "${tableName}_owner" ON ${tableFullName};
|
|
213
|
+
`);
|
|
214
|
+
// RLS policy: users can only access their own keys
|
|
215
|
+
await pool.query(`
|
|
216
|
+
CREATE POLICY "${tableName}_owner" ON ${tableFullName}
|
|
217
|
+
FOR ALL
|
|
218
|
+
USING (user_id::text = current_setting('app.current_user_id', true))
|
|
219
|
+
WITH CHECK (user_id::text = current_setting('app.current_user_id', true));
|
|
220
|
+
`);
|
|
221
|
+
}
|
|
222
|
+
},
|
|
223
|
+
async create(params) {
|
|
224
|
+
const { user_id, name, key_type, scopes, expires_at } = params;
|
|
225
|
+
// Generate API key based on configured environment
|
|
226
|
+
const isTest = environment === 'test';
|
|
227
|
+
const { key: plaintextKey, hash, prefix } = generateApiKey(isTest);
|
|
228
|
+
// Calculate expiration if not provided
|
|
229
|
+
const expiration = expires_at || (defaultExpirationDays !== null
|
|
230
|
+
? new Date(Date.now() + defaultExpirationDays * 24 * 60 * 60 * 1000)
|
|
231
|
+
: null);
|
|
232
|
+
return withRLSContext(getPool(), user_id, async (client) => {
|
|
233
|
+
const result = await client.query(`INSERT INTO ${tableFullName}
|
|
234
|
+
(user_id, name, key_hash, key_prefix, key_type, scopes, expires_at)
|
|
235
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
|
236
|
+
RETURNING *`, [user_id, name, hash, prefix, key_type, scopes, expiration]);
|
|
237
|
+
const row = result.rows[0];
|
|
238
|
+
logger.info('API key created', {
|
|
239
|
+
action: 'api_key.created',
|
|
240
|
+
user_id,
|
|
241
|
+
key_id: row.id,
|
|
242
|
+
key_prefix: row.key_prefix,
|
|
243
|
+
key_type: row.key_type,
|
|
244
|
+
scopes: row.scopes,
|
|
245
|
+
timestamp: new Date().toISOString(),
|
|
246
|
+
});
|
|
247
|
+
return {
|
|
248
|
+
...row,
|
|
249
|
+
plaintext_key: plaintextKey,
|
|
250
|
+
};
|
|
251
|
+
});
|
|
252
|
+
},
|
|
253
|
+
async list(userId) {
|
|
254
|
+
return withRLSContext(getPool(), userId, async (client) => {
|
|
255
|
+
const result = await client.query(`SELECT * FROM ${tableFullName} WHERE user_id = $1 ORDER BY created_at DESC`, [userId]);
|
|
256
|
+
return result.rows;
|
|
257
|
+
});
|
|
258
|
+
},
|
|
259
|
+
async get(userId, keyId) {
|
|
260
|
+
return withRLSContext(getPool(), userId, async (client) => {
|
|
261
|
+
const result = await client.query(`SELECT * FROM ${tableFullName} WHERE user_id = $1 AND id = $2`, [userId, keyId]);
|
|
262
|
+
if (result.rows.length === 0) {
|
|
263
|
+
return null;
|
|
264
|
+
}
|
|
265
|
+
return result.rows[0];
|
|
266
|
+
});
|
|
267
|
+
},
|
|
268
|
+
async verify(plaintextKey) {
|
|
269
|
+
// Validate format first
|
|
270
|
+
if (!isValidKeyFormat(plaintextKey)) {
|
|
271
|
+
return null;
|
|
272
|
+
}
|
|
273
|
+
const pool = getPool();
|
|
274
|
+
const prefix = getKeyPrefix(plaintextKey);
|
|
275
|
+
// Find key by prefix (no RLS needed for verification)
|
|
276
|
+
const result = await pool.query(`SELECT * FROM ${tableFullName}
|
|
277
|
+
WHERE key_prefix = $1 AND is_active = true`, [prefix]);
|
|
278
|
+
if (result.rows.length === 0) {
|
|
279
|
+
return null;
|
|
280
|
+
}
|
|
281
|
+
const key = result.rows[0];
|
|
282
|
+
// Verify hash
|
|
283
|
+
if (!verifyApiKey(plaintextKey, key.key_hash)) {
|
|
284
|
+
return null;
|
|
285
|
+
}
|
|
286
|
+
// Check expiration
|
|
287
|
+
if (key.expires_at && new Date() > new Date(key.expires_at)) {
|
|
288
|
+
return null;
|
|
289
|
+
}
|
|
290
|
+
return key;
|
|
291
|
+
},
|
|
292
|
+
async update(userId, keyId, params) {
|
|
293
|
+
const updates = [];
|
|
294
|
+
const values = [userId, keyId];
|
|
295
|
+
let paramIndex = 3;
|
|
296
|
+
if (params.name !== undefined) {
|
|
297
|
+
updates.push(`name = $${paramIndex++}`);
|
|
298
|
+
values.push(params.name);
|
|
299
|
+
}
|
|
300
|
+
if (params.scopes !== undefined) {
|
|
301
|
+
updates.push(`scopes = $${paramIndex++}`);
|
|
302
|
+
values.push(params.scopes);
|
|
303
|
+
}
|
|
304
|
+
if (params.expires_at !== undefined) {
|
|
305
|
+
updates.push(`expires_at = $${paramIndex++}`);
|
|
306
|
+
values.push(params.expires_at);
|
|
307
|
+
}
|
|
308
|
+
if (params.is_active !== undefined) {
|
|
309
|
+
updates.push(`is_active = $${paramIndex++}`);
|
|
310
|
+
values.push(params.is_active);
|
|
311
|
+
}
|
|
312
|
+
if (updates.length === 0) {
|
|
313
|
+
// No updates, just return current key
|
|
314
|
+
return this.get(userId, keyId);
|
|
315
|
+
}
|
|
316
|
+
updates.push(`updated_at = NOW()`);
|
|
317
|
+
return withRLSContext(getPool(), userId, async (client) => {
|
|
318
|
+
const result = await client.query(`UPDATE ${tableFullName}
|
|
319
|
+
SET ${updates.join(', ')}
|
|
320
|
+
WHERE user_id = $1 AND id = $2
|
|
321
|
+
RETURNING *`, values);
|
|
322
|
+
if (result.rows.length === 0) {
|
|
323
|
+
return null;
|
|
324
|
+
}
|
|
325
|
+
const updatedKey = result.rows[0];
|
|
326
|
+
logger.info('API key updated', {
|
|
327
|
+
action: 'api_key.updated',
|
|
328
|
+
user_id: userId,
|
|
329
|
+
key_id: keyId,
|
|
330
|
+
changes: Object.keys(params),
|
|
331
|
+
timestamp: new Date().toISOString(),
|
|
332
|
+
});
|
|
333
|
+
return updatedKey;
|
|
334
|
+
});
|
|
335
|
+
},
|
|
336
|
+
async delete(userId, keyId) {
|
|
337
|
+
return withRLSContext(getPool(), userId, async (client) => {
|
|
338
|
+
const result = await client.query(`DELETE FROM ${tableFullName} WHERE user_id = $1 AND id = $2`, [userId, keyId]);
|
|
339
|
+
const deleted = (result.rowCount ?? 0) > 0;
|
|
340
|
+
if (deleted) {
|
|
341
|
+
logger.info('API key deleted', {
|
|
342
|
+
action: 'api_key.deleted',
|
|
343
|
+
user_id: userId,
|
|
344
|
+
key_id: keyId,
|
|
345
|
+
timestamp: new Date().toISOString(),
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
return deleted;
|
|
349
|
+
});
|
|
350
|
+
},
|
|
351
|
+
async recordUsage(keyId) {
|
|
352
|
+
const pool = getPool();
|
|
353
|
+
await pool.query(`UPDATE ${tableFullName} SET last_used_at = NOW() WHERE id = $1`, [keyId]);
|
|
354
|
+
},
|
|
355
|
+
async shutdown() {
|
|
356
|
+
// Pool is managed externally, nothing to do here
|
|
357
|
+
},
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
//# sourceMappingURL=postgres-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"postgres-store.js","sourceRoot":"","sources":["../../../../src/plugins/api-keys/stores/postgres-store.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API Keys Plugin Types
|
|
3
|
+
*
|
|
4
|
+
* Type definitions for API key authentication and management.
|
|
5
|
+
* Supports PostgreSQL with Row-Level Security (RLS) for data isolation.
|
|
6
|
+
*
|
|
7
|
+
* Copyright (c) 2025 QwickApps.com. All rights reserved.
|
|
8
|
+
*/
|
|
9
|
+
import { z } from 'zod';
|
|
10
|
+
/**
|
|
11
|
+
* API key scope type
|
|
12
|
+
*/
|
|
13
|
+
export type ApiKeyScope = 'read' | 'write' | 'admin';
|
|
14
|
+
/**
|
|
15
|
+
* API key type (M2M = machine-to-machine, PAT = personal access token)
|
|
16
|
+
*/
|
|
17
|
+
export type ApiKeyType = 'm2m' | 'pat';
|
|
18
|
+
/**
|
|
19
|
+
* API key record in the database
|
|
20
|
+
*/
|
|
21
|
+
export interface ApiKey {
|
|
22
|
+
/** Primary key - UUID */
|
|
23
|
+
id: string;
|
|
24
|
+
/** User ID (foreign key to users table) */
|
|
25
|
+
user_id: string;
|
|
26
|
+
/** Human-readable name for the key */
|
|
27
|
+
name: string;
|
|
28
|
+
/** Hashed API key (SHA-256) */
|
|
29
|
+
key_hash: string;
|
|
30
|
+
/** Key prefix for identification (e.g., 'qk_live_') - stored in plaintext */
|
|
31
|
+
key_prefix: string;
|
|
32
|
+
/** Key type: m2m (machine-to-machine) or pat (personal access token) */
|
|
33
|
+
key_type: ApiKeyType;
|
|
34
|
+
/** Scopes granted to this key */
|
|
35
|
+
scopes: ApiKeyScope[];
|
|
36
|
+
/** Last time this key was used */
|
|
37
|
+
last_used_at: Date | null;
|
|
38
|
+
/** Expiration date (null = never expires) */
|
|
39
|
+
expires_at: Date | null;
|
|
40
|
+
/** Whether the key is active */
|
|
41
|
+
is_active: boolean;
|
|
42
|
+
/** When the key was created */
|
|
43
|
+
created_at: Date;
|
|
44
|
+
/** When the key was last updated */
|
|
45
|
+
updated_at: Date;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* API key creation parameters
|
|
49
|
+
*/
|
|
50
|
+
export interface CreateApiKeyParams {
|
|
51
|
+
/** User ID who owns this key */
|
|
52
|
+
user_id: string;
|
|
53
|
+
/** Human-readable name for the key */
|
|
54
|
+
name: string;
|
|
55
|
+
/** Key type: m2m or pat */
|
|
56
|
+
key_type: ApiKeyType;
|
|
57
|
+
/** Scopes to grant */
|
|
58
|
+
scopes: ApiKeyScope[];
|
|
59
|
+
/** Optional expiration date */
|
|
60
|
+
expires_at?: Date;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* API key update parameters
|
|
64
|
+
*/
|
|
65
|
+
export interface UpdateApiKeyParams {
|
|
66
|
+
/** New name (optional) */
|
|
67
|
+
name?: string;
|
|
68
|
+
/** New scopes (optional) */
|
|
69
|
+
scopes?: ApiKeyScope[];
|
|
70
|
+
/** New expiration date (optional) */
|
|
71
|
+
expires_at?: Date;
|
|
72
|
+
/** Activate/deactivate key (optional) */
|
|
73
|
+
is_active?: boolean;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* API key with plaintext key (only returned on creation)
|
|
77
|
+
*/
|
|
78
|
+
export interface ApiKeyWithPlaintext extends ApiKey {
|
|
79
|
+
/** Plaintext API key - only available on creation */
|
|
80
|
+
plaintext_key: string;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* API key store interface - all storage backends must implement this
|
|
84
|
+
*/
|
|
85
|
+
export interface ApiKeyStore {
|
|
86
|
+
/** Store name (e.g., 'postgres', 'memory') */
|
|
87
|
+
name: string;
|
|
88
|
+
/**
|
|
89
|
+
* Initialize the store (create tables, RLS policies, etc.)
|
|
90
|
+
*/
|
|
91
|
+
initialize(): Promise<void>;
|
|
92
|
+
/**
|
|
93
|
+
* Create a new API key
|
|
94
|
+
* Returns the key with plaintext value (only time plaintext is accessible)
|
|
95
|
+
*/
|
|
96
|
+
create(params: CreateApiKeyParams): Promise<ApiKeyWithPlaintext>;
|
|
97
|
+
/**
|
|
98
|
+
* Get all API keys for a user
|
|
99
|
+
*/
|
|
100
|
+
list(userId: string): Promise<ApiKey[]>;
|
|
101
|
+
/**
|
|
102
|
+
* Get a specific API key by ID
|
|
103
|
+
* Returns null if key doesn't exist or doesn't belong to user
|
|
104
|
+
*/
|
|
105
|
+
get(userId: string, keyId: string): Promise<ApiKey | null>;
|
|
106
|
+
/**
|
|
107
|
+
* Verify an API key and return the associated key record
|
|
108
|
+
* Returns null if key is invalid, expired, or inactive
|
|
109
|
+
*/
|
|
110
|
+
verify(plaintextKey: string): Promise<ApiKey | null>;
|
|
111
|
+
/**
|
|
112
|
+
* Update an API key
|
|
113
|
+
* Returns the updated key or null if key doesn't exist
|
|
114
|
+
*/
|
|
115
|
+
update(userId: string, keyId: string, params: UpdateApiKeyParams): Promise<ApiKey | null>;
|
|
116
|
+
/**
|
|
117
|
+
* Delete an API key
|
|
118
|
+
* Returns true if key was deleted, false if it didn't exist
|
|
119
|
+
*/
|
|
120
|
+
delete(userId: string, keyId: string): Promise<boolean>;
|
|
121
|
+
/**
|
|
122
|
+
* Record key usage (updates last_used_at timestamp)
|
|
123
|
+
*/
|
|
124
|
+
recordUsage(keyId: string): Promise<void>;
|
|
125
|
+
/**
|
|
126
|
+
* Shutdown the store
|
|
127
|
+
*/
|
|
128
|
+
shutdown(): Promise<void>;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* PostgreSQL API key store configuration
|
|
132
|
+
*/
|
|
133
|
+
export interface PostgresApiKeyStoreConfig {
|
|
134
|
+
/** PostgreSQL pool instance or a function that returns one (for lazy initialization) */
|
|
135
|
+
pool: unknown | (() => unknown);
|
|
136
|
+
/** Table name (default: 'api_keys') */
|
|
137
|
+
tableName?: string;
|
|
138
|
+
/** Schema name (default: 'public') */
|
|
139
|
+
schema?: string;
|
|
140
|
+
/** Auto-create tables on init (default: true) */
|
|
141
|
+
autoCreateTables?: boolean;
|
|
142
|
+
/** Enable RLS (default: true) */
|
|
143
|
+
enableRLS?: boolean;
|
|
144
|
+
/** Key expiration in days (default: 90, null = never expires) */
|
|
145
|
+
defaultExpirationDays?: number | null;
|
|
146
|
+
/** Environment for key prefix (default: from NODE_ENV, 'test' in non-production, 'live' in production) */
|
|
147
|
+
environment?: 'test' | 'live';
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* API keys API configuration
|
|
151
|
+
*/
|
|
152
|
+
export interface ApiKeysApiConfig {
|
|
153
|
+
/** API route prefix (default: '/api-keys') */
|
|
154
|
+
prefix?: string;
|
|
155
|
+
/** Enable API endpoints (default: true) */
|
|
156
|
+
enabled?: boolean;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* API keys plugin configuration
|
|
160
|
+
*/
|
|
161
|
+
export interface ApiKeysPluginConfig {
|
|
162
|
+
/** API key storage backend */
|
|
163
|
+
store: ApiKeyStore;
|
|
164
|
+
/** API configuration */
|
|
165
|
+
api?: ApiKeysApiConfig;
|
|
166
|
+
/** Enable debug logging */
|
|
167
|
+
debug?: boolean;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Zod schema for API key scope
|
|
171
|
+
*/
|
|
172
|
+
export declare const ApiKeyScopeSchema: z.ZodEnum<["read", "write", "admin"]>;
|
|
173
|
+
/**
|
|
174
|
+
* Zod schema for API key type
|
|
175
|
+
*/
|
|
176
|
+
export declare const ApiKeyTypeSchema: z.ZodEnum<["m2m", "pat"]>;
|
|
177
|
+
/**
|
|
178
|
+
* Zod schema for creating an API key
|
|
179
|
+
*/
|
|
180
|
+
export declare const CreateApiKeySchema: z.ZodObject<{
|
|
181
|
+
name: z.ZodString;
|
|
182
|
+
key_type: z.ZodEnum<["m2m", "pat"]>;
|
|
183
|
+
scopes: z.ZodArray<z.ZodEnum<["read", "write", "admin"]>, "many">;
|
|
184
|
+
expires_at: z.ZodOptional<z.ZodDate>;
|
|
185
|
+
}, "strip", z.ZodTypeAny, {
|
|
186
|
+
name: string;
|
|
187
|
+
scopes: ("admin" | "read" | "write")[];
|
|
188
|
+
key_type: "m2m" | "pat";
|
|
189
|
+
expires_at?: Date | undefined;
|
|
190
|
+
}, {
|
|
191
|
+
name: string;
|
|
192
|
+
scopes: ("admin" | "read" | "write")[];
|
|
193
|
+
key_type: "m2m" | "pat";
|
|
194
|
+
expires_at?: Date | undefined;
|
|
195
|
+
}>;
|
|
196
|
+
/**
|
|
197
|
+
* Zod schema for updating an API key
|
|
198
|
+
*/
|
|
199
|
+
export declare const UpdateApiKeySchema: z.ZodEffects<z.ZodObject<{
|
|
200
|
+
name: z.ZodOptional<z.ZodString>;
|
|
201
|
+
scopes: z.ZodOptional<z.ZodArray<z.ZodEnum<["read", "write", "admin"]>, "many">>;
|
|
202
|
+
expires_at: z.ZodOptional<z.ZodDate>;
|
|
203
|
+
is_active: z.ZodOptional<z.ZodBoolean>;
|
|
204
|
+
}, "strip", z.ZodTypeAny, {
|
|
205
|
+
name?: string | undefined;
|
|
206
|
+
scopes?: ("admin" | "read" | "write")[] | undefined;
|
|
207
|
+
expires_at?: Date | undefined;
|
|
208
|
+
is_active?: boolean | undefined;
|
|
209
|
+
}, {
|
|
210
|
+
name?: string | undefined;
|
|
211
|
+
scopes?: ("admin" | "read" | "write")[] | undefined;
|
|
212
|
+
expires_at?: Date | undefined;
|
|
213
|
+
is_active?: boolean | undefined;
|
|
214
|
+
}>, {
|
|
215
|
+
name?: string | undefined;
|
|
216
|
+
scopes?: ("admin" | "read" | "write")[] | undefined;
|
|
217
|
+
expires_at?: Date | undefined;
|
|
218
|
+
is_active?: boolean | undefined;
|
|
219
|
+
}, {
|
|
220
|
+
name?: string | undefined;
|
|
221
|
+
scopes?: ("admin" | "read" | "write")[] | undefined;
|
|
222
|
+
expires_at?: Date | undefined;
|
|
223
|
+
is_active?: boolean | undefined;
|
|
224
|
+
}>;
|
|
225
|
+
/**
|
|
226
|
+
* Zod schema for API key record
|
|
227
|
+
*/
|
|
228
|
+
export declare const ApiKeySchema: z.ZodObject<{
|
|
229
|
+
id: z.ZodString;
|
|
230
|
+
user_id: z.ZodString;
|
|
231
|
+
name: z.ZodString;
|
|
232
|
+
key_hash: z.ZodString;
|
|
233
|
+
key_prefix: z.ZodString;
|
|
234
|
+
key_type: z.ZodEnum<["m2m", "pat"]>;
|
|
235
|
+
scopes: z.ZodArray<z.ZodEnum<["read", "write", "admin"]>, "many">;
|
|
236
|
+
last_used_at: z.ZodNullable<z.ZodDate>;
|
|
237
|
+
expires_at: z.ZodNullable<z.ZodDate>;
|
|
238
|
+
is_active: z.ZodBoolean;
|
|
239
|
+
created_at: z.ZodDate;
|
|
240
|
+
updated_at: z.ZodDate;
|
|
241
|
+
}, "strip", z.ZodTypeAny, {
|
|
242
|
+
name: string;
|
|
243
|
+
id: string;
|
|
244
|
+
scopes: ("admin" | "read" | "write")[];
|
|
245
|
+
created_at: Date;
|
|
246
|
+
expires_at: Date | null;
|
|
247
|
+
user_id: string;
|
|
248
|
+
updated_at: Date;
|
|
249
|
+
is_active: boolean;
|
|
250
|
+
key_type: "m2m" | "pat";
|
|
251
|
+
key_hash: string;
|
|
252
|
+
key_prefix: string;
|
|
253
|
+
last_used_at: Date | null;
|
|
254
|
+
}, {
|
|
255
|
+
name: string;
|
|
256
|
+
id: string;
|
|
257
|
+
scopes: ("admin" | "read" | "write")[];
|
|
258
|
+
created_at: Date;
|
|
259
|
+
expires_at: Date | null;
|
|
260
|
+
user_id: string;
|
|
261
|
+
updated_at: Date;
|
|
262
|
+
is_active: boolean;
|
|
263
|
+
key_type: "m2m" | "pat";
|
|
264
|
+
key_hash: string;
|
|
265
|
+
key_prefix: string;
|
|
266
|
+
last_used_at: Date | null;
|
|
267
|
+
}>;
|
|
268
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/plugins/api-keys/types.ts"],"names":[],"mappings":"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"}
|