@alliance-droid/svelte-auth-core 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.
Files changed (178) hide show
  1. package/dist/adapter-context.d.ts +19 -0
  2. package/dist/adapter-context.d.ts.map +1 -0
  3. package/dist/adapter-context.js +68 -0
  4. package/dist/adapter-context.js.map +1 -0
  5. package/dist/adapters/__tests__/adapter-tests.d.ts +7 -0
  6. package/dist/adapters/__tests__/adapter-tests.d.ts.map +1 -0
  7. package/dist/adapters/__tests__/adapter-tests.js +206 -0
  8. package/dist/adapters/__tests__/adapter-tests.js.map +1 -0
  9. package/dist/adapters/adapter.d.ts +60 -0
  10. package/dist/adapters/adapter.d.ts.map +1 -0
  11. package/dist/adapters/adapter.js +2 -0
  12. package/dist/adapters/adapter.js.map +1 -0
  13. package/dist/adapters/filesystem-adapter.d.ts +26 -0
  14. package/dist/adapters/filesystem-adapter.d.ts.map +1 -0
  15. package/dist/adapters/filesystem-adapter.js +148 -0
  16. package/dist/adapters/filesystem-adapter.js.map +1 -0
  17. package/dist/adapters/index.d.ts +6 -0
  18. package/dist/adapters/index.d.ts.map +1 -0
  19. package/dist/adapters/index.js +5 -0
  20. package/dist/adapters/index.js.map +1 -0
  21. package/dist/adapters/mongodb-adapter.d.ts +27 -0
  22. package/dist/adapters/mongodb-adapter.d.ts.map +1 -0
  23. package/dist/adapters/mongodb-adapter.js +213 -0
  24. package/dist/adapters/mongodb-adapter.js.map +1 -0
  25. package/dist/adapters/postgres-adapter.d.ts +30 -0
  26. package/dist/adapters/postgres-adapter.d.ts.map +1 -0
  27. package/dist/adapters/postgres-adapter.js +237 -0
  28. package/dist/adapters/postgres-adapter.js.map +1 -0
  29. package/dist/adapters/sqlite-adapter.d.ts +26 -0
  30. package/dist/adapters/sqlite-adapter.d.ts.map +1 -0
  31. package/dist/adapters/sqlite-adapter.js +261 -0
  32. package/dist/adapters/sqlite-adapter.js.map +1 -0
  33. package/dist/auth.d.ts +48 -0
  34. package/dist/auth.d.ts.map +1 -0
  35. package/dist/auth.js +205 -0
  36. package/dist/auth.js.map +1 -0
  37. package/dist/client-jwt.d.ts +30 -0
  38. package/dist/client-jwt.d.ts.map +1 -0
  39. package/dist/client-jwt.js +57 -0
  40. package/dist/client-jwt.js.map +1 -0
  41. package/dist/client-store.d.ts +31 -0
  42. package/dist/client-store.d.ts.map +1 -0
  43. package/dist/client-store.js +122 -0
  44. package/dist/client-store.js.map +1 -0
  45. package/dist/cors.d.ts +48 -0
  46. package/dist/cors.d.ts.map +1 -0
  47. package/dist/cors.js +88 -0
  48. package/dist/cors.js.map +1 -0
  49. package/dist/csrf.d.ts +57 -0
  50. package/dist/csrf.d.ts.map +1 -0
  51. package/dist/csrf.js +95 -0
  52. package/dist/csrf.js.map +1 -0
  53. package/dist/db.d.ts +22 -0
  54. package/dist/db.d.ts.map +1 -0
  55. package/dist/db.js +43 -0
  56. package/dist/db.js.map +1 -0
  57. package/dist/index.d.ts +35 -0
  58. package/dist/index.d.ts.map +1 -0
  59. package/dist/index.js +36 -0
  60. package/dist/index.js.map +1 -0
  61. package/dist/input-validation.d.ts +78 -0
  62. package/dist/input-validation.d.ts.map +1 -0
  63. package/dist/input-validation.js +238 -0
  64. package/dist/input-validation.js.map +1 -0
  65. package/dist/oauth-callback.d.ts +31 -0
  66. package/dist/oauth-callback.d.ts.map +1 -0
  67. package/dist/oauth-callback.js +254 -0
  68. package/dist/oauth-callback.js.map +1 -0
  69. package/dist/oauth-providers.d.ts +92 -0
  70. package/dist/oauth-providers.d.ts.map +1 -0
  71. package/dist/oauth-providers.js +213 -0
  72. package/dist/oauth-providers.js.map +1 -0
  73. package/dist/oauth-types.d.ts +77 -0
  74. package/dist/oauth-types.d.ts.map +1 -0
  75. package/dist/oauth-types.js +2 -0
  76. package/dist/oauth-types.js.map +1 -0
  77. package/dist/password.d.ts +31 -0
  78. package/dist/password.d.ts.map +1 -0
  79. package/dist/password.js +54 -0
  80. package/dist/password.js.map +1 -0
  81. package/dist/providers/github-oauth.d.ts +58 -0
  82. package/dist/providers/github-oauth.d.ts.map +1 -0
  83. package/dist/providers/github-oauth.js +230 -0
  84. package/dist/providers/github-oauth.js.map +1 -0
  85. package/dist/providers/google-oauth.d.ts +46 -0
  86. package/dist/providers/google-oauth.d.ts.map +1 -0
  87. package/dist/providers/google-oauth.js +177 -0
  88. package/dist/providers/google-oauth.js.map +1 -0
  89. package/dist/providers/oidc-oauth.d.ts +85 -0
  90. package/dist/providers/oidc-oauth.d.ts.map +1 -0
  91. package/dist/providers/oidc-oauth.js +301 -0
  92. package/dist/providers/oidc-oauth.js.map +1 -0
  93. package/dist/rate-limit.d.ts +36 -0
  94. package/dist/rate-limit.d.ts.map +1 -0
  95. package/dist/rate-limit.js +88 -0
  96. package/dist/rate-limit.js.map +1 -0
  97. package/dist/rate-limiting.d.ts +113 -0
  98. package/dist/rate-limiting.d.ts.map +1 -0
  99. package/dist/rate-limiting.js +221 -0
  100. package/dist/rate-limiting.js.map +1 -0
  101. package/dist/security-headers.d.ts +54 -0
  102. package/dist/security-headers.d.ts.map +1 -0
  103. package/dist/security-headers.js +123 -0
  104. package/dist/security-headers.js.map +1 -0
  105. package/dist/session.d.ts +13 -0
  106. package/dist/session.d.ts.map +1 -0
  107. package/dist/session.js +33 -0
  108. package/dist/session.js.map +1 -0
  109. package/dist/sql-injection-prevention.d.ts +94 -0
  110. package/dist/sql-injection-prevention.d.ts.map +1 -0
  111. package/dist/sql-injection-prevention.js +222 -0
  112. package/dist/sql-injection-prevention.js.map +1 -0
  113. package/dist/token.d.ts +22 -0
  114. package/dist/token.d.ts.map +1 -0
  115. package/dist/token.js +31 -0
  116. package/dist/token.js.map +1 -0
  117. package/dist/types.d.ts +81 -0
  118. package/dist/types.d.ts.map +1 -0
  119. package/dist/types.js +2 -0
  120. package/dist/types.js.map +1 -0
  121. package/dist/user.d.ts +33 -0
  122. package/dist/user.d.ts.map +1 -0
  123. package/dist/user.js +144 -0
  124. package/dist/user.js.map +1 -0
  125. package/package.json +48 -0
  126. package/src/adapter-context.ts +72 -0
  127. package/src/adapters/__tests__/adapter-tests.ts +254 -0
  128. package/src/adapters/__tests__/filesystem-adapter.test.ts +48 -0
  129. package/src/adapters/__tests__/mongodb-adapter.test.ts +64 -0
  130. package/src/adapters/__tests__/postgres-adapter.test.ts +62 -0
  131. package/src/adapters/__tests__/sqlite-adapter.test.ts +103 -0
  132. package/src/adapters/__tests__/test-fs-adapter.json +4 -0
  133. package/src/adapters/adapter.ts +72 -0
  134. package/src/adapters/filesystem-adapter.ts +153 -0
  135. package/src/adapters/index.ts +5 -0
  136. package/src/adapters/mongodb-adapter.ts +208 -0
  137. package/src/adapters/postgres-adapter.ts +261 -0
  138. package/src/adapters/sqlite-adapter.ts +284 -0
  139. package/src/auth.ts +239 -0
  140. package/src/client-jwt.test.ts +137 -0
  141. package/src/client-jwt.ts +67 -0
  142. package/src/client-store.test.ts +149 -0
  143. package/src/client-store.ts +144 -0
  144. package/src/cors.test.ts +175 -0
  145. package/src/cors.ts +115 -0
  146. package/src/csrf.test.ts +226 -0
  147. package/src/csrf.ts +126 -0
  148. package/src/db.ts +57 -0
  149. package/src/index.ts +143 -0
  150. package/src/input-validation.test.ts +347 -0
  151. package/src/input-validation.ts +307 -0
  152. package/src/integration.test.ts +322 -0
  153. package/src/oauth-callback.test.ts +282 -0
  154. package/src/oauth-callback.ts +323 -0
  155. package/src/oauth-providers.ts +232 -0
  156. package/src/oauth-types.ts +82 -0
  157. package/src/password.test.ts +89 -0
  158. package/src/password.ts +62 -0
  159. package/src/providers/github-oauth.test.ts +290 -0
  160. package/src/providers/github-oauth.ts +226 -0
  161. package/src/providers/google-oauth.test.ts +240 -0
  162. package/src/providers/google-oauth.ts +166 -0
  163. package/src/providers/oidc-oauth.test.ts +367 -0
  164. package/src/providers/oidc-oauth.ts +302 -0
  165. package/src/rate-limit.test.ts +308 -0
  166. package/src/rate-limit.ts +118 -0
  167. package/src/rate-limiting.test.ts +390 -0
  168. package/src/rate-limiting.ts +275 -0
  169. package/src/security-headers.test.ts +242 -0
  170. package/src/security-headers.ts +160 -0
  171. package/src/security-penetration.test.ts +705 -0
  172. package/src/session.ts +42 -0
  173. package/src/sql-injection-prevention.test.ts +337 -0
  174. package/src/sql-injection-prevention.ts +272 -0
  175. package/src/token.test.ts +67 -0
  176. package/src/token.ts +34 -0
  177. package/src/types.ts +87 -0
  178. package/src/user.ts +165 -0
@@ -0,0 +1,72 @@
1
+ import type { DatabaseAdapter } from './adapters/adapter';
2
+ import { FileSystemAdapter } from './adapters/filesystem-adapter';
3
+
4
+ /**
5
+ * Global adapter context
6
+ * Manages the active database adapter instance
7
+ */
8
+ class AdapterContext {
9
+ private adapter: DatabaseAdapter | null = null;
10
+
11
+ /**
12
+ * Set the database adapter
13
+ */
14
+ setAdapter(adapter: DatabaseAdapter): void {
15
+ this.adapter = adapter;
16
+ }
17
+
18
+ /**
19
+ * Get the current database adapter
20
+ * Throws if no adapter is set
21
+ */
22
+ getAdapter(): DatabaseAdapter {
23
+ if (!this.adapter) {
24
+ throw new Error(
25
+ 'Database adapter not initialized. Call initializeAdapter() first.'
26
+ );
27
+ }
28
+ return this.adapter;
29
+ }
30
+
31
+ /**
32
+ * Check if adapter is initialized
33
+ */
34
+ isInitialized(): boolean {
35
+ return this.adapter !== null;
36
+ }
37
+ }
38
+
39
+ // Global singleton instance
40
+ const context = new AdapterContext();
41
+
42
+ /**
43
+ * Initialize the database adapter
44
+ * Must be called before any auth operations
45
+ */
46
+ export async function initializeAdapter(adapter: DatabaseAdapter): Promise<void> {
47
+ context.setAdapter(adapter);
48
+ await adapter.initialize();
49
+ }
50
+
51
+ /**
52
+ * Initialize with the default FileSystem adapter
53
+ */
54
+ export async function initializeDefaultAdapter(dbPath?: string): Promise<void> {
55
+ const adapter = new FileSystemAdapter(dbPath);
56
+ await initializeAdapter(adapter);
57
+ }
58
+
59
+ /**
60
+ * Get the current adapter
61
+ */
62
+ export function getAdapter(): DatabaseAdapter {
63
+ return context.getAdapter();
64
+ }
65
+
66
+ /**
67
+ * Close the current adapter
68
+ */
69
+ export async function closeAdapter(): Promise<void> {
70
+ const adapter = context.getAdapter();
71
+ await adapter.close();
72
+ }
@@ -0,0 +1,254 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
+ import type { DatabaseAdapter } from '../adapter';
3
+ import type { User, Session } from '../../types';
4
+
5
+ /**
6
+ * Shared test suite for database adapters
7
+ * Can be used to test any implementation of DatabaseAdapter
8
+ */
9
+ export function createAdapterTestSuite(
10
+ name: string,
11
+ createAdapter: () => DatabaseAdapter
12
+ ) {
13
+ describe(`${name} Adapter Tests`, () => {
14
+ let adapter: DatabaseAdapter;
15
+
16
+ beforeEach(async () => {
17
+ adapter = createAdapter();
18
+ await adapter.initialize();
19
+ });
20
+
21
+ afterEach(async () => {
22
+ await adapter.close();
23
+ });
24
+
25
+ describe('User Operations', () => {
26
+ it('should create a user', async () => {
27
+ const user: User = {
28
+ id: 'user-1',
29
+ email: 'test@example.com',
30
+ passwordHash: 'hashed-password',
31
+ emailVerified: false,
32
+ createdAt: Date.now(),
33
+ updatedAt: Date.now()
34
+ };
35
+
36
+ await adapter.createUser(user);
37
+ const retrieved = await adapter.getUserById('user-1');
38
+
39
+ expect(retrieved).toBeDefined();
40
+ expect(retrieved?.email).toBe('test@example.com');
41
+ });
42
+
43
+ it('should get user by email', async () => {
44
+ const user: User = {
45
+ id: 'user-2',
46
+ email: 'test2@example.com',
47
+ passwordHash: 'hashed-password',
48
+ emailVerified: false,
49
+ createdAt: Date.now(),
50
+ updatedAt: Date.now()
51
+ };
52
+
53
+ await adapter.createUser(user);
54
+ const retrieved = await adapter.getUserByEmail('test2@example.com');
55
+
56
+ expect(retrieved).toBeDefined();
57
+ expect(retrieved?.id).toBe('user-2');
58
+ });
59
+
60
+ it('should return null for non-existent user', async () => {
61
+ const user = await adapter.getUserById('non-existent');
62
+ expect(user).toBeNull();
63
+ });
64
+
65
+ it('should update user', async () => {
66
+ const user: User = {
67
+ id: 'user-3',
68
+ email: 'test3@example.com',
69
+ passwordHash: 'hashed-password',
70
+ emailVerified: false,
71
+ createdAt: Date.now(),
72
+ updatedAt: Date.now()
73
+ };
74
+
75
+ await adapter.createUser(user);
76
+ await adapter.updateUser('user-3', {
77
+ emailVerified: true,
78
+ passwordHash: 'new-hashed-password'
79
+ });
80
+
81
+ const updated = await adapter.getUserById('user-3');
82
+ expect(updated?.emailVerified).toBe(true);
83
+ expect(updated?.passwordHash).toBe('new-hashed-password');
84
+ });
85
+
86
+ it('should get all users', async () => {
87
+ const user1: User = {
88
+ id: 'user-4',
89
+ email: 'test4@example.com',
90
+ passwordHash: 'hashed-password',
91
+ emailVerified: false,
92
+ createdAt: Date.now(),
93
+ updatedAt: Date.now()
94
+ };
95
+
96
+ const user2: User = {
97
+ id: 'user-5',
98
+ email: 'test5@example.com',
99
+ passwordHash: 'hashed-password',
100
+ emailVerified: false,
101
+ createdAt: Date.now(),
102
+ updatedAt: Date.now()
103
+ };
104
+
105
+ await adapter.createUser(user1);
106
+ await adapter.createUser(user2);
107
+
108
+ const users = await adapter.getAllUsers();
109
+ expect(users.length).toBeGreaterThanOrEqual(2);
110
+ });
111
+ });
112
+
113
+ describe('Session Operations', () => {
114
+ it('should create a session', async () => {
115
+ const session: Session = {
116
+ userId: 'user-1',
117
+ email: 'test@example.com',
118
+ createdAt: Date.now(),
119
+ expiresAt: Date.now() + 7 * 24 * 60 * 60 * 1000
120
+ };
121
+
122
+ await adapter.createSession('session-1', session);
123
+ const retrieved = await adapter.getSession('session-1');
124
+
125
+ expect(retrieved).toBeDefined();
126
+ expect(retrieved?.userId).toBe('user-1');
127
+ expect(retrieved?.email).toBe('test@example.com');
128
+ });
129
+
130
+ it('should return null for non-existent session', async () => {
131
+ const session = await adapter.getSession('non-existent');
132
+ expect(session).toBeNull();
133
+ });
134
+
135
+ it('should return null for expired session', async () => {
136
+ const now = Date.now();
137
+ const expiredSession: Session = {
138
+ userId: 'user-1',
139
+ email: 'test@example.com',
140
+ createdAt: now - 1000,
141
+ expiresAt: now - 100 // Expired 100ms ago
142
+ };
143
+
144
+ await adapter.createSession('expired-session', expiredSession);
145
+ const retrieved = await adapter.getSession('expired-session');
146
+
147
+ expect(retrieved).toBeNull();
148
+ });
149
+
150
+ it('should delete session', async () => {
151
+ const session: Session = {
152
+ userId: 'user-1',
153
+ email: 'test@example.com',
154
+ createdAt: Date.now(),
155
+ expiresAt: Date.now() + 7 * 24 * 60 * 60 * 1000
156
+ };
157
+
158
+ await adapter.createSession('session-to-delete', session);
159
+ await adapter.deleteSession('session-to-delete');
160
+
161
+ const retrieved = await adapter.getSession('session-to-delete');
162
+ expect(retrieved).toBeNull();
163
+ });
164
+
165
+ it('should delete expired sessions', async () => {
166
+ const now = Date.now();
167
+
168
+ const expiredSession1: Session = {
169
+ userId: 'user-1',
170
+ email: 'test@example.com',
171
+ createdAt: now - 2000,
172
+ expiresAt: now - 1000
173
+ };
174
+
175
+ const expiredSession2: Session = {
176
+ userId: 'user-2',
177
+ email: 'test2@example.com',
178
+ createdAt: now - 2000,
179
+ expiresAt: now - 500
180
+ };
181
+
182
+ const validSession: Session = {
183
+ userId: 'user-3',
184
+ email: 'test3@example.com',
185
+ createdAt: now,
186
+ expiresAt: now + 7 * 24 * 60 * 60 * 1000
187
+ };
188
+
189
+ await adapter.createSession('expired-1', expiredSession1);
190
+ await adapter.createSession('expired-2', expiredSession2);
191
+ await adapter.createSession('valid', validSession);
192
+
193
+ const deletedCount = await adapter.deleteExpiredSessions();
194
+
195
+ expect(deletedCount).toBeGreaterThanOrEqual(2);
196
+ const valid = await adapter.getSession('valid');
197
+ expect(valid).toBeDefined();
198
+ });
199
+
200
+ it('should get all sessions', async () => {
201
+ const session1: Session = {
202
+ userId: 'user-1',
203
+ email: 'test@example.com',
204
+ createdAt: Date.now(),
205
+ expiresAt: Date.now() + 7 * 24 * 60 * 60 * 1000
206
+ };
207
+
208
+ const session2: Session = {
209
+ userId: 'user-2',
210
+ email: 'test2@example.com',
211
+ createdAt: Date.now(),
212
+ expiresAt: Date.now() + 7 * 24 * 60 * 60 * 1000
213
+ };
214
+
215
+ await adapter.createSession('session-all-1', session1);
216
+ await adapter.createSession('session-all-2', session2);
217
+
218
+ const sessions = await adapter.getAllSessions();
219
+ expect(sessions.length).toBeGreaterThanOrEqual(2);
220
+ });
221
+ });
222
+
223
+ describe('Data Clearing', () => {
224
+ it('should clear all data', async () => {
225
+ const user: User = {
226
+ id: 'user-clear',
227
+ email: 'clear@example.com',
228
+ passwordHash: 'hashed-password',
229
+ emailVerified: false,
230
+ createdAt: Date.now(),
231
+ updatedAt: Date.now()
232
+ };
233
+
234
+ const session: Session = {
235
+ userId: 'user-clear',
236
+ email: 'clear@example.com',
237
+ createdAt: Date.now(),
238
+ expiresAt: Date.now() + 7 * 24 * 60 * 60 * 1000
239
+ };
240
+
241
+ await adapter.createUser(user);
242
+ await adapter.createSession('session-clear', session);
243
+
244
+ await adapter.clear();
245
+
246
+ const users = await adapter.getAllUsers();
247
+ const sessions = await adapter.getAllSessions();
248
+
249
+ expect(users.length).toBe(0);
250
+ expect(sessions.length).toBe(0);
251
+ });
252
+ });
253
+ });
254
+ }
@@ -0,0 +1,48 @@
1
+ import { describe, it } from 'vitest';
2
+ import { FileSystemAdapter } from '../filesystem-adapter';
3
+ import { createAdapterTestSuite } from './adapter-tests';
4
+ import fs from 'fs';
5
+ import path from 'path';
6
+
7
+ // Create a temporary directory for tests
8
+ const testDbPath = path.join(__dirname, 'test-fs-adapter.json');
9
+
10
+ // Clean up after tests
11
+ if (fs.existsSync(testDbPath)) {
12
+ fs.unlinkSync(testDbPath);
13
+ }
14
+
15
+ createAdapterTestSuite('FileSystem', () => new FileSystemAdapter(testDbPath));
16
+
17
+ // Additional FileSystem-specific tests
18
+ describe('FileSystem Adapter - Specific', () => {
19
+ it('should persist data to file', async () => {
20
+ const testPath = path.join(__dirname, 'test-fs-persist.json');
21
+
22
+ // Create and populate adapter
23
+ const adapter = new FileSystemAdapter(testPath);
24
+ await adapter.initialize();
25
+
26
+ const user = {
27
+ id: 'persist-test',
28
+ email: 'persist@example.com',
29
+ passwordHash: 'test-hash',
30
+ emailVerified: false,
31
+ createdAt: Date.now(),
32
+ updatedAt: Date.now()
33
+ };
34
+
35
+ await adapter.createUser(user);
36
+ await adapter.close();
37
+
38
+ // Verify file was created and contains data
39
+ if (fs.existsSync(testPath)) {
40
+ const fileContent = fs.readFileSync(testPath, 'utf-8');
41
+ const data = JSON.parse(fileContent);
42
+ if ('users' in data && data.users) {
43
+ console.log('File persistence verified');
44
+ }
45
+ fs.unlinkSync(testPath);
46
+ }
47
+ });
48
+ });
@@ -0,0 +1,64 @@
1
+ import { describe, it } from 'vitest';
2
+ import { MongoDBAdapter } from '../mongodb-adapter';
3
+ import { createAdapterTestSuite } from './adapter-tests';
4
+
5
+ /**
6
+ * MongoDB Adapter Tests
7
+ *
8
+ * These tests will only run if a MongoDB database is available
9
+ * To run these tests:
10
+ * 1. Start a MongoDB instance (local or MongoDB Atlas)
11
+ * 2. Set environment variable:
12
+ * - MONGODB_URI (default: mongodb://localhost:27017/auth_db)
13
+ * 3. Run: npm test -- --reporter=verbose
14
+ */
15
+
16
+ const isMongoDBAvailable = process.env.TEST_MONGODB === 'true' ||
17
+ process.env.MONGODB_URI !== undefined;
18
+
19
+ if (isMongoDBAvailable) {
20
+ createAdapterTestSuite('MongoDB', () => new MongoDBAdapter());
21
+ } else {
22
+ describe('MongoDB Adapter', () => {
23
+ it('should skip tests if database is not configured', () => {
24
+ console.log('MongoDB tests skipped - set TEST_MONGODB=true or MONGODB_URI to run');
25
+ });
26
+ });
27
+ }
28
+
29
+ // Test connection configuration
30
+ describe('MongoDB Adapter - Configuration', () => {
31
+ it('should accept custom connection URI', () => {
32
+ const adapter = new MongoDBAdapter('mongodb+srv://user:pass@cluster.mongodb.net/auth_db');
33
+ expect(adapter).toBeDefined();
34
+ });
35
+
36
+ it('should read from MONGODB_URI environment variable', () => {
37
+ const originalUri = process.env.MONGODB_URI;
38
+
39
+ process.env.MONGODB_URI = 'mongodb://custom-host:27017/custom_db';
40
+
41
+ const adapter = new MongoDBAdapter();
42
+ expect(adapter).toBeDefined();
43
+
44
+ // Restore original env
45
+ if (originalUri) {
46
+ process.env.MONGODB_URI = originalUri;
47
+ } else {
48
+ delete process.env.MONGODB_URI;
49
+ }
50
+ });
51
+
52
+ it('should default to localhost', () => {
53
+ const originalUri = process.env.MONGODB_URI;
54
+ delete process.env.MONGODB_URI;
55
+
56
+ const adapter = new MongoDBAdapter();
57
+ expect(adapter).toBeDefined();
58
+
59
+ // Restore original env
60
+ if (originalUri) {
61
+ process.env.MONGODB_URI = originalUri;
62
+ }
63
+ });
64
+ });
@@ -0,0 +1,62 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { PostgresAdapter } from '../postgres-adapter';
3
+ import { createAdapterTestSuite } from './adapter-tests';
4
+
5
+ /**
6
+ * PostgreSQL Adapter Tests
7
+ *
8
+ * These tests will only run if a PostgreSQL database is available
9
+ * To run these tests:
10
+ * 1. Start a PostgreSQL database (local or docker)
11
+ * 2. Set environment variables:
12
+ * - DB_HOST (default: localhost)
13
+ * - DB_PORT (default: 5432)
14
+ * - DB_NAME (default: auth_db)
15
+ * - DB_USER (default: postgres)
16
+ * - DB_PASSWORD (default: postgres)
17
+ * 3. Run: npm test
18
+ */
19
+
20
+ const isPostgresAvailable = process.env.TEST_POSTGRES === 'true' ||
21
+ process.env.DB_HOST !== undefined;
22
+
23
+ if (isPostgresAvailable) {
24
+ createAdapterTestSuite('PostgreSQL', () => new PostgresAdapter());
25
+ } else {
26
+ describe('PostgreSQL Adapter', () => {
27
+ it('should skip tests if database is not configured', () => {
28
+ console.log('PostgreSQL tests skipped - set TEST_POSTGRES=true or DB_HOST to run');
29
+ });
30
+ });
31
+ }
32
+
33
+ // Test connection configuration
34
+ describe('PostgreSQL Adapter - Configuration', () => {
35
+ it('should accept custom connection config', () => {
36
+ const adapter = new PostgresAdapter({
37
+ host: 'custom-host',
38
+ port: 5433,
39
+ database: 'custom_db',
40
+ user: 'custom_user',
41
+ password: 'custom_pass'
42
+ });
43
+
44
+ expect(adapter).toBeDefined();
45
+ });
46
+
47
+ it('should read from environment variables', () => {
48
+ const originalEnv = { ...process.env };
49
+
50
+ process.env.DB_HOST = 'env-host';
51
+ process.env.DB_PORT = '5434';
52
+ process.env.DB_NAME = 'env_db';
53
+ process.env.DB_USER = 'env_user';
54
+ process.env.DB_PASSWORD = 'env_pass';
55
+
56
+ const adapter = new PostgresAdapter();
57
+ expect(adapter).toBeDefined();
58
+
59
+ // Restore original env
60
+ process.env = originalEnv;
61
+ });
62
+ });
@@ -0,0 +1,103 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { SqliteAdapter } from '../sqlite-adapter';
3
+ import { createAdapterTestSuite } from './adapter-tests';
4
+ import fs from 'fs';
5
+ import path from 'path';
6
+
7
+ // Create a temporary directory for tests
8
+ const testDbPath = path.join(__dirname, 'test-sqlite-adapter.db');
9
+
10
+ // Clean up before tests
11
+ if (fs.existsSync(testDbPath)) {
12
+ fs.unlinkSync(testDbPath);
13
+ }
14
+
15
+ createAdapterTestSuite('SQLite', () => new SqliteAdapter(testDbPath));
16
+
17
+ // Additional SQLite-specific tests
18
+ describe('SQLite Adapter - Specific', () => {
19
+ it('should create database file', async () => {
20
+ const testPath = path.join(__dirname, 'test-sqlite-create.db');
21
+
22
+ const adapter = new SqliteAdapter(testPath);
23
+ await adapter.initialize();
24
+ await adapter.close();
25
+
26
+ expect(fs.existsSync(testPath)).toBe(true);
27
+
28
+ // Clean up
29
+ if (fs.existsSync(testPath)) {
30
+ fs.unlinkSync(testPath);
31
+ }
32
+ });
33
+
34
+ it('should persist data to database file', async () => {
35
+ const testPath = path.join(__dirname, 'test-sqlite-persist.db');
36
+
37
+ // Create and populate adapter
38
+ const adapter = new SqliteAdapter(testPath);
39
+ await adapter.initialize();
40
+
41
+ const user = {
42
+ id: 'persist-test',
43
+ email: 'persist@example.com',
44
+ passwordHash: 'test-hash',
45
+ emailVerified: true,
46
+ createdAt: Date.now(),
47
+ updatedAt: Date.now()
48
+ };
49
+
50
+ await adapter.createUser(user);
51
+ await adapter.close();
52
+
53
+ // Verify data persists by opening new adapter instance
54
+ const adapter2 = new SqliteAdapter(testPath);
55
+ await adapter2.initialize();
56
+
57
+ const retrievedUser = await adapter2.getUserById('persist-test');
58
+ expect(retrievedUser).toBeDefined();
59
+ expect(retrievedUser?.email).toBe('persist@example.com');
60
+
61
+ await adapter2.close();
62
+
63
+ // Clean up
64
+ if (fs.existsSync(testPath)) {
65
+ fs.unlinkSync(testPath);
66
+ }
67
+ });
68
+
69
+ it('should support database reuse', async () => {
70
+ const testPath = path.join(__dirname, 'test-sqlite-reuse.db');
71
+
72
+ // First adapter session
73
+ const adapter1 = new SqliteAdapter(testPath);
74
+ await adapter1.initialize();
75
+
76
+ const user1 = {
77
+ id: 'user-1',
78
+ email: 'user1@example.com',
79
+ passwordHash: 'hash1',
80
+ emailVerified: false,
81
+ createdAt: Date.now(),
82
+ updatedAt: Date.now()
83
+ };
84
+
85
+ await adapter1.createUser(user1);
86
+ await adapter1.close();
87
+
88
+ // Second adapter session - should see data from first
89
+ const adapter2 = new SqliteAdapter(testPath);
90
+ await adapter2.initialize();
91
+
92
+ const users = await adapter2.getAllUsers();
93
+ expect(users.length).toBeGreaterThan(0);
94
+ expect(users.some((u) => u.id === 'user-1')).toBe(true);
95
+
96
+ await adapter2.close();
97
+
98
+ // Clean up
99
+ if (fs.existsSync(testPath)) {
100
+ fs.unlinkSync(testPath);
101
+ }
102
+ });
103
+ });
@@ -0,0 +1,4 @@
1
+ {
2
+ "users": {},
3
+ "sessions": {}
4
+ }
@@ -0,0 +1,72 @@
1
+ import type { User, Session } from '../types';
2
+
3
+ /**
4
+ * Database adapter interface
5
+ * Defines the contract that all database adapters must implement
6
+ */
7
+ export interface DatabaseAdapter {
8
+ /**
9
+ * Initialize the database connection and schema
10
+ */
11
+ initialize(): Promise<void>;
12
+
13
+ /**
14
+ * Close the database connection
15
+ */
16
+ close(): Promise<void>;
17
+
18
+ /**
19
+ * Create a new user
20
+ */
21
+ createUser(user: User): Promise<void>;
22
+
23
+ /**
24
+ * Get user by ID
25
+ */
26
+ getUserById(id: string): Promise<User | null>;
27
+
28
+ /**
29
+ * Get user by email
30
+ */
31
+ getUserByEmail(email: string): Promise<User | null>;
32
+
33
+ /**
34
+ * Update user
35
+ */
36
+ updateUser(id: string, updates: Partial<User>): Promise<void>;
37
+
38
+ /**
39
+ * Create a new session
40
+ */
41
+ createSession(sessionId: string, session: Session): Promise<void>;
42
+
43
+ /**
44
+ * Get session by ID
45
+ */
46
+ getSession(sessionId: string): Promise<Session | null>;
47
+
48
+ /**
49
+ * Delete session
50
+ */
51
+ deleteSession(sessionId: string): Promise<void>;
52
+
53
+ /**
54
+ * Delete all expired sessions
55
+ */
56
+ deleteExpiredSessions(): Promise<number>;
57
+
58
+ /**
59
+ * Get all users (for testing/admin purposes)
60
+ */
61
+ getAllUsers(): Promise<User[]>;
62
+
63
+ /**
64
+ * Get all sessions (for testing/admin purposes)
65
+ */
66
+ getAllSessions(): Promise<Session[]>;
67
+
68
+ /**
69
+ * Clear all data (for testing)
70
+ */
71
+ clear(): Promise<void>;
72
+ }