@joonweb/joonweb-sdk 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.js +8 -0
- package/package.json +60 -0
- package/src/Auth/Auth.js +193 -0
- package/src/Auth/OAuth.js +135 -0
- package/src/Auth/SessionManager.js +145 -0
- package/src/Auth/session/Session.js +46 -0
- package/src/Auth/session/StorageInterface.js +48 -0
- package/src/Auth/session/storage/MemoryStorage.js +34 -0
- package/src/Auth/session/storage/MongoDBStorage.js +107 -0
- package/src/Auth/session/storage/MySQLStorage.js +281 -0
- package/src/Auth/session/storage/PostgreSQLStorage.js +372 -0
- package/src/Auth/session/storage/RedisStorage.js +133 -0
- package/src/Auth/session/storage/SQLiteStorage.js +133 -0
- package/src/Auth/session/storage/index.js +46 -0
- package/src/Clients/BaseClient.js +66 -0
- package/src/Context.js +82 -0
- package/src/Helper.js +92 -0
- package/src/JoonWebAPI.js +81 -0
- package/src/Resources/BaseResource.js +32 -0
- package/src/Resources/Customer.js +34 -0
- package/src/Resources/Order.js +18 -0
- package/src/Resources/Product.js +30 -0
- package/src/Resources/Site.js +11 -0
- package/src/Resources/Theme.js +7 -0
- package/src/Resources/Webhook.js +30 -0
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
// src/Auth/session/storage/PostgreSQLStorage.js
|
|
2
|
+
let pg;
|
|
3
|
+
|
|
4
|
+
try {
|
|
5
|
+
pg = require('pg');
|
|
6
|
+
} catch (error) {
|
|
7
|
+
// pg package not installed
|
|
8
|
+
pg = null;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
class PostgreSQLStorage {
|
|
12
|
+
constructor(options = {}) {
|
|
13
|
+
if (!pg) {
|
|
14
|
+
throw new Error('pg package not installed. Run: npm install pg');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
this.config = {
|
|
18
|
+
host: options.host || 'localhost',
|
|
19
|
+
port: options.port || 5432,
|
|
20
|
+
user: options.user || 'postgres',
|
|
21
|
+
password: options.password || '',
|
|
22
|
+
database: options.database || 'joonweb_sessions',
|
|
23
|
+
max: options.max || 20,
|
|
24
|
+
idleTimeoutMillis: options.idleTimeoutMillis || 30000,
|
|
25
|
+
connectionTimeoutMillis: options.connectionTimeoutMillis || 2000,
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
this.tableName = options.tableName || 'sessions';
|
|
29
|
+
this.schema = options.schema || 'public';
|
|
30
|
+
this.pool = null;
|
|
31
|
+
this.initialized = false;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async initDatabase() {
|
|
35
|
+
if (!this.pool) {
|
|
36
|
+
this.pool = new pg.Pool(this.config);
|
|
37
|
+
|
|
38
|
+
// Test connection
|
|
39
|
+
const client = await this.pool.connect();
|
|
40
|
+
try {
|
|
41
|
+
// Create schema if it doesn't exist
|
|
42
|
+
await client.query(`CREATE SCHEMA IF NOT EXISTS ${this.schema}`);
|
|
43
|
+
|
|
44
|
+
// Create table if not exists
|
|
45
|
+
await this.createTable(client);
|
|
46
|
+
|
|
47
|
+
// Create cleanup job for expired sessions
|
|
48
|
+
await this.createCleanupTrigger(client);
|
|
49
|
+
|
|
50
|
+
this.initialized = true;
|
|
51
|
+
console.log(`✅ PostgreSQL storage initialized (${this.config.database}.${this.schema}.${this.tableName})`);
|
|
52
|
+
} finally {
|
|
53
|
+
client.release();
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async createTable(client) {
|
|
59
|
+
await client.query(`
|
|
60
|
+
CREATE TABLE IF NOT EXISTS ${this.schema}.${this.tableName} (
|
|
61
|
+
id VARCHAR(255) PRIMARY KEY,
|
|
62
|
+
site_domain VARCHAR(255) NOT NULL,
|
|
63
|
+
access_token TEXT NOT NULL,
|
|
64
|
+
scope TEXT,
|
|
65
|
+
user_data JSONB,
|
|
66
|
+
expires_at BIGINT,
|
|
67
|
+
authenticated_at BIGINT NOT NULL,
|
|
68
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
69
|
+
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
70
|
+
is_online BOOLEAN DEFAULT FALSE,
|
|
71
|
+
online_access_info JSONB,
|
|
72
|
+
state VARCHAR(255),
|
|
73
|
+
metadata JSONB,
|
|
74
|
+
|
|
75
|
+
-- Indexes for performance
|
|
76
|
+
CONSTRAINT unique_site_domain UNIQUE (site_domain)
|
|
77
|
+
)
|
|
78
|
+
`);
|
|
79
|
+
|
|
80
|
+
// Create indexes
|
|
81
|
+
await client.query(`
|
|
82
|
+
CREATE INDEX IF NOT EXISTS idx_sessions_expires_at
|
|
83
|
+
ON ${this.schema}.${this.tableName} (expires_at)
|
|
84
|
+
WHERE expires_at IS NOT NULL
|
|
85
|
+
`);
|
|
86
|
+
|
|
87
|
+
await client.query(`
|
|
88
|
+
CREATE INDEX IF NOT EXISTS idx_sessions_created_at
|
|
89
|
+
ON ${this.schema}.${this.tableName} (created_at)
|
|
90
|
+
`);
|
|
91
|
+
|
|
92
|
+
await client.query(`
|
|
93
|
+
CREATE INDEX IF NOT EXISTS idx_sessions_is_online
|
|
94
|
+
ON ${this.schema}.${this.tableName} (is_online)
|
|
95
|
+
`);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async createCleanupTrigger(client) {
|
|
99
|
+
// Create function to cleanup expired sessions
|
|
100
|
+
await client.query(`
|
|
101
|
+
CREATE OR REPLACE FUNCTION ${this.schema}.cleanup_expired_sessions()
|
|
102
|
+
RETURNS void AS $$
|
|
103
|
+
BEGIN
|
|
104
|
+
DELETE FROM ${this.schema}.${this.tableName}
|
|
105
|
+
WHERE expires_at IS NOT NULL
|
|
106
|
+
AND expires_at < EXTRACT(EPOCH FROM CURRENT_TIMESTAMP) * 1000;
|
|
107
|
+
END;
|
|
108
|
+
$$ LANGUAGE plpgsql
|
|
109
|
+
`);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async ensureConnected() {
|
|
113
|
+
if (!this.initialized) {
|
|
114
|
+
await this.initDatabase();
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async store(siteDomain, session) {
|
|
119
|
+
await this.ensureConnected();
|
|
120
|
+
|
|
121
|
+
const client = await this.pool.connect();
|
|
122
|
+
try {
|
|
123
|
+
// Generate a unique ID for the session
|
|
124
|
+
const crypto = require('crypto');
|
|
125
|
+
const sessionId = crypto.randomBytes(16).toString('hex');
|
|
126
|
+
|
|
127
|
+
await client.query(
|
|
128
|
+
`INSERT INTO ${this.schema}.${this.tableName}
|
|
129
|
+
(id, site_domain, access_token, scope, user_data, expires_at,
|
|
130
|
+
authenticated_at, is_online, online_access_info, state, metadata)
|
|
131
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
|
|
132
|
+
ON CONFLICT (site_domain)
|
|
133
|
+
DO UPDATE SET
|
|
134
|
+
access_token = EXCLUDED.access_token,
|
|
135
|
+
scope = EXCLUDED.scope,
|
|
136
|
+
user_data = EXCLUDED.user_data,
|
|
137
|
+
expires_at = EXCLUDED.expires_at,
|
|
138
|
+
is_online = EXCLUDED.is_online,
|
|
139
|
+
online_access_info = EXCLUDED.online_access_info,
|
|
140
|
+
state = EXCLUDED.state,
|
|
141
|
+
metadata = EXCLUDED.metadata,
|
|
142
|
+
updated_at = CURRENT_TIMESTAMP`,
|
|
143
|
+
[
|
|
144
|
+
sessionId,
|
|
145
|
+
siteDomain,
|
|
146
|
+
session.access_token,
|
|
147
|
+
session.scope,
|
|
148
|
+
JSON.stringify(session.user || {}),
|
|
149
|
+
session.expires_at,
|
|
150
|
+
session.authenticated_at,
|
|
151
|
+
session.is_online || false,
|
|
152
|
+
JSON.stringify(session.online_access_info || {}),
|
|
153
|
+
session.state || '',
|
|
154
|
+
JSON.stringify(session.metadata || {})
|
|
155
|
+
]
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
return true;
|
|
159
|
+
} finally {
|
|
160
|
+
client.release();
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
async load(siteDomain) {
|
|
165
|
+
await this.ensureConnected();
|
|
166
|
+
|
|
167
|
+
const client = await this.pool.connect();
|
|
168
|
+
try {
|
|
169
|
+
const result = await client.query(
|
|
170
|
+
`SELECT * FROM ${this.schema}.${this.tableName}
|
|
171
|
+
WHERE site_domain = $1
|
|
172
|
+
ORDER BY updated_at DESC LIMIT 1`,
|
|
173
|
+
[siteDomain]
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
if (result.rows.length === 0) {
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const row = result.rows[0];
|
|
181
|
+
|
|
182
|
+
// Parse JSONB fields (PostgreSQL returns them as objects)
|
|
183
|
+
const session = {
|
|
184
|
+
id: row.id,
|
|
185
|
+
site_domain: row.site_domain,
|
|
186
|
+
access_token: row.access_token,
|
|
187
|
+
scope: row.scope,
|
|
188
|
+
user: row.user_data || null,
|
|
189
|
+
expires_at: row.expires_at,
|
|
190
|
+
authenticated_at: row.authenticated_at,
|
|
191
|
+
is_online: Boolean(row.is_online),
|
|
192
|
+
online_access_info: row.online_access_info || {},
|
|
193
|
+
state: row.state,
|
|
194
|
+
metadata: row.metadata || {},
|
|
195
|
+
created_at: row.created_at,
|
|
196
|
+
updated_at: row.updated_at
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
return session;
|
|
200
|
+
} finally {
|
|
201
|
+
client.release();
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
async delete(siteDomain) {
|
|
206
|
+
await this.ensureConnected();
|
|
207
|
+
|
|
208
|
+
const client = await this.pool.connect();
|
|
209
|
+
try {
|
|
210
|
+
const result = await client.query(
|
|
211
|
+
`DELETE FROM ${this.schema}.${this.tableName} WHERE site_domain = $1`,
|
|
212
|
+
[siteDomain]
|
|
213
|
+
);
|
|
214
|
+
|
|
215
|
+
return result.rowCount > 0;
|
|
216
|
+
} finally {
|
|
217
|
+
client.release();
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
async list() {
|
|
222
|
+
await this.ensureConnected();
|
|
223
|
+
|
|
224
|
+
const client = await this.pool.connect();
|
|
225
|
+
try {
|
|
226
|
+
const result = await client.query(
|
|
227
|
+
`SELECT * FROM ${this.schema}.${this.tableName}
|
|
228
|
+
ORDER BY updated_at DESC`
|
|
229
|
+
);
|
|
230
|
+
|
|
231
|
+
return result.rows.map(row => ({
|
|
232
|
+
id: row.id,
|
|
233
|
+
site_domain: row.site_domain,
|
|
234
|
+
access_token: row.access_token,
|
|
235
|
+
scope: row.scope,
|
|
236
|
+
user: row.user_data || null,
|
|
237
|
+
expires_at: row.expires_at,
|
|
238
|
+
authenticated_at: row.authenticated_at,
|
|
239
|
+
is_online: Boolean(row.is_online),
|
|
240
|
+
online_access_info: row.online_access_info || {},
|
|
241
|
+
state: row.state,
|
|
242
|
+
metadata: row.metadata || {},
|
|
243
|
+
created_at: row.created_at,
|
|
244
|
+
updated_at: row.updated_at
|
|
245
|
+
}));
|
|
246
|
+
} finally {
|
|
247
|
+
client.release();
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
async clear() {
|
|
252
|
+
await this.ensureConnected();
|
|
253
|
+
|
|
254
|
+
const client = await this.pool.connect();
|
|
255
|
+
try {
|
|
256
|
+
await client.query(`TRUNCATE TABLE ${this.schema}.${this.tableName} RESTART IDENTITY`);
|
|
257
|
+
return true;
|
|
258
|
+
} finally {
|
|
259
|
+
client.release();
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
async close() {
|
|
264
|
+
if (this.pool) {
|
|
265
|
+
await this.pool.end();
|
|
266
|
+
}
|
|
267
|
+
return true;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
async cleanupExpiredSessions() {
|
|
271
|
+
await this.ensureConnected();
|
|
272
|
+
|
|
273
|
+
const client = await this.pool.connect();
|
|
274
|
+
try {
|
|
275
|
+
const result = await client.query(
|
|
276
|
+
`DELETE FROM ${this.schema}.${this.tableName}
|
|
277
|
+
WHERE expires_at IS NOT NULL
|
|
278
|
+
AND expires_at < EXTRACT(EPOCH FROM CURRENT_TIMESTAMP) * 1000`
|
|
279
|
+
);
|
|
280
|
+
|
|
281
|
+
if (result.rowCount > 0) {
|
|
282
|
+
console.log(`🧹 Cleaned up ${result.rowCount} expired sessions from PostgreSQL`);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
return result.rowCount;
|
|
286
|
+
} finally {
|
|
287
|
+
client.release();
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
async getStats() {
|
|
292
|
+
await this.ensureConnected();
|
|
293
|
+
|
|
294
|
+
const client = await this.pool.connect();
|
|
295
|
+
try {
|
|
296
|
+
const result = await client.query(`
|
|
297
|
+
SELECT
|
|
298
|
+
COUNT(*) as total_sessions,
|
|
299
|
+
COUNT(CASE WHEN expires_at IS NULL OR expires_at > EXTRACT(EPOCH FROM CURRENT_TIMESTAMP) * 1000 THEN 1 END) as active_sessions,
|
|
300
|
+
COUNT(CASE WHEN is_online = TRUE THEN 1 END) as online_sessions,
|
|
301
|
+
MIN(created_at) as oldest_session,
|
|
302
|
+
MAX(updated_at) as latest_activity,
|
|
303
|
+
pg_size_pretty(pg_total_relation_size('${this.schema}.${this.tableName}')) as table_size
|
|
304
|
+
FROM ${this.schema}.${this.tableName}
|
|
305
|
+
`);
|
|
306
|
+
|
|
307
|
+
return result.rows[0];
|
|
308
|
+
} finally {
|
|
309
|
+
client.release();
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
async healthCheck() {
|
|
314
|
+
try {
|
|
315
|
+
await this.ensureConnected();
|
|
316
|
+
const client = await this.pool.connect();
|
|
317
|
+
await client.query('SELECT 1');
|
|
318
|
+
client.release();
|
|
319
|
+
|
|
320
|
+
return {
|
|
321
|
+
status: 'healthy',
|
|
322
|
+
storage: 'postgresql',
|
|
323
|
+
database: this.config.database,
|
|
324
|
+
table: `${this.schema}.${this.tableName}`
|
|
325
|
+
};
|
|
326
|
+
} catch (error) {
|
|
327
|
+
return {
|
|
328
|
+
status: 'unhealthy',
|
|
329
|
+
storage: 'postgresql',
|
|
330
|
+
error: error.message
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// PostgreSQL-specific: Full-text search
|
|
336
|
+
async searchSessions(searchTerm) {
|
|
337
|
+
await this.ensureConnected();
|
|
338
|
+
|
|
339
|
+
const client = await this.pool.connect();
|
|
340
|
+
try {
|
|
341
|
+
const result = await client.query(
|
|
342
|
+
`SELECT * FROM ${this.schema}.${this.tableName}
|
|
343
|
+
WHERE site_domain ILIKE $1
|
|
344
|
+
OR access_token ILIKE $1
|
|
345
|
+
OR scope ILIKE $1
|
|
346
|
+
OR (metadata::text) ILIKE $1
|
|
347
|
+
ORDER BY updated_at DESC`,
|
|
348
|
+
[`%${searchTerm}%`]
|
|
349
|
+
);
|
|
350
|
+
|
|
351
|
+
return result.rows.map(row => ({
|
|
352
|
+
id: row.id,
|
|
353
|
+
site_domain: row.site_domain,
|
|
354
|
+
access_token: row.access_token,
|
|
355
|
+
scope: row.scope,
|
|
356
|
+
user: row.user_data || null,
|
|
357
|
+
expires_at: row.expires_at,
|
|
358
|
+
authenticated_at: row.authenticated_at,
|
|
359
|
+
is_online: Boolean(row.is_online),
|
|
360
|
+
online_access_info: row.online_access_info || {},
|
|
361
|
+
state: row.state,
|
|
362
|
+
metadata: row.metadata || {},
|
|
363
|
+
created_at: row.created_at,
|
|
364
|
+
updated_at: row.updated_at
|
|
365
|
+
}));
|
|
366
|
+
} finally {
|
|
367
|
+
client.release();
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
module.exports = PostgreSQLStorage;
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
// src/Auth/session/storage/RedisStorage.js
|
|
2
|
+
let redis;
|
|
3
|
+
|
|
4
|
+
try {
|
|
5
|
+
redis = require('redis');
|
|
6
|
+
} catch (error) {
|
|
7
|
+
// redis not installed
|
|
8
|
+
redis = null;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
class RedisStorage {
|
|
12
|
+
constructor(options = {}) {
|
|
13
|
+
if (!redis) {
|
|
14
|
+
throw new Error('redis package not installed. Run: npm install redis');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
this.url = options.url || 'redis://localhost:6379';
|
|
18
|
+
this.prefix = options.prefix || 'joonweb:session:';
|
|
19
|
+
this.client = null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async connect() {
|
|
23
|
+
if (!this.client) {
|
|
24
|
+
this.client = redis.createClient({ url: this.url });
|
|
25
|
+
await this.client.connect();
|
|
26
|
+
|
|
27
|
+
// Handle connection errors
|
|
28
|
+
this.client.on('error', (err) => {
|
|
29
|
+
console.error('Redis connection error:', err);
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
return this.client;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async ensureConnected() {
|
|
36
|
+
if (!this.client) {
|
|
37
|
+
await this.connect();
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async store(siteDomain, session) {
|
|
42
|
+
await this.ensureConnected();
|
|
43
|
+
|
|
44
|
+
const key = `${this.prefix}${siteDomain}`;
|
|
45
|
+
const sessionData = JSON.stringify(session);
|
|
46
|
+
|
|
47
|
+
if (session.expires_at) {
|
|
48
|
+
const ttl = Math.floor((session.expires_at - Date.now()) / 1000);
|
|
49
|
+
if (ttl > 0) {
|
|
50
|
+
await this.client.setEx(key, ttl, sessionData);
|
|
51
|
+
} else {
|
|
52
|
+
await this.client.set(key, sessionData);
|
|
53
|
+
}
|
|
54
|
+
} else {
|
|
55
|
+
await this.client.set(key, sessionData);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async load(siteDomain) {
|
|
62
|
+
await this.ensureConnected();
|
|
63
|
+
|
|
64
|
+
const key = `${this.prefix}${siteDomain}`;
|
|
65
|
+
const data = await this.client.get(key);
|
|
66
|
+
|
|
67
|
+
if (!data) return null;
|
|
68
|
+
|
|
69
|
+
try {
|
|
70
|
+
return JSON.parse(data);
|
|
71
|
+
} catch (error) {
|
|
72
|
+
console.error('Failed to parse Redis session data:', error);
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async delete(siteDomain) {
|
|
78
|
+
await this.ensureConnected();
|
|
79
|
+
|
|
80
|
+
const key = `${this.prefix}${siteDomain}`;
|
|
81
|
+
const result = await this.client.del(key);
|
|
82
|
+
return result > 0;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async list() {
|
|
86
|
+
await this.ensureConnected();
|
|
87
|
+
|
|
88
|
+
const keys = await this.client.keys(`${this.prefix}*`);
|
|
89
|
+
const sessions = [];
|
|
90
|
+
|
|
91
|
+
for (const key of keys) {
|
|
92
|
+
const data = await this.client.get(key);
|
|
93
|
+
if (data) {
|
|
94
|
+
try {
|
|
95
|
+
sessions.push(JSON.parse(data));
|
|
96
|
+
} catch (error) {
|
|
97
|
+
console.error(`Failed to parse session from key ${key}:`, error);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return sessions;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async clear() {
|
|
106
|
+
await this.ensureConnected();
|
|
107
|
+
|
|
108
|
+
const keys = await this.client.keys(`${this.prefix}*`);
|
|
109
|
+
if (keys.length > 0) {
|
|
110
|
+
await this.client.del(keys);
|
|
111
|
+
}
|
|
112
|
+
return true;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async close() {
|
|
116
|
+
if (this.client) {
|
|
117
|
+
await this.client.quit();
|
|
118
|
+
}
|
|
119
|
+
return true;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async ping() {
|
|
123
|
+
await this.ensureConnected();
|
|
124
|
+
return await this.client.ping();
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async info() {
|
|
128
|
+
await this.ensureConnected();
|
|
129
|
+
return await this.client.info();
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
module.exports = RedisStorage;
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
// src/Auth/session/storage/SQLiteStorage.js
|
|
2
|
+
const sqlite3 = require('sqlite3').verbose();
|
|
3
|
+
|
|
4
|
+
class SQLiteStorage {
|
|
5
|
+
constructor(options = {}) {
|
|
6
|
+
this.dbPath = options.path || './joonweb-sessions.db';
|
|
7
|
+
this.db = new sqlite3.Database(this.dbPath);
|
|
8
|
+
this.initDatabase();
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
async initDatabase() {
|
|
12
|
+
return new Promise((resolve, reject) => {
|
|
13
|
+
this.db.run(`
|
|
14
|
+
CREATE TABLE IF NOT EXISTS sessions (
|
|
15
|
+
site_domain TEXT PRIMARY KEY,
|
|
16
|
+
access_token TEXT NOT NULL,
|
|
17
|
+
scope TEXT,
|
|
18
|
+
user_data TEXT,
|
|
19
|
+
expires_at INTEGER,
|
|
20
|
+
authenticated_at INTEGER,
|
|
21
|
+
created_at INTEGER DEFAULT (strftime('%s', 'now')),
|
|
22
|
+
updated_at INTEGER DEFAULT (strftime('%s', 'now')),
|
|
23
|
+
metadata TEXT
|
|
24
|
+
)
|
|
25
|
+
`, (err) => {
|
|
26
|
+
if (err) reject(err);
|
|
27
|
+
else resolve();
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async store(siteDomain, session) {
|
|
33
|
+
return new Promise((resolve, reject) => {
|
|
34
|
+
this.db.run(
|
|
35
|
+
`INSERT OR REPLACE INTO sessions
|
|
36
|
+
(site_domain, access_token, scope, user_data, expires_at,
|
|
37
|
+
authenticated_at, updated_at, metadata)
|
|
38
|
+
VALUES (?, ?, ?, ?, ?, ?, strftime('%s', 'now'), ?)`,
|
|
39
|
+
[
|
|
40
|
+
siteDomain,
|
|
41
|
+
session.access_token,
|
|
42
|
+
session.scope,
|
|
43
|
+
JSON.stringify(session.user || {}),
|
|
44
|
+
session.expires_at,
|
|
45
|
+
session.authenticated_at,
|
|
46
|
+
JSON.stringify(session.metadata || {})
|
|
47
|
+
],
|
|
48
|
+
(err) => {
|
|
49
|
+
if (err) reject(err);
|
|
50
|
+
else resolve(true);
|
|
51
|
+
}
|
|
52
|
+
);
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async load(siteDomain) {
|
|
57
|
+
return new Promise((resolve, reject) => {
|
|
58
|
+
this.db.get(
|
|
59
|
+
'SELECT * FROM sessions WHERE site_domain = ?',
|
|
60
|
+
[siteDomain],
|
|
61
|
+
(err, row) => {
|
|
62
|
+
if (err) {
|
|
63
|
+
reject(err);
|
|
64
|
+
} else if (row) {
|
|
65
|
+
// Parse JSON fields
|
|
66
|
+
const session = {
|
|
67
|
+
site_domain: row.site_domain,
|
|
68
|
+
access_token: row.access_token,
|
|
69
|
+
scope: row.scope,
|
|
70
|
+
user: JSON.parse(row.user_data || '{}'),
|
|
71
|
+
expires_at: row.expires_at,
|
|
72
|
+
authenticated_at: row.authenticated_at,
|
|
73
|
+
metadata: JSON.parse(row.metadata || {})
|
|
74
|
+
};
|
|
75
|
+
resolve(session);
|
|
76
|
+
} else {
|
|
77
|
+
resolve(null);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
);
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async delete(siteDomain) {
|
|
85
|
+
return new Promise((resolve, reject) => {
|
|
86
|
+
this.db.run('DELETE FROM sessions WHERE site_domain = ?', [siteDomain], (err) => {
|
|
87
|
+
if (err) reject(err);
|
|
88
|
+
else resolve(true);
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async list() {
|
|
94
|
+
return new Promise((resolve, reject) => {
|
|
95
|
+
this.db.all('SELECT * FROM sessions', (err, rows) => {
|
|
96
|
+
if (err) {
|
|
97
|
+
reject(err);
|
|
98
|
+
} else {
|
|
99
|
+
const sessions = rows.map(row => ({
|
|
100
|
+
site_domain: row.site_domain,
|
|
101
|
+
access_token: row.access_token,
|
|
102
|
+
scope: row.scope,
|
|
103
|
+
user: JSON.parse(row.user_data || '{}'),
|
|
104
|
+
expires_at: row.expires_at,
|
|
105
|
+
authenticated_at: row.authenticated_at,
|
|
106
|
+
metadata: JSON.parse(row.metadata || {})
|
|
107
|
+
}));
|
|
108
|
+
resolve(sessions);
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async clear() {
|
|
115
|
+
return new Promise((resolve, reject) => {
|
|
116
|
+
this.db.run('DELETE FROM sessions', (err) => {
|
|
117
|
+
if (err) reject(err);
|
|
118
|
+
else resolve(true);
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
async close() {
|
|
124
|
+
return new Promise((resolve, reject) => {
|
|
125
|
+
this.db.close((err) => {
|
|
126
|
+
if (err) reject(err);
|
|
127
|
+
else resolve(true);
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
module.exports = SQLiteStorage;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// src/Auth/session/storage/index.js
|
|
2
|
+
const MemoryStorage = require('./MemoryStorage');
|
|
3
|
+
const SQLiteStorage = require('./SQLiteStorage');
|
|
4
|
+
const MongoDBStorage = require('./MongoDBStorage');
|
|
5
|
+
const RedisStorage = require('./RedisStorage');
|
|
6
|
+
const MySQLStorage = require('./MySQLStorage');
|
|
7
|
+
const PostgreSQLStorage = require('./PostgreSQLStorage');
|
|
8
|
+
|
|
9
|
+
function createStorageAdapter(type = 'memory', options = {}) {
|
|
10
|
+
const normalizedType = type.toLowerCase();
|
|
11
|
+
|
|
12
|
+
switch (normalizedType) {
|
|
13
|
+
case 'sqlite':
|
|
14
|
+
return new SQLiteStorage(options);
|
|
15
|
+
|
|
16
|
+
case 'mongodb':
|
|
17
|
+
return new MongoDBStorage(options);
|
|
18
|
+
|
|
19
|
+
case 'redis':
|
|
20
|
+
return new RedisStorage(options);
|
|
21
|
+
|
|
22
|
+
case 'mysql':
|
|
23
|
+
case 'mariadb':
|
|
24
|
+
return new MySQLStorage(options);
|
|
25
|
+
|
|
26
|
+
case 'postgres':
|
|
27
|
+
case 'postgresql':
|
|
28
|
+
case 'pgsql':
|
|
29
|
+
return new PostgreSQLStorage(options);
|
|
30
|
+
|
|
31
|
+
case 'memory':
|
|
32
|
+
default:
|
|
33
|
+
return new MemoryStorage();
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Export everything
|
|
38
|
+
module.exports = {
|
|
39
|
+
MemoryStorage,
|
|
40
|
+
SQLiteStorage,
|
|
41
|
+
MongoDBStorage,
|
|
42
|
+
RedisStorage,
|
|
43
|
+
MySQLStorage,
|
|
44
|
+
PostgreSQLStorage,
|
|
45
|
+
createStorageAdapter
|
|
46
|
+
};
|