@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.
- package/dist/adapter-context.d.ts +19 -0
- package/dist/adapter-context.d.ts.map +1 -0
- package/dist/adapter-context.js +68 -0
- package/dist/adapter-context.js.map +1 -0
- package/dist/adapters/__tests__/adapter-tests.d.ts +7 -0
- package/dist/adapters/__tests__/adapter-tests.d.ts.map +1 -0
- package/dist/adapters/__tests__/adapter-tests.js +206 -0
- package/dist/adapters/__tests__/adapter-tests.js.map +1 -0
- package/dist/adapters/adapter.d.ts +60 -0
- package/dist/adapters/adapter.d.ts.map +1 -0
- package/dist/adapters/adapter.js +2 -0
- package/dist/adapters/adapter.js.map +1 -0
- package/dist/adapters/filesystem-adapter.d.ts +26 -0
- package/dist/adapters/filesystem-adapter.d.ts.map +1 -0
- package/dist/adapters/filesystem-adapter.js +148 -0
- package/dist/adapters/filesystem-adapter.js.map +1 -0
- package/dist/adapters/index.d.ts +6 -0
- package/dist/adapters/index.d.ts.map +1 -0
- package/dist/adapters/index.js +5 -0
- package/dist/adapters/index.js.map +1 -0
- package/dist/adapters/mongodb-adapter.d.ts +27 -0
- package/dist/adapters/mongodb-adapter.d.ts.map +1 -0
- package/dist/adapters/mongodb-adapter.js +213 -0
- package/dist/adapters/mongodb-adapter.js.map +1 -0
- package/dist/adapters/postgres-adapter.d.ts +30 -0
- package/dist/adapters/postgres-adapter.d.ts.map +1 -0
- package/dist/adapters/postgres-adapter.js +237 -0
- package/dist/adapters/postgres-adapter.js.map +1 -0
- package/dist/adapters/sqlite-adapter.d.ts +26 -0
- package/dist/adapters/sqlite-adapter.d.ts.map +1 -0
- package/dist/adapters/sqlite-adapter.js +261 -0
- package/dist/adapters/sqlite-adapter.js.map +1 -0
- package/dist/auth.d.ts +48 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +205 -0
- package/dist/auth.js.map +1 -0
- package/dist/client-jwt.d.ts +30 -0
- package/dist/client-jwt.d.ts.map +1 -0
- package/dist/client-jwt.js +57 -0
- package/dist/client-jwt.js.map +1 -0
- package/dist/client-store.d.ts +31 -0
- package/dist/client-store.d.ts.map +1 -0
- package/dist/client-store.js +122 -0
- package/dist/client-store.js.map +1 -0
- package/dist/cors.d.ts +48 -0
- package/dist/cors.d.ts.map +1 -0
- package/dist/cors.js +88 -0
- package/dist/cors.js.map +1 -0
- package/dist/csrf.d.ts +57 -0
- package/dist/csrf.d.ts.map +1 -0
- package/dist/csrf.js +95 -0
- package/dist/csrf.js.map +1 -0
- package/dist/db.d.ts +22 -0
- package/dist/db.d.ts.map +1 -0
- package/dist/db.js +43 -0
- package/dist/db.js.map +1 -0
- package/dist/index.d.ts +35 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +36 -0
- package/dist/index.js.map +1 -0
- package/dist/input-validation.d.ts +78 -0
- package/dist/input-validation.d.ts.map +1 -0
- package/dist/input-validation.js +238 -0
- package/dist/input-validation.js.map +1 -0
- package/dist/oauth-callback.d.ts +31 -0
- package/dist/oauth-callback.d.ts.map +1 -0
- package/dist/oauth-callback.js +254 -0
- package/dist/oauth-callback.js.map +1 -0
- package/dist/oauth-providers.d.ts +92 -0
- package/dist/oauth-providers.d.ts.map +1 -0
- package/dist/oauth-providers.js +213 -0
- package/dist/oauth-providers.js.map +1 -0
- package/dist/oauth-types.d.ts +77 -0
- package/dist/oauth-types.d.ts.map +1 -0
- package/dist/oauth-types.js +2 -0
- package/dist/oauth-types.js.map +1 -0
- package/dist/password.d.ts +31 -0
- package/dist/password.d.ts.map +1 -0
- package/dist/password.js +54 -0
- package/dist/password.js.map +1 -0
- package/dist/providers/github-oauth.d.ts +58 -0
- package/dist/providers/github-oauth.d.ts.map +1 -0
- package/dist/providers/github-oauth.js +230 -0
- package/dist/providers/github-oauth.js.map +1 -0
- package/dist/providers/google-oauth.d.ts +46 -0
- package/dist/providers/google-oauth.d.ts.map +1 -0
- package/dist/providers/google-oauth.js +177 -0
- package/dist/providers/google-oauth.js.map +1 -0
- package/dist/providers/oidc-oauth.d.ts +85 -0
- package/dist/providers/oidc-oauth.d.ts.map +1 -0
- package/dist/providers/oidc-oauth.js +301 -0
- package/dist/providers/oidc-oauth.js.map +1 -0
- package/dist/rate-limit.d.ts +36 -0
- package/dist/rate-limit.d.ts.map +1 -0
- package/dist/rate-limit.js +88 -0
- package/dist/rate-limit.js.map +1 -0
- package/dist/rate-limiting.d.ts +113 -0
- package/dist/rate-limiting.d.ts.map +1 -0
- package/dist/rate-limiting.js +221 -0
- package/dist/rate-limiting.js.map +1 -0
- package/dist/security-headers.d.ts +54 -0
- package/dist/security-headers.d.ts.map +1 -0
- package/dist/security-headers.js +123 -0
- package/dist/security-headers.js.map +1 -0
- package/dist/session.d.ts +13 -0
- package/dist/session.d.ts.map +1 -0
- package/dist/session.js +33 -0
- package/dist/session.js.map +1 -0
- package/dist/sql-injection-prevention.d.ts +94 -0
- package/dist/sql-injection-prevention.d.ts.map +1 -0
- package/dist/sql-injection-prevention.js +222 -0
- package/dist/sql-injection-prevention.js.map +1 -0
- package/dist/token.d.ts +22 -0
- package/dist/token.d.ts.map +1 -0
- package/dist/token.js +31 -0
- package/dist/token.js.map +1 -0
- package/dist/types.d.ts +81 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/user.d.ts +33 -0
- package/dist/user.d.ts.map +1 -0
- package/dist/user.js +144 -0
- package/dist/user.js.map +1 -0
- package/package.json +48 -0
- package/src/adapter-context.ts +72 -0
- package/src/adapters/__tests__/adapter-tests.ts +254 -0
- package/src/adapters/__tests__/filesystem-adapter.test.ts +48 -0
- package/src/adapters/__tests__/mongodb-adapter.test.ts +64 -0
- package/src/adapters/__tests__/postgres-adapter.test.ts +62 -0
- package/src/adapters/__tests__/sqlite-adapter.test.ts +103 -0
- package/src/adapters/__tests__/test-fs-adapter.json +4 -0
- package/src/adapters/adapter.ts +72 -0
- package/src/adapters/filesystem-adapter.ts +153 -0
- package/src/adapters/index.ts +5 -0
- package/src/adapters/mongodb-adapter.ts +208 -0
- package/src/adapters/postgres-adapter.ts +261 -0
- package/src/adapters/sqlite-adapter.ts +284 -0
- package/src/auth.ts +239 -0
- package/src/client-jwt.test.ts +137 -0
- package/src/client-jwt.ts +67 -0
- package/src/client-store.test.ts +149 -0
- package/src/client-store.ts +144 -0
- package/src/cors.test.ts +175 -0
- package/src/cors.ts +115 -0
- package/src/csrf.test.ts +226 -0
- package/src/csrf.ts +126 -0
- package/src/db.ts +57 -0
- package/src/index.ts +143 -0
- package/src/input-validation.test.ts +347 -0
- package/src/input-validation.ts +307 -0
- package/src/integration.test.ts +322 -0
- package/src/oauth-callback.test.ts +282 -0
- package/src/oauth-callback.ts +323 -0
- package/src/oauth-providers.ts +232 -0
- package/src/oauth-types.ts +82 -0
- package/src/password.test.ts +89 -0
- package/src/password.ts +62 -0
- package/src/providers/github-oauth.test.ts +290 -0
- package/src/providers/github-oauth.ts +226 -0
- package/src/providers/google-oauth.test.ts +240 -0
- package/src/providers/google-oauth.ts +166 -0
- package/src/providers/oidc-oauth.test.ts +367 -0
- package/src/providers/oidc-oauth.ts +302 -0
- package/src/rate-limit.test.ts +308 -0
- package/src/rate-limit.ts +118 -0
- package/src/rate-limiting.test.ts +390 -0
- package/src/rate-limiting.ts +275 -0
- package/src/security-headers.test.ts +242 -0
- package/src/security-headers.ts +160 -0
- package/src/security-penetration.test.ts +705 -0
- package/src/session.ts +42 -0
- package/src/sql-injection-prevention.test.ts +337 -0
- package/src/sql-injection-prevention.ts +272 -0
- package/src/token.test.ts +67 -0
- package/src/token.ts +34 -0
- package/src/types.ts +87 -0
- 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,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
|
+
}
|