@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
package/src/csrf.test.ts
ADDED
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
generateCsrfToken,
|
|
4
|
+
createCsrfToken,
|
|
5
|
+
validateCsrfToken,
|
|
6
|
+
revokeCsrfToken,
|
|
7
|
+
cleanupExpiredCsrfTokens,
|
|
8
|
+
getCsrfTokenStats,
|
|
9
|
+
csrfTokenStore
|
|
10
|
+
} from './csrf';
|
|
11
|
+
|
|
12
|
+
describe('CSRF Token Utilities', () => {
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
// Clear token store before each test
|
|
15
|
+
csrfTokenStore.tokens.clear();
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
describe('generateCsrfToken', () => {
|
|
19
|
+
it('should generate a random token', () => {
|
|
20
|
+
const token1 = generateCsrfToken();
|
|
21
|
+
const token2 = generateCsrfToken();
|
|
22
|
+
|
|
23
|
+
expect(token1).toBeTruthy();
|
|
24
|
+
expect(token2).toBeTruthy();
|
|
25
|
+
expect(token1).not.toBe(token2);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('should generate tokens in base64url format', () => {
|
|
29
|
+
const token = generateCsrfToken();
|
|
30
|
+
// Base64url contains A-Z, a-z, 0-9, -, _
|
|
31
|
+
expect(token).toMatch(/^[A-Za-z0-9_-]+$/);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('should generate tokens of consistent length', () => {
|
|
35
|
+
const tokens = Array.from({ length: 10 }, () => generateCsrfToken());
|
|
36
|
+
const lengths = tokens.map((t) => t.length);
|
|
37
|
+
const firstLength = lengths[0];
|
|
38
|
+
|
|
39
|
+
expect(lengths.every((l) => l === firstLength)).toBe(true);
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
describe('createCsrfToken', () => {
|
|
44
|
+
it('should create and store a token for a session', () => {
|
|
45
|
+
const sessionId = 'session-123';
|
|
46
|
+
const token = createCsrfToken(sessionId);
|
|
47
|
+
|
|
48
|
+
expect(token).toBeTruthy();
|
|
49
|
+
expect(csrfTokenStore.tokens.has(sessionId)).toBe(true);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('should set expiry time correctly', () => {
|
|
53
|
+
const sessionId = 'session-456';
|
|
54
|
+
const beforeCreation = Date.now();
|
|
55
|
+
createCsrfToken(sessionId, 30); // 30 minutes
|
|
56
|
+
const afterCreation = Date.now();
|
|
57
|
+
|
|
58
|
+
const stored = csrfTokenStore.tokens.get(sessionId);
|
|
59
|
+
expect(stored).toBeTruthy();
|
|
60
|
+
expect(stored!.expiresAt).toBeGreaterThanOrEqual(beforeCreation + 30 * 60 * 1000);
|
|
61
|
+
expect(stored!.expiresAt).toBeLessThanOrEqual(afterCreation + 30 * 60 * 1000);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('should use default expiry of 60 minutes', () => {
|
|
65
|
+
const sessionId = 'session-789';
|
|
66
|
+
const beforeCreation = Date.now();
|
|
67
|
+
const token = createCsrfToken(sessionId);
|
|
68
|
+
const afterCreation = Date.now();
|
|
69
|
+
|
|
70
|
+
const stored = csrfTokenStore.tokens.get(sessionId);
|
|
71
|
+
expect(stored!.expiresAt).toBeGreaterThanOrEqual(beforeCreation + 60 * 60 * 1000);
|
|
72
|
+
expect(stored!.expiresAt).toBeLessThanOrEqual(afterCreation + 60 * 60 * 1000);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('should store creation timestamp', () => {
|
|
76
|
+
const sessionId = 'session-abc';
|
|
77
|
+
const beforeCreation = Date.now();
|
|
78
|
+
createCsrfToken(sessionId);
|
|
79
|
+
const afterCreation = Date.now();
|
|
80
|
+
|
|
81
|
+
const stored = csrfTokenStore.tokens.get(sessionId);
|
|
82
|
+
expect(stored!.createdAt).toBeGreaterThanOrEqual(beforeCreation);
|
|
83
|
+
expect(stored!.createdAt).toBeLessThanOrEqual(afterCreation);
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
describe('validateCsrfToken', () => {
|
|
88
|
+
it('should validate a correct token', () => {
|
|
89
|
+
const sessionId = 'session-valid';
|
|
90
|
+
const token = createCsrfToken(sessionId);
|
|
91
|
+
|
|
92
|
+
const isValid = validateCsrfToken(sessionId, token);
|
|
93
|
+
expect(isValid).toBe(true);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('should reject an incorrect token', () => {
|
|
97
|
+
const sessionId = 'session-invalid';
|
|
98
|
+
createCsrfToken(sessionId);
|
|
99
|
+
|
|
100
|
+
const isValid = validateCsrfToken(sessionId, 'wrong-token');
|
|
101
|
+
expect(isValid).toBe(false);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('should reject token for non-existent session', () => {
|
|
105
|
+
const isValid = validateCsrfToken('non-existent-session', 'some-token');
|
|
106
|
+
expect(isValid).toBe(false);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('should reject expired tokens', async () => {
|
|
110
|
+
const sessionId = 'session-expired';
|
|
111
|
+
// Create token that expires in 1 millisecond
|
|
112
|
+
const token = createCsrfToken(sessionId, 0.00001);
|
|
113
|
+
|
|
114
|
+
// Wait a bit to ensure expiry
|
|
115
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
116
|
+
|
|
117
|
+
const isValid = validateCsrfToken(sessionId, token);
|
|
118
|
+
expect(isValid).toBe(false);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('should clean up expired token from store', async () => {
|
|
122
|
+
const sessionId = 'session-cleanup';
|
|
123
|
+
createCsrfToken(sessionId, 0.00001);
|
|
124
|
+
|
|
125
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
126
|
+
|
|
127
|
+
validateCsrfToken(sessionId, generateCsrfToken());
|
|
128
|
+
|
|
129
|
+
// Token should be deleted from store
|
|
130
|
+
expect(csrfTokenStore.tokens.has(sessionId)).toBe(false);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it('should perform timing-safe comparison', () => {
|
|
134
|
+
const sessionId = 'session-timing';
|
|
135
|
+
const token = createCsrfToken(sessionId);
|
|
136
|
+
|
|
137
|
+
// Test with same length but different token
|
|
138
|
+
const invalidToken = token.split('').reverse().join('');
|
|
139
|
+
const isValid = validateCsrfToken(sessionId, invalidToken);
|
|
140
|
+
|
|
141
|
+
expect(isValid).toBe(false);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it('should reject tokens of different length', () => {
|
|
145
|
+
const sessionId = 'session-length';
|
|
146
|
+
createCsrfToken(sessionId);
|
|
147
|
+
|
|
148
|
+
const isValid = validateCsrfToken(sessionId, 'short');
|
|
149
|
+
expect(isValid).toBe(false);
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
describe('revokeCsrfToken', () => {
|
|
154
|
+
it('should remove token from store', () => {
|
|
155
|
+
const sessionId = 'session-revoke';
|
|
156
|
+
createCsrfToken(sessionId);
|
|
157
|
+
|
|
158
|
+
expect(csrfTokenStore.tokens.has(sessionId)).toBe(true);
|
|
159
|
+
|
|
160
|
+
revokeCsrfToken(sessionId);
|
|
161
|
+
|
|
162
|
+
expect(csrfTokenStore.tokens.has(sessionId)).toBe(false);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it('should handle revoking non-existent token gracefully', () => {
|
|
166
|
+
expect(() => revokeCsrfToken('non-existent')).not.toThrow();
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it('should prevent validation after revocation', () => {
|
|
170
|
+
const sessionId = 'session-revoke-validate';
|
|
171
|
+
const token = createCsrfToken(sessionId);
|
|
172
|
+
|
|
173
|
+
revokeCsrfToken(sessionId);
|
|
174
|
+
|
|
175
|
+
const isValid = validateCsrfToken(sessionId, token);
|
|
176
|
+
expect(isValid).toBe(false);
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
describe('cleanupExpiredCsrfTokens', () => {
|
|
181
|
+
it('should remove expired tokens', async () => {
|
|
182
|
+
createCsrfToken('session-1', 0.00001);
|
|
183
|
+
createCsrfToken('session-2', 60); // Valid
|
|
184
|
+
|
|
185
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
186
|
+
|
|
187
|
+
expect(csrfTokenStore.tokens.size).toBe(2);
|
|
188
|
+
|
|
189
|
+
cleanupExpiredCsrfTokens();
|
|
190
|
+
|
|
191
|
+
expect(csrfTokenStore.tokens.size).toBe(1);
|
|
192
|
+
expect(csrfTokenStore.tokens.has('session-2')).toBe(true);
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it('should keep valid tokens', () => {
|
|
196
|
+
createCsrfToken('session-a', 60);
|
|
197
|
+
createCsrfToken('session-b', 60);
|
|
198
|
+
createCsrfToken('session-c', 60);
|
|
199
|
+
|
|
200
|
+
cleanupExpiredCsrfTokens();
|
|
201
|
+
|
|
202
|
+
expect(csrfTokenStore.tokens.size).toBe(3);
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
it('should handle empty token store', () => {
|
|
206
|
+
expect(() => cleanupExpiredCsrfTokens()).not.toThrow();
|
|
207
|
+
expect(csrfTokenStore.tokens.size).toBe(0);
|
|
208
|
+
});
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
describe('getCsrfTokenStats', () => {
|
|
212
|
+
it('should return token count', () => {
|
|
213
|
+
createCsrfToken('session-1');
|
|
214
|
+
createCsrfToken('session-2');
|
|
215
|
+
createCsrfToken('session-3');
|
|
216
|
+
|
|
217
|
+
const stats = getCsrfTokenStats();
|
|
218
|
+
expect(stats.tokenCount).toBe(3);
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
it('should return zero for empty store', () => {
|
|
222
|
+
const stats = getCsrfTokenStats();
|
|
223
|
+
expect(stats.tokenCount).toBe(0);
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
});
|
package/src/csrf.ts
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { randomBytes, timingSafeEqual } from 'crypto';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* CSRF Token storage interface
|
|
5
|
+
* Stores CSRF tokens associated with sessions
|
|
6
|
+
*/
|
|
7
|
+
export interface CsrfTokenStore {
|
|
8
|
+
tokens: Map<string, CsrfTokenData>;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* CSRF Token data with expiry
|
|
13
|
+
*/
|
|
14
|
+
export interface CsrfTokenData {
|
|
15
|
+
token: string;
|
|
16
|
+
expiresAt: number;
|
|
17
|
+
createdAt: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* In-memory CSRF token store
|
|
22
|
+
*/
|
|
23
|
+
export const csrfTokenStore: CsrfTokenStore = {
|
|
24
|
+
tokens: new Map()
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Generate a cryptographically secure CSRF token
|
|
29
|
+
* @returns CSRF token (base64 encoded for safe transmission)
|
|
30
|
+
*/
|
|
31
|
+
export function generateCsrfToken(): string {
|
|
32
|
+
return randomBytes(32).toString('base64url');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Generate CSRF token for a session
|
|
37
|
+
* @param sessionId - Session ID to store the token under
|
|
38
|
+
* @param expiryMinutes - Token expiry time in minutes (default: 60)
|
|
39
|
+
* @returns CSRF token
|
|
40
|
+
*/
|
|
41
|
+
export function createCsrfToken(sessionId: string, expiryMinutes: number = 60): string {
|
|
42
|
+
const token = generateCsrfToken();
|
|
43
|
+
const expiresAt = Date.now() + expiryMinutes * 60 * 1000;
|
|
44
|
+
|
|
45
|
+
csrfTokenStore.tokens.set(sessionId, {
|
|
46
|
+
token,
|
|
47
|
+
expiresAt,
|
|
48
|
+
createdAt: Date.now()
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
return token;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Validate a CSRF token against stored token for session
|
|
56
|
+
* Uses timing-safe comparison to prevent timing attacks
|
|
57
|
+
* @param sessionId - Session ID
|
|
58
|
+
* @param providedToken - Token to validate (from form/header)
|
|
59
|
+
* @returns True if valid, false otherwise
|
|
60
|
+
*/
|
|
61
|
+
export function validateCsrfToken(sessionId: string, providedToken: string): boolean {
|
|
62
|
+
const stored = csrfTokenStore.tokens.get(sessionId);
|
|
63
|
+
|
|
64
|
+
if (!stored) {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Check if token has expired
|
|
69
|
+
if (Date.now() > stored.expiresAt) {
|
|
70
|
+
csrfTokenStore.tokens.delete(sessionId);
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
try {
|
|
75
|
+
// Use timing-safe comparison to prevent timing attacks
|
|
76
|
+
const storedBuffer = Buffer.from(stored.token);
|
|
77
|
+
const providedBuffer = Buffer.from(providedToken);
|
|
78
|
+
|
|
79
|
+
// Check lengths match before comparison
|
|
80
|
+
if (storedBuffer.length !== providedBuffer.length) {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return timingSafeEqual(storedBuffer, providedBuffer);
|
|
85
|
+
} catch {
|
|
86
|
+
// If comparison fails, token is invalid
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Revoke a CSRF token
|
|
93
|
+
* @param sessionId - Session ID
|
|
94
|
+
*/
|
|
95
|
+
export function revokeCsrfToken(sessionId: string): void {
|
|
96
|
+
csrfTokenStore.tokens.delete(sessionId);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Clean up expired CSRF tokens
|
|
101
|
+
* Should be run periodically
|
|
102
|
+
*/
|
|
103
|
+
export function cleanupExpiredCsrfTokens(): void {
|
|
104
|
+
const now = Date.now();
|
|
105
|
+
const expiredSessions: string[] = [];
|
|
106
|
+
|
|
107
|
+
csrfTokenStore.tokens.forEach((data, sessionId) => {
|
|
108
|
+
if (now > data.expiresAt) {
|
|
109
|
+
expiredSessions.push(sessionId);
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
expiredSessions.forEach((sessionId) => {
|
|
114
|
+
csrfTokenStore.tokens.delete(sessionId);
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Get token statistics (for debugging/monitoring)
|
|
120
|
+
* @returns Number of stored tokens
|
|
121
|
+
*/
|
|
122
|
+
export function getCsrfTokenStats(): { tokenCount: number } {
|
|
123
|
+
return {
|
|
124
|
+
tokenCount: csrfTokenStore.tokens.size
|
|
125
|
+
};
|
|
126
|
+
}
|
package/src/db.ts
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import type { User } from './types';
|
|
4
|
+
import type { OAuthUser, OAuthSession } from './oauth-types';
|
|
5
|
+
|
|
6
|
+
interface DatabaseStore {
|
|
7
|
+
users: Record<string, User>;
|
|
8
|
+
sessions: Record<string, any>;
|
|
9
|
+
oauthUsers?: Record<string, OAuthUser>;
|
|
10
|
+
oauthSessions?: Record<string, OAuthSession>;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
let dbPath: string = '';
|
|
14
|
+
let store: DatabaseStore | null = null;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Get or initialize the database
|
|
18
|
+
*/
|
|
19
|
+
export function getDatabase(customDbPath?: string): DatabaseStore {
|
|
20
|
+
if (store) {
|
|
21
|
+
return store;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
dbPath = customDbPath || path.join(process.cwd(), 'auth.json');
|
|
25
|
+
|
|
26
|
+
if (fs.existsSync(dbPath)) {
|
|
27
|
+
const data = fs.readFileSync(dbPath, 'utf-8');
|
|
28
|
+
store = JSON.parse(data) as DatabaseStore;
|
|
29
|
+
} else {
|
|
30
|
+
store = {
|
|
31
|
+
users: {},
|
|
32
|
+
sessions: {}
|
|
33
|
+
};
|
|
34
|
+
saveDatabase();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return store as DatabaseStore;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Save database to disk
|
|
42
|
+
*/
|
|
43
|
+
export function saveDatabase(): void {
|
|
44
|
+
if (store && dbPath) {
|
|
45
|
+
fs.writeFileSync(dbPath, JSON.stringify(store, null, 2), 'utf-8');
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Close database connection
|
|
51
|
+
*/
|
|
52
|
+
export function closeDatabase(): void {
|
|
53
|
+
if (store) {
|
|
54
|
+
saveDatabase();
|
|
55
|
+
store = null;
|
|
56
|
+
}
|
|
57
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
// Types
|
|
2
|
+
export type { User, RegisterInput, LoginInput, Session, AuthResponse, PasswordResetRequest, PasswordReset } from './types';
|
|
3
|
+
|
|
4
|
+
// Database adapters
|
|
5
|
+
export type { DatabaseAdapter } from './adapters/adapter';
|
|
6
|
+
export { FileSystemAdapter, PostgresAdapter, MongoDBAdapter, SqliteAdapter } from './adapters';
|
|
7
|
+
export { initializeAdapter, initializeDefaultAdapter, getAdapter, closeAdapter } from './adapter-context';
|
|
8
|
+
|
|
9
|
+
// Legacy database (kept for backward compatibility)
|
|
10
|
+
export { getDatabase, closeDatabase } from './db';
|
|
11
|
+
|
|
12
|
+
// Password utilities
|
|
13
|
+
export { hashPassword, verifyPassword, validatePasswordStrength, isValidEmail } from './password';
|
|
14
|
+
|
|
15
|
+
// Token utilities
|
|
16
|
+
export { generateToken, generateId, getTokenExpiry, isTokenExpired } from './token';
|
|
17
|
+
|
|
18
|
+
// CSRF protection
|
|
19
|
+
export {
|
|
20
|
+
generateCsrfToken,
|
|
21
|
+
createCsrfToken,
|
|
22
|
+
validateCsrfToken,
|
|
23
|
+
revokeCsrfToken,
|
|
24
|
+
cleanupExpiredCsrfTokens,
|
|
25
|
+
getCsrfTokenStats
|
|
26
|
+
} from './csrf';
|
|
27
|
+
export type { CsrfTokenStore, CsrfTokenData } from './csrf';
|
|
28
|
+
|
|
29
|
+
// CORS configuration
|
|
30
|
+
export {
|
|
31
|
+
isOriginAllowed,
|
|
32
|
+
generateCorsHeaders,
|
|
33
|
+
isPreflightRequest,
|
|
34
|
+
validateCorsRequest,
|
|
35
|
+
defaultCorsConfig
|
|
36
|
+
} from './cors';
|
|
37
|
+
export type { CorsConfig } from './cors';
|
|
38
|
+
|
|
39
|
+
// Security headers
|
|
40
|
+
export {
|
|
41
|
+
generateSecurityHeaders,
|
|
42
|
+
validateCSP,
|
|
43
|
+
getDevelopmentCSP,
|
|
44
|
+
getProductionCSP,
|
|
45
|
+
defaultSecurityHeadersConfig,
|
|
46
|
+
securityLevels
|
|
47
|
+
} from './security-headers';
|
|
48
|
+
export type { SecurityHeadersConfig } from './security-headers';
|
|
49
|
+
|
|
50
|
+
// Input validation and sanitization
|
|
51
|
+
export {
|
|
52
|
+
sanitizeString,
|
|
53
|
+
validateEmail,
|
|
54
|
+
validatePassword,
|
|
55
|
+
validateUsername,
|
|
56
|
+
validateUrl,
|
|
57
|
+
validateNumber,
|
|
58
|
+
validateUuid,
|
|
59
|
+
escapeHtml,
|
|
60
|
+
validateObject
|
|
61
|
+
} from './input-validation';
|
|
62
|
+
export type { ValidationResult } from './input-validation';
|
|
63
|
+
|
|
64
|
+
// SQL injection prevention
|
|
65
|
+
export {
|
|
66
|
+
isSafePropertyName,
|
|
67
|
+
getSafeProperty,
|
|
68
|
+
safeFilter,
|
|
69
|
+
safeSearch,
|
|
70
|
+
sanitizeForLikeQuery,
|
|
71
|
+
prepareDatabaseValue,
|
|
72
|
+
validateDatabaseQuery,
|
|
73
|
+
databaseSecurityGuides,
|
|
74
|
+
getParameterizedQueryExample
|
|
75
|
+
} from './sql-injection-prevention';
|
|
76
|
+
export type { SafeQueryParams } from './sql-injection-prevention';
|
|
77
|
+
|
|
78
|
+
// Rate limiting
|
|
79
|
+
export {
|
|
80
|
+
RateLimiter,
|
|
81
|
+
rateLimitPresets,
|
|
82
|
+
getClientIdentifier,
|
|
83
|
+
getRateLimitHeaders
|
|
84
|
+
} from './rate-limiting';
|
|
85
|
+
export type { RateLimitConfig, RateLimitAttempt } from './rate-limiting';
|
|
86
|
+
|
|
87
|
+
// User operations
|
|
88
|
+
export { registerUser, getUserById, getUserByEmail, verifyUserEmail, setPasswordResetToken, updateUserPassword } from './user';
|
|
89
|
+
|
|
90
|
+
// Authentication
|
|
91
|
+
export {
|
|
92
|
+
loginUser,
|
|
93
|
+
createSession,
|
|
94
|
+
getSession,
|
|
95
|
+
destroySession,
|
|
96
|
+
requestPasswordReset,
|
|
97
|
+
verifyPasswordResetToken,
|
|
98
|
+
resetPassword,
|
|
99
|
+
verifyEmailToken,
|
|
100
|
+
completeEmailVerification,
|
|
101
|
+
cleanupExpiredSessions
|
|
102
|
+
} from './auth';
|
|
103
|
+
|
|
104
|
+
// Client-side stores and utilities (Svelte)
|
|
105
|
+
export { auth, user, isAuthenticated, isLoading, authError, accessToken } from './client-store';
|
|
106
|
+
export type { User as ClientUser, AuthState } from './client-store';
|
|
107
|
+
export { decodeToken, isTokenExpired as isTokenExpiredClient, getTokenTimeRemaining, createAuthHeader, extractToken } from './client-jwt';
|
|
108
|
+
export type { TokenPayload } from './client-jwt';
|
|
109
|
+
|
|
110
|
+
// OAuth/OIDC Types
|
|
111
|
+
export type {
|
|
112
|
+
OAuthProviderConfig,
|
|
113
|
+
OAuthUserProfile,
|
|
114
|
+
OAuthTokenResponse,
|
|
115
|
+
OAuthCodeResponse,
|
|
116
|
+
OAuthSession,
|
|
117
|
+
OAuthUser,
|
|
118
|
+
OAuthCallbackResponse
|
|
119
|
+
} from './oauth-types';
|
|
120
|
+
|
|
121
|
+
// OAuth Providers
|
|
122
|
+
export { OAuthProviderFactory, OAuthProfileParser, OAuthTokenManager, OAuthConfigStore } from './oauth-providers';
|
|
123
|
+
|
|
124
|
+
// OAuth Callbacks and Session Management
|
|
125
|
+
export {
|
|
126
|
+
handleOAuthCallback,
|
|
127
|
+
getOAuthSession,
|
|
128
|
+
destroyOAuthSession,
|
|
129
|
+
refreshOAuthToken,
|
|
130
|
+
getOAuthUser,
|
|
131
|
+
updateOAuthUserProfile,
|
|
132
|
+
cleanupExpiredOAuthSessions
|
|
133
|
+
} from './oauth-callback';
|
|
134
|
+
|
|
135
|
+
// OAuth Specific Providers
|
|
136
|
+
export { GoogleOAuthProvider } from './providers/google-oauth';
|
|
137
|
+
export type { GoogleOAuthConfig } from './providers/google-oauth';
|
|
138
|
+
|
|
139
|
+
export { GitHubOAuthProvider } from './providers/github-oauth';
|
|
140
|
+
export type { GitHubOAuthConfig } from './providers/github-oauth';
|
|
141
|
+
|
|
142
|
+
export { OIDCOAuthProvider } from './providers/oidc-oauth';
|
|
143
|
+
export type { OIDCProviderConfig } from './providers/oidc-oauth';
|