@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,153 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import type { User, Session } from '../types';
4
+ import type { DatabaseAdapter } from './adapter';
5
+
6
+ interface DatabaseStore {
7
+ users: Record<string, User>;
8
+ sessions: Record<string, Session>;
9
+ }
10
+
11
+ /**
12
+ * File system adapter - JSON file-based storage
13
+ * Useful for development and testing
14
+ */
15
+ export class FileSystemAdapter implements DatabaseAdapter {
16
+ private dbPath: string;
17
+ private store: DatabaseStore | null = null;
18
+
19
+ constructor(dbPath?: string) {
20
+ this.dbPath = dbPath || path.join(process.cwd(), 'auth.json');
21
+ }
22
+
23
+ async initialize(): Promise<void> {
24
+ try {
25
+ if (fs.existsSync(this.dbPath)) {
26
+ const data = fs.readFileSync(this.dbPath, 'utf-8');
27
+ this.store = JSON.parse(data) as DatabaseStore;
28
+ } else {
29
+ this.store = {
30
+ users: {},
31
+ sessions: {}
32
+ };
33
+ this.save();
34
+ }
35
+ } catch (error) {
36
+ console.error('Failed to initialize filesystem adapter:', error);
37
+ this.store = {
38
+ users: {},
39
+ sessions: {}
40
+ };
41
+ }
42
+ }
43
+
44
+ async close(): Promise<void> {
45
+ if (this.store) {
46
+ this.save();
47
+ this.store = null;
48
+ }
49
+ }
50
+
51
+ async createUser(user: User): Promise<void> {
52
+ if (!this.store) throw new Error('Database not initialized');
53
+ this.store.users[user.id] = user;
54
+ this.save();
55
+ }
56
+
57
+ async getUserById(id: string): Promise<User | null> {
58
+ if (!this.store) throw new Error('Database not initialized');
59
+ return this.store.users[id] || null;
60
+ }
61
+
62
+ async getUserByEmail(email: string): Promise<User | null> {
63
+ if (!this.store) throw new Error('Database not initialized');
64
+ return (
65
+ Object.values(this.store.users).find((u) => u.email === email) || null
66
+ );
67
+ }
68
+
69
+ async updateUser(id: string, updates: Partial<User>): Promise<void> {
70
+ if (!this.store) throw new Error('Database not initialized');
71
+ const user = this.store.users[id];
72
+ if (!user) throw new Error('User not found');
73
+
74
+ this.store.users[id] = { ...user, ...updates };
75
+ this.save();
76
+ }
77
+
78
+ async createSession(sessionId: string, session: Session): Promise<void> {
79
+ if (!this.store) throw new Error('Database not initialized');
80
+ this.store.sessions[sessionId] = session;
81
+ this.save();
82
+ }
83
+
84
+ async getSession(sessionId: string): Promise<Session | null> {
85
+ if (!this.store) throw new Error('Database not initialized');
86
+ const session = this.store.sessions[sessionId];
87
+
88
+ if (!session || session.expiresAt < Date.now()) {
89
+ return null;
90
+ }
91
+
92
+ return session;
93
+ }
94
+
95
+ async deleteSession(sessionId: string): Promise<void> {
96
+ if (!this.store) throw new Error('Database not initialized');
97
+ delete this.store.sessions[sessionId];
98
+ this.save();
99
+ }
100
+
101
+ async deleteExpiredSessions(): Promise<number> {
102
+ if (!this.store) throw new Error('Database not initialized');
103
+ const now = Date.now();
104
+ let count = 0;
105
+
106
+ for (const [sessionId, session] of Object.entries(this.store.sessions)) {
107
+ if (session.expiresAt < now) {
108
+ delete this.store.sessions[sessionId];
109
+ count++;
110
+ }
111
+ }
112
+
113
+ if (count > 0) {
114
+ this.save();
115
+ }
116
+
117
+ return count;
118
+ }
119
+
120
+ async getAllUsers(): Promise<User[]> {
121
+ if (!this.store) throw new Error('Database not initialized');
122
+ return Object.values(this.store.users);
123
+ }
124
+
125
+ async getAllSessions(): Promise<Session[]> {
126
+ if (!this.store) throw new Error('Database not initialized');
127
+ return Object.values(this.store.sessions);
128
+ }
129
+
130
+ async clear(): Promise<void> {
131
+ if (!this.store) throw new Error('Database not initialized');
132
+ this.store = {
133
+ users: {},
134
+ sessions: {}
135
+ };
136
+ this.save();
137
+ }
138
+
139
+ private save(): void {
140
+ if (this.store && this.dbPath) {
141
+ try {
142
+ const dir = path.dirname(this.dbPath);
143
+ if (!fs.existsSync(dir)) {
144
+ fs.mkdirSync(dir, { recursive: true });
145
+ }
146
+ fs.writeFileSync(this.dbPath, JSON.stringify(this.store, null, 2), 'utf-8');
147
+ } catch (error) {
148
+ console.error('Failed to save database:', error);
149
+ throw error;
150
+ }
151
+ }
152
+ }
153
+ }
@@ -0,0 +1,5 @@
1
+ export type { DatabaseAdapter } from './adapter';
2
+ export { FileSystemAdapter } from './filesystem-adapter';
3
+ export { PostgresAdapter } from './postgres-adapter';
4
+ export { MongoDBAdapter } from './mongodb-adapter';
5
+ export { SqliteAdapter } from './sqlite-adapter';
@@ -0,0 +1,208 @@
1
+ import { MongoClient, type Db, type Collection } from 'mongodb';
2
+ import type { User, Session } from '../types';
3
+ import type { DatabaseAdapter } from './adapter';
4
+
5
+ /**
6
+ * MongoDB adapter
7
+ * Requires a MongoDB instance (local or MongoDB Atlas)
8
+ */
9
+ export class MongoDBAdapter implements DatabaseAdapter {
10
+ private client: MongoClient;
11
+ private db: Db | null = null;
12
+ private usersCollection: Collection<User> | null = null;
13
+ private sessionsCollection: Collection<Session> | null = null;
14
+
15
+ constructor(connectionUri?: string) {
16
+ const uri = connectionUri || process.env.MONGODB_URI || 'mongodb://localhost:27017/auth_db';
17
+ this.client = new MongoClient(uri);
18
+ }
19
+
20
+ async initialize(): Promise<void> {
21
+ try {
22
+ await this.client.connect();
23
+ this.db = this.client.db('auth_db');
24
+
25
+ // Create collections if they don't exist
26
+ const collections = await this.db.listCollections().toArray();
27
+ const collectionNames = collections.map((c: any) => c.name);
28
+
29
+ if (!collectionNames.includes('users')) {
30
+ await this.db.createCollection('users');
31
+ }
32
+
33
+ if (!collectionNames.includes('sessions')) {
34
+ await this.db.createCollection('sessions');
35
+ }
36
+
37
+ this.usersCollection = this.db.collection('users');
38
+ this.sessionsCollection = this.db.collection('sessions');
39
+
40
+ // Create indexes
41
+ await this.usersCollection.createIndex({ email: 1 }, { unique: true });
42
+ await this.usersCollection.createIndex({ verificationToken: 1 });
43
+ await this.usersCollection.createIndex({ resetToken: 1 });
44
+
45
+ await this.sessionsCollection.createIndex({ userId: 1 });
46
+ await this.sessionsCollection.createIndex({ expiresAt: 1 });
47
+ } catch (error) {
48
+ console.error('Failed to initialize MongoDB adapter:', error);
49
+ throw error;
50
+ }
51
+ }
52
+
53
+ async close(): Promise<void> {
54
+ try {
55
+ await this.client.close();
56
+ this.db = null;
57
+ this.usersCollection = null;
58
+ this.sessionsCollection = null;
59
+ } catch (error) {
60
+ console.error('Failed to close MongoDB adapter:', error);
61
+ throw error;
62
+ }
63
+ }
64
+
65
+ async createUser(user: User): Promise<void> {
66
+ if (!this.usersCollection) throw new Error('Database not initialized');
67
+
68
+ try {
69
+ await this.usersCollection.insertOne(user as any);
70
+ } catch (error) {
71
+ console.error('Failed to create user:', error);
72
+ throw error;
73
+ }
74
+ }
75
+
76
+ async getUserById(id: string): Promise<User | null> {
77
+ if (!this.usersCollection) throw new Error('Database not initialized');
78
+
79
+ try {
80
+ const user = await this.usersCollection.findOne({ _id: id } as any);
81
+ return user as unknown as User | null;
82
+ } catch (error) {
83
+ console.error('Failed to get user by ID:', error);
84
+ throw error;
85
+ }
86
+ }
87
+
88
+ async getUserByEmail(email: string): Promise<User | null> {
89
+ if (!this.usersCollection) throw new Error('Database not initialized');
90
+
91
+ try {
92
+ const user = await this.usersCollection.findOne({ email } as any);
93
+ return user as unknown as User | null;
94
+ } catch (error) {
95
+ console.error('Failed to get user by email:', error);
96
+ throw error;
97
+ }
98
+ }
99
+
100
+ async updateUser(id: string, updates: Partial<User>): Promise<void> {
101
+ if (!this.usersCollection) throw new Error('Database not initialized');
102
+
103
+ try {
104
+ const result = await this.usersCollection.updateOne(
105
+ { _id: id } as any,
106
+ { $set: updates as any }
107
+ );
108
+
109
+ if (result.matchedCount === 0) {
110
+ throw new Error('User not found');
111
+ }
112
+ } catch (error) {
113
+ console.error('Failed to update user:', error);
114
+ throw error;
115
+ }
116
+ }
117
+
118
+ async createSession(sessionId: string, session: Session): Promise<void> {
119
+ if (!this.sessionsCollection) throw new Error('Database not initialized');
120
+
121
+ try {
122
+ await this.sessionsCollection.insertOne({ _id: sessionId, ...session } as any);
123
+ } catch (error) {
124
+ console.error('Failed to create session:', error);
125
+ throw error;
126
+ }
127
+ }
128
+
129
+ async getSession(sessionId: string): Promise<Session | null> {
130
+ if (!this.sessionsCollection) throw new Error('Database not initialized');
131
+
132
+ try {
133
+ const session = await this.sessionsCollection.findOne({ _id: sessionId } as any);
134
+
135
+ if (!session || session.expiresAt < Date.now()) {
136
+ return null;
137
+ }
138
+
139
+ return session as unknown as Session;
140
+ } catch (error) {
141
+ console.error('Failed to get session:', error);
142
+ throw error;
143
+ }
144
+ }
145
+
146
+ async deleteSession(sessionId: string): Promise<void> {
147
+ if (!this.sessionsCollection) throw new Error('Database not initialized');
148
+
149
+ try {
150
+ await this.sessionsCollection.deleteOne({ _id: sessionId } as any);
151
+ } catch (error) {
152
+ console.error('Failed to delete session:', error);
153
+ throw error;
154
+ }
155
+ }
156
+
157
+ async deleteExpiredSessions(): Promise<number> {
158
+ if (!this.sessionsCollection) throw new Error('Database not initialized');
159
+
160
+ try {
161
+ const result = await this.sessionsCollection.deleteMany({
162
+ expiresAt: { $lt: Date.now() }
163
+ } as any);
164
+ return result.deletedCount || 0;
165
+ } catch (error) {
166
+ console.error('Failed to delete expired sessions:', error);
167
+ throw error;
168
+ }
169
+ }
170
+
171
+ async getAllUsers(): Promise<User[]> {
172
+ if (!this.usersCollection) throw new Error('Database not initialized');
173
+
174
+ try {
175
+ const users = await this.usersCollection.find({} as any).toArray();
176
+ return users as unknown as User[];
177
+ } catch (error) {
178
+ console.error('Failed to get all users:', error);
179
+ throw error;
180
+ }
181
+ }
182
+
183
+ async getAllSessions(): Promise<Session[]> {
184
+ if (!this.sessionsCollection) throw new Error('Database not initialized');
185
+
186
+ try {
187
+ const sessions = await this.sessionsCollection.find({} as any).toArray();
188
+ return sessions as unknown as Session[];
189
+ } catch (error) {
190
+ console.error('Failed to get all sessions:', error);
191
+ throw error;
192
+ }
193
+ }
194
+
195
+ async clear(): Promise<void> {
196
+ try {
197
+ if (this.usersCollection) {
198
+ await this.usersCollection.deleteMany({} as any);
199
+ }
200
+ if (this.sessionsCollection) {
201
+ await this.sessionsCollection.deleteMany({} as any);
202
+ }
203
+ } catch (error) {
204
+ console.error('Failed to clear database:', error);
205
+ throw error;
206
+ }
207
+ }
208
+ }
@@ -0,0 +1,261 @@
1
+ import { Pool } from 'pg';
2
+ import type { User, Session } from '../types';
3
+ import type { DatabaseAdapter } from './adapter';
4
+
5
+ /**
6
+ * PostgreSQL adapter using pg library
7
+ * Requires a PostgreSQL database to be set up
8
+ */
9
+ export class PostgresAdapter implements DatabaseAdapter {
10
+ private pool: Pool;
11
+
12
+ constructor(connectionConfig?: {
13
+ host?: string;
14
+ port?: number;
15
+ database?: string;
16
+ user?: string;
17
+ password?: string;
18
+ }) {
19
+ const config = {
20
+ host: connectionConfig?.host || process.env.DB_HOST || 'localhost',
21
+ port: connectionConfig?.port || parseInt(process.env.DB_PORT || '5432'),
22
+ database: connectionConfig?.database || process.env.DB_NAME || 'auth_db',
23
+ user: connectionConfig?.user || process.env.DB_USER || 'postgres',
24
+ password: connectionConfig?.password || process.env.DB_PASSWORD || 'postgres'
25
+ };
26
+
27
+ this.pool = new Pool(config);
28
+ }
29
+
30
+ async initialize(): Promise<void> {
31
+ try {
32
+ const client = await this.pool.connect();
33
+ try {
34
+ // Create users table
35
+ await client.query(`
36
+ CREATE TABLE IF NOT EXISTS users (
37
+ id VARCHAR(255) PRIMARY KEY,
38
+ email VARCHAR(255) UNIQUE NOT NULL,
39
+ "passwordHash" VARCHAR(255) NOT NULL,
40
+ "emailVerified" BOOLEAN DEFAULT FALSE,
41
+ "verificationToken" VARCHAR(255),
42
+ "verificationTokenExpires" BIGINT,
43
+ "resetToken" VARCHAR(255),
44
+ "resetTokenExpires" BIGINT,
45
+ "createdAt" BIGINT NOT NULL,
46
+ "updatedAt" BIGINT NOT NULL,
47
+ CONSTRAINT unique_email UNIQUE (email)
48
+ )
49
+ `);
50
+
51
+ // Create sessions table
52
+ await client.query(`
53
+ CREATE TABLE IF NOT EXISTS sessions (
54
+ id VARCHAR(255) PRIMARY KEY,
55
+ "userId" VARCHAR(255) NOT NULL,
56
+ email VARCHAR(255) NOT NULL,
57
+ "createdAt" BIGINT NOT NULL,
58
+ "expiresAt" BIGINT NOT NULL,
59
+ FOREIGN KEY ("userId") REFERENCES users(id) ON DELETE CASCADE
60
+ )
61
+ `);
62
+
63
+ // Create indexes for performance
64
+ await client.query(`
65
+ CREATE INDEX IF NOT EXISTS idx_users_email ON users(email)
66
+ `);
67
+
68
+ await client.query(`
69
+ CREATE INDEX IF NOT EXISTS idx_sessions_expires ON sessions("expiresAt")
70
+ `);
71
+
72
+ } finally {
73
+ client.release();
74
+ }
75
+ } catch (error) {
76
+ console.error('Failed to initialize PostgreSQL adapter:', error);
77
+ throw error;
78
+ }
79
+ }
80
+
81
+ async close(): Promise<void> {
82
+ try {
83
+ await this.pool.end();
84
+ } catch (error) {
85
+ console.error('Failed to close PostgreSQL adapter:', error);
86
+ throw error;
87
+ }
88
+ }
89
+
90
+ async createUser(user: User): Promise<void> {
91
+ const query = `
92
+ INSERT INTO users (
93
+ id, email, "passwordHash", "emailVerified",
94
+ "verificationToken", "verificationTokenExpires",
95
+ "createdAt", "updatedAt"
96
+ ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
97
+ `;
98
+
99
+ try {
100
+ await this.pool.query(query, [
101
+ user.id,
102
+ user.email,
103
+ user.passwordHash,
104
+ user.emailVerified,
105
+ user.verificationToken,
106
+ user.verificationTokenExpires,
107
+ user.createdAt,
108
+ user.updatedAt
109
+ ]);
110
+ } catch (error) {
111
+ console.error('Failed to create user:', error);
112
+ throw error;
113
+ }
114
+ }
115
+
116
+ async getUserById(id: string): Promise<User | null> {
117
+ const query = 'SELECT * FROM users WHERE id = $1';
118
+
119
+ try {
120
+ const result = await this.pool.query(query, [id]);
121
+ return result.rows[0] || null;
122
+ } catch (error) {
123
+ console.error('Failed to get user by ID:', error);
124
+ throw error;
125
+ }
126
+ }
127
+
128
+ async getUserByEmail(email: string): Promise<User | null> {
129
+ const query = 'SELECT * FROM users WHERE email = $1';
130
+
131
+ try {
132
+ const result = await this.pool.query(query, [email]);
133
+ return result.rows[0] || null;
134
+ } catch (error) {
135
+ console.error('Failed to get user by email:', error);
136
+ throw error;
137
+ }
138
+ }
139
+
140
+ async updateUser(id: string, updates: Partial<User>): Promise<void> {
141
+ const updateEntries = Object.entries(updates).filter(
142
+ ([_, value]) => value !== undefined
143
+ );
144
+
145
+ if (updateEntries.length === 0) {
146
+ return;
147
+ }
148
+
149
+ const setClause = updateEntries
150
+ .map(([key], index) => `"${key}" = $${index + 1}`)
151
+ .join(', ');
152
+
153
+ const values = [...updateEntries.map(([_, value]) => value), id];
154
+ const query = `UPDATE users SET ${setClause} WHERE id = $${values.length}`;
155
+
156
+ try {
157
+ await this.pool.query(query, values);
158
+ } catch (error) {
159
+ console.error('Failed to update user:', error);
160
+ throw error;
161
+ }
162
+ }
163
+
164
+ async createSession(sessionId: string, session: Session): Promise<void> {
165
+ const query = `
166
+ INSERT INTO sessions (id, "userId", email, "createdAt", "expiresAt")
167
+ VALUES ($1, $2, $3, $4, $5)
168
+ `;
169
+
170
+ try {
171
+ await this.pool.query(query, [
172
+ sessionId,
173
+ session.userId,
174
+ session.email,
175
+ session.createdAt,
176
+ session.expiresAt
177
+ ]);
178
+ } catch (error) {
179
+ console.error('Failed to create session:', error);
180
+ throw error;
181
+ }
182
+ }
183
+
184
+ async getSession(sessionId: string): Promise<Session | null> {
185
+ const query = 'SELECT * FROM sessions WHERE id = $1';
186
+
187
+ try {
188
+ const result = await this.pool.query(query, [sessionId]);
189
+ const session = result.rows[0];
190
+
191
+ if (!session || session.expiresAt < Date.now()) {
192
+ return null;
193
+ }
194
+
195
+ return session;
196
+ } catch (error) {
197
+ console.error('Failed to get session:', error);
198
+ throw error;
199
+ }
200
+ }
201
+
202
+ async deleteSession(sessionId: string): Promise<void> {
203
+ const query = 'DELETE FROM sessions WHERE id = $1';
204
+
205
+ try {
206
+ await this.pool.query(query, [sessionId]);
207
+ } catch (error) {
208
+ console.error('Failed to delete session:', error);
209
+ throw error;
210
+ }
211
+ }
212
+
213
+ async deleteExpiredSessions(): Promise<number> {
214
+ const query = 'DELETE FROM sessions WHERE "expiresAt" < $1';
215
+
216
+ try {
217
+ const result = await this.pool.query(query, [Date.now()]);
218
+ return result.rowCount || 0;
219
+ } catch (error) {
220
+ console.error('Failed to delete expired sessions:', error);
221
+ throw error;
222
+ }
223
+ }
224
+
225
+ async getAllUsers(): Promise<User[]> {
226
+ const query = 'SELECT * FROM users';
227
+
228
+ try {
229
+ const result = await this.pool.query(query);
230
+ return result.rows;
231
+ } catch (error) {
232
+ console.error('Failed to get all users:', error);
233
+ throw error;
234
+ }
235
+ }
236
+
237
+ async getAllSessions(): Promise<Session[]> {
238
+ const query = 'SELECT * FROM sessions';
239
+
240
+ try {
241
+ const result = await this.pool.query(query);
242
+ return result.rows;
243
+ } catch (error) {
244
+ console.error('Failed to get all sessions:', error);
245
+ throw error;
246
+ }
247
+ }
248
+
249
+ async clear(): Promise<void> {
250
+ const client = await this.pool.connect();
251
+ try {
252
+ await client.query('TRUNCATE sessions CASCADE');
253
+ await client.query('TRUNCATE users CASCADE');
254
+ } catch (error) {
255
+ console.error('Failed to clear database:', error);
256
+ throw error;
257
+ } finally {
258
+ client.release();
259
+ }
260
+ }
261
+ }