@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.
@@ -0,0 +1,107 @@
1
+ // src/Auth/session/storage/MongoDBStorage.js
2
+ let MongoClient;
3
+
4
+ try {
5
+ MongoClient = require('mongodb').MongoClient;
6
+ } catch (error) {
7
+ // mongodb not installed
8
+ MongoClient = null;
9
+ }
10
+
11
+ class MongoDBStorage {
12
+ constructor(options = {}) {
13
+ if (!MongoClient) {
14
+ throw new Error('mongodb package not installed. Run: npm install mongodb');
15
+ }
16
+
17
+ this.url = options.url || 'mongodb://localhost:27017';
18
+ this.dbName = options.dbName || 'joonweb';
19
+ this.collectionName = options.collectionName || 'sessions';
20
+ this.client = null;
21
+ this.collection = null;
22
+ }
23
+
24
+ async initDatabase() {
25
+ this.client = new MongoClient(this.url);
26
+ await this.client.connect();
27
+ const db = this.client.db(this.dbName);
28
+ this.collection = db.collection(this.collectionName);
29
+
30
+ // Create TTL index for automatic expiry
31
+ await this.collection.createIndex(
32
+ { expires_at: 1 },
33
+ { expireAfterSeconds: 0 }
34
+ );
35
+
36
+ // Create index on site_domain
37
+ await this.collection.createIndex({ site_domain: 1 }, { unique: true });
38
+ }
39
+
40
+ async ensureConnected() {
41
+ if (!this.collection) {
42
+ await this.initDatabase();
43
+ }
44
+ }
45
+
46
+ async store(siteDomain, session) {
47
+ await this.ensureConnected();
48
+
49
+ await this.collection.updateOne(
50
+ { site_domain: siteDomain },
51
+ {
52
+ $set: {
53
+ ...session,
54
+ updated_at: Date.now(),
55
+ _id: siteDomain // Use siteDomain as _id for uniqueness
56
+ }
57
+ },
58
+ { upsert: true }
59
+ );
60
+ return true;
61
+ }
62
+
63
+ async load(siteDomain) {
64
+ await this.ensureConnected();
65
+ const session = await this.collection.findOne({ site_domain: siteDomain });
66
+
67
+ if (session) {
68
+ // Remove MongoDB _id field
69
+ delete session._id;
70
+ return session;
71
+ }
72
+
73
+ return null;
74
+ }
75
+
76
+ async delete(siteDomain) {
77
+ await this.ensureConnected();
78
+ const result = await this.collection.deleteOne({ site_domain: siteDomain });
79
+ return result.deletedCount > 0;
80
+ }
81
+
82
+ async list() {
83
+ await this.ensureConnected();
84
+ const sessions = await this.collection.find({}).toArray();
85
+
86
+ // Remove MongoDB _id field from all sessions
87
+ return sessions.map(session => {
88
+ const { _id, ...rest } = session;
89
+ return rest;
90
+ });
91
+ }
92
+
93
+ async clear() {
94
+ await this.ensureConnected();
95
+ await this.collection.deleteMany({});
96
+ return true;
97
+ }
98
+
99
+ async close() {
100
+ if (this.client) {
101
+ await this.client.close();
102
+ }
103
+ return true;
104
+ }
105
+ }
106
+
107
+ module.exports = MongoDBStorage;
@@ -0,0 +1,281 @@
1
+ // src/Auth/session/storage/MySQLStorage.js
2
+ let mysql;
3
+
4
+ try {
5
+ mysql = require('mysql2/promise');
6
+ } catch (error) {
7
+ // mysql2 not installed
8
+ mysql = null;
9
+ }
10
+
11
+ class MySQLStorage {
12
+ constructor(options = {}) {
13
+ if (!mysql) {
14
+ throw new Error('mysql2 package not installed. Run: npm install mysql2');
15
+ }
16
+
17
+ this.config = {
18
+ host: options.host || 'localhost',
19
+ port: options.port || 3306,
20
+ user: options.user || 'root',
21
+ password: options.password || '',
22
+ database: options.database || 'joonweb_sessions',
23
+ connectionLimit: options.connectionLimit || 10,
24
+ waitForConnections: true,
25
+ queueLimit: 0,
26
+ enableKeepAlive: true,
27
+ keepAliveInitialDelay: 0
28
+ };
29
+
30
+ this.tableName = options.tableName || 'sessions';
31
+ this.pool = null;
32
+ }
33
+
34
+ async initDatabase() {
35
+ if (!this.pool) {
36
+ this.pool = mysql.createPool(this.config);
37
+
38
+ // Create table if not exists
39
+ await this.createTable();
40
+ }
41
+ }
42
+
43
+ async createTable() {
44
+ const connection = await this.pool.getConnection();
45
+ try {
46
+ await connection.query(`
47
+ CREATE TABLE IF NOT EXISTS \`${this.tableName}\` (
48
+ \`id\` VARCHAR(255) NOT NULL,
49
+ \`site_domain\` VARCHAR(255) NOT NULL,
50
+ \`access_token\` TEXT NOT NULL,
51
+ \`scope\` TEXT,
52
+ \`user_data\` JSON,
53
+ \`expires_at\` BIGINT,
54
+ \`authenticated_at\` BIGINT,
55
+ \`created_at\` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
56
+ \`updated_at\` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
57
+ \`is_online\` BOOLEAN DEFAULT FALSE,
58
+ \`online_access_info\` JSON,
59
+ \`state\` VARCHAR(255),
60
+ \`metadata\` JSON,
61
+ PRIMARY KEY (\`id\`),
62
+ UNIQUE KEY \`idx_site_domain\` (\`site_domain\`),
63
+ KEY \`idx_expires_at\` (\`expires_at\`),
64
+ KEY \`idx_created_at\` (\`created_at\`)
65
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
66
+ `);
67
+
68
+ // Add cleanup event for expired sessions
69
+ await this.cleanupExpiredSessions();
70
+ } finally {
71
+ connection.release();
72
+ }
73
+ }
74
+
75
+ async ensureConnected() {
76
+ if (!this.pool) {
77
+ await this.initDatabase();
78
+ }
79
+ }
80
+
81
+ async store(siteDomain, session) {
82
+ await this.ensureConnected();
83
+
84
+ const connection = await this.pool.getConnection();
85
+ try {
86
+ // Generate a unique ID for the session
87
+ const sessionId = require('crypto').randomBytes(16).toString('hex');
88
+
89
+ await connection.query(
90
+ `INSERT INTO \`${this.tableName}\`
91
+ (id, site_domain, access_token, scope, user_data, expires_at, authenticated_at, is_online, online_access_info, state, metadata)
92
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
93
+ ON DUPLICATE KEY UPDATE
94
+ access_token = VALUES(access_token),
95
+ scope = VALUES(scope),
96
+ user_data = VALUES(user_data),
97
+ expires_at = VALUES(expires_at),
98
+ is_online = VALUES(is_online),
99
+ online_access_info = VALUES(online_access_info),
100
+ state = VALUES(state),
101
+ metadata = VALUES(metadata),
102
+ updated_at = CURRENT_TIMESTAMP`,
103
+ [
104
+ sessionId,
105
+ siteDomain,
106
+ session.access_token,
107
+ session.scope,
108
+ JSON.stringify(session.user || {}),
109
+ session.expires_at,
110
+ session.authenticated_at,
111
+ session.is_online || false,
112
+ JSON.stringify(session.online_access_info || {}),
113
+ session.state || '',
114
+ JSON.stringify(session.metadata || {})
115
+ ]
116
+ );
117
+
118
+ return true;
119
+ } finally {
120
+ connection.release();
121
+ }
122
+ }
123
+
124
+ async load(siteDomain) {
125
+ await this.ensureConnected();
126
+
127
+ const connection = await this.pool.getConnection();
128
+ try {
129
+ const [rows] = await connection.query(
130
+ `SELECT * FROM \`${this.tableName}\` WHERE site_domain = ? ORDER BY updated_at DESC LIMIT 1`,
131
+ [siteDomain]
132
+ );
133
+
134
+ if (rows.length === 0) {
135
+ return null;
136
+ }
137
+
138
+ const row = rows[0];
139
+
140
+ // Parse JSON fields
141
+ const session = {
142
+ id: row.id,
143
+ site_domain: row.site_domain,
144
+ access_token: row.access_token,
145
+ scope: row.scope,
146
+ user: row.user_data ? JSON.parse(row.user_data) : null,
147
+ expires_at: row.expires_at,
148
+ authenticated_at: row.authenticated_at,
149
+ is_online: Boolean(row.is_online),
150
+ online_access_info: row.online_access_info ? JSON.parse(row.online_access_info) : {},
151
+ state: row.state,
152
+ metadata: row.metadata ? JSON.parse(row.metadata) : {},
153
+ created_at: row.created_at,
154
+ updated_at: row.updated_at
155
+ };
156
+
157
+ return session;
158
+ } finally {
159
+ connection.release();
160
+ }
161
+ }
162
+
163
+ async delete(siteDomain) {
164
+ await this.ensureConnected();
165
+
166
+ const connection = await this.pool.getConnection();
167
+ try {
168
+ const [result] = await connection.query(
169
+ `DELETE FROM \`${this.tableName}\` WHERE site_domain = ?`,
170
+ [siteDomain]
171
+ );
172
+
173
+ return result.affectedRows > 0;
174
+ } finally {
175
+ connection.release();
176
+ }
177
+ }
178
+
179
+ async list() {
180
+ await this.ensureConnected();
181
+
182
+ const connection = await this.pool.getConnection();
183
+ try {
184
+ const [rows] = await connection.query(
185
+ `SELECT * FROM \`${this.tableName}\` ORDER BY updated_at DESC`
186
+ );
187
+
188
+ return rows.map(row => ({
189
+ id: row.id,
190
+ site_domain: row.site_domain,
191
+ access_token: row.access_token,
192
+ scope: row.scope,
193
+ user: row.user_data ? JSON.parse(row.user_data) : null,
194
+ expires_at: row.expires_at,
195
+ authenticated_at: row.authenticated_at,
196
+ is_online: Boolean(row.is_online),
197
+ online_access_info: row.online_access_info ? JSON.parse(row.online_access_info) : {},
198
+ state: row.state,
199
+ metadata: row.metadata ? JSON.parse(row.metadata) : {},
200
+ created_at: row.created_at,
201
+ updated_at: row.updated_at
202
+ }));
203
+ } finally {
204
+ connection.release();
205
+ }
206
+ }
207
+
208
+ async clear() {
209
+ await this.ensureConnected();
210
+
211
+ const connection = await this.pool.getConnection();
212
+ try {
213
+ await connection.query(`TRUNCATE TABLE \`${this.tableName}\``);
214
+ return true;
215
+ } finally {
216
+ connection.release();
217
+ }
218
+ }
219
+
220
+ async close() {
221
+ if (this.pool) {
222
+ await this.pool.end();
223
+ }
224
+ return true;
225
+ }
226
+
227
+ async cleanupExpiredSessions() {
228
+ await this.ensureConnected();
229
+
230
+ const connection = await this.pool.getConnection();
231
+ try {
232
+ const [result] = await connection.query(
233
+ `DELETE FROM \`${this.tableName}\` WHERE expires_at IS NOT NULL AND expires_at < ?`,
234
+ [Date.now()]
235
+ );
236
+
237
+ if (result.affectedRows > 0) {
238
+ console.log(`🧹 Cleaned up ${result.affectedRows} expired sessions from MySQL`);
239
+ }
240
+
241
+ return result.affectedRows;
242
+ } finally {
243
+ connection.release();
244
+ }
245
+ }
246
+
247
+ async getStats() {
248
+ await this.ensureConnected();
249
+
250
+ const connection = await this.pool.getConnection();
251
+ try {
252
+ const [rows] = await connection.query(`
253
+ SELECT
254
+ COUNT(*) as total_sessions,
255
+ COUNT(CASE WHEN expires_at IS NULL OR expires_at > ? THEN 1 END) as active_sessions,
256
+ COUNT(CASE WHEN is_online = TRUE THEN 1 END) as online_sessions,
257
+ MIN(created_at) as oldest_session,
258
+ MAX(updated_at) as latest_activity
259
+ FROM \`${this.tableName}\`
260
+ `, [Date.now()]);
261
+
262
+ return rows[0];
263
+ } finally {
264
+ connection.release();
265
+ }
266
+ }
267
+
268
+ async healthCheck() {
269
+ try {
270
+ await this.ensureConnected();
271
+ const connection = await this.pool.getConnection();
272
+ await connection.ping();
273
+ connection.release();
274
+ return { status: 'healthy', storage: 'mysql' };
275
+ } catch (error) {
276
+ return { status: 'unhealthy', storage: 'mysql', error: error.message };
277
+ }
278
+ }
279
+ }
280
+
281
+ module.exports = MySQLStorage;