@cequrebackends/plugin-security 0.13.0 → 0.14.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/auth-routes.d.ts.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +58 -3
- package/dist/password.d.ts +12 -0
- package/dist/password.d.ts.map +1 -0
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth-routes.d.ts","sourceRoot":"","sources":["../src/auth-routes.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;
|
|
1
|
+
{"version":3,"file":"auth-routes.d.ts","sourceRoot":"","sources":["../src/auth-routes.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAqD/D,wBAAgB,eAAe,CAAC,MAAM,EAAE,aAAa,CAAC,GAAG,CAAC,QA8azD"}
|
package/dist/index.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ export { type TokenStore, InMemoryTokenStore, AdapterTokenStore, REFRESH_TOKEN_C
|
|
|
5
5
|
export * from './audit';
|
|
6
6
|
export * from './encryption';
|
|
7
7
|
export * from './mfa';
|
|
8
|
+
export { hashPassword, verifyPassword } from './password';
|
|
8
9
|
export { type SecretsProvider, EnvVarSecrets } from './secrets';
|
|
9
10
|
export { signWebhook, verifyWebhookSignature, WEBHOOK_SIGNATURE_HEADER } from './webhooks';
|
|
10
11
|
export declare function defaultSecurity(): CequrePlugin;
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAqB,MAAM,2BAA2B,CAAC;AAMjF,cAAc,QAAQ,CAAC;AACvB,cAAc,gBAAgB,CAAC;AAC/B,OAAO,EAAE,KAAK,UAAU,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,wBAAwB,EAAE,MAAM,eAAe,CAAC;AACjH,cAAc,SAAS,CAAC;AACxB,cAAc,cAAc,CAAC;AAC7B,cAAc,OAAO,CAAC;AACtB,OAAO,EAAE,KAAK,eAAe,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAChE,OAAO,EAAE,WAAW,EAAE,sBAAsB,EAAE,wBAAwB,EAAE,MAAM,YAAY,CAAC;AAI3F,wBAAgB,eAAe,IAAI,YAAY,CAuI9C"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAqB,MAAM,2BAA2B,CAAC;AAMjF,cAAc,QAAQ,CAAC;AACvB,cAAc,gBAAgB,CAAC;AAC/B,OAAO,EAAE,KAAK,UAAU,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,wBAAwB,EAAE,MAAM,eAAe,CAAC;AACjH,cAAc,SAAS,CAAC;AACxB,cAAc,cAAc,CAAC;AAC7B,cAAc,OAAO,CAAC;AACtB,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAC1D,OAAO,EAAE,KAAK,eAAe,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAChE,OAAO,EAAE,WAAW,EAAE,sBAAsB,EAAE,wBAAwB,EAAE,MAAM,YAAY,CAAC;AAI3F,wBAAgB,eAAe,IAAI,YAAY,CAuI9C"}
|
package/dist/index.js
CHANGED
|
@@ -115,6 +115,57 @@ async function hashBackupCode(code) {
|
|
|
115
115
|
var BASE32_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
|
|
116
116
|
var init_mfa = () => {};
|
|
117
117
|
|
|
118
|
+
// src/password.ts
|
|
119
|
+
async function hashPassword(plaintext) {
|
|
120
|
+
if (isBun) {
|
|
121
|
+
return globalThis.Bun.password.hash(plaintext, "bcrypt");
|
|
122
|
+
}
|
|
123
|
+
const salt = crypto.getRandomValues(new Uint8Array(SALT_LENGTH));
|
|
124
|
+
const keyMaterial = await crypto.subtle.importKey("raw", new TextEncoder().encode(plaintext), "PBKDF2", false, ["deriveBits"]);
|
|
125
|
+
const derivedBits = await crypto.subtle.deriveBits({
|
|
126
|
+
name: "PBKDF2",
|
|
127
|
+
salt,
|
|
128
|
+
iterations: PBKDF2_ITERATIONS,
|
|
129
|
+
hash: "SHA-256"
|
|
130
|
+
}, keyMaterial, KEY_LENGTH * 8);
|
|
131
|
+
const hash = new Uint8Array(derivedBits);
|
|
132
|
+
return `pbkdf2:${PBKDF2_ITERATIONS}:${btoa(String.fromCharCode(...salt))}:${btoa(String.fromCharCode(...hash))}`;
|
|
133
|
+
}
|
|
134
|
+
async function verifyPassword(plaintext, stored) {
|
|
135
|
+
if (isBun && !stored.startsWith("pbkdf2:")) {
|
|
136
|
+
return globalThis.Bun.password.verify(plaintext, stored);
|
|
137
|
+
}
|
|
138
|
+
const parts = stored.split(":");
|
|
139
|
+
if (parts.length !== 4 || parts[0] !== "pbkdf2") {
|
|
140
|
+
if (isBun) {
|
|
141
|
+
return globalThis.Bun.password.verify(plaintext, stored);
|
|
142
|
+
}
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
145
|
+
const iterations = parseInt(parts[1], 10);
|
|
146
|
+
const salt = Uint8Array.from(atob(parts[2]), (c) => c.charCodeAt(0));
|
|
147
|
+
const storedHash = atob(parts[3]);
|
|
148
|
+
const keyMaterial = await crypto.subtle.importKey("raw", new TextEncoder().encode(plaintext), "PBKDF2", false, ["deriveBits"]);
|
|
149
|
+
const derivedBits = await crypto.subtle.deriveBits({
|
|
150
|
+
name: "PBKDF2",
|
|
151
|
+
salt,
|
|
152
|
+
iterations,
|
|
153
|
+
hash: "SHA-256"
|
|
154
|
+
}, keyMaterial, KEY_LENGTH * 8);
|
|
155
|
+
const hash = String.fromCharCode(...new Uint8Array(derivedBits));
|
|
156
|
+
if (hash.length !== storedHash.length)
|
|
157
|
+
return false;
|
|
158
|
+
let diff = 0;
|
|
159
|
+
for (let i = 0;i < hash.length; i++) {
|
|
160
|
+
diff |= hash.charCodeAt(i) ^ storedHash.charCodeAt(i);
|
|
161
|
+
}
|
|
162
|
+
return diff === 0;
|
|
163
|
+
}
|
|
164
|
+
var isBun, PBKDF2_ITERATIONS = 1e5, SALT_LENGTH = 16, KEY_LENGTH = 32;
|
|
165
|
+
var init_password = __esm(() => {
|
|
166
|
+
isBun = typeof globalThis.Bun !== "undefined";
|
|
167
|
+
});
|
|
168
|
+
|
|
118
169
|
// src/auth-routes.ts
|
|
119
170
|
var exports_auth_routes = {};
|
|
120
171
|
__export(exports_auth_routes, {
|
|
@@ -194,7 +245,7 @@ function setupAuthRoutes(cequre) {
|
|
|
194
245
|
const existing = await cequre.secureFind(collection.slug, { where: { email: { eq: data.email } }, limit: 1 });
|
|
195
246
|
if (existing.docs.length > 0)
|
|
196
247
|
return new Response(JSON.stringify({ error: "User already exists" }), { status: 409 });
|
|
197
|
-
const hashedPassword = await
|
|
248
|
+
const hashedPassword = await hashPassword(data.password);
|
|
198
249
|
const registerData = cequre.stripForbiddenFields(collection.slug, { ...data, password: hashedPassword });
|
|
199
250
|
registerData.password = hashedPassword;
|
|
200
251
|
const user = await cequre.secureCreate(collection.slug, registerData);
|
|
@@ -239,7 +290,7 @@ function setupAuthRoutes(cequre) {
|
|
|
239
290
|
await cequre.recordFailedLogin(collection.slug, data.email, lockoutConfig);
|
|
240
291
|
return new Response(JSON.stringify({ error: "Oops! The email or password credentials provided don't match our records." }), { status: 401 });
|
|
241
292
|
}
|
|
242
|
-
const valid = await
|
|
293
|
+
const valid = await verifyPassword(data.password, user.password);
|
|
243
294
|
if (!valid) {
|
|
244
295
|
await cequre.recordFailedLogin(collection.slug, data.email, lockoutConfig);
|
|
245
296
|
return new Response(JSON.stringify({ error: "Oops! The email or password credentials provided don't match our records." }), { status: 401 });
|
|
@@ -503,7 +554,7 @@ function setupAuthRoutes(cequre) {
|
|
|
503
554
|
if (!verified || verified.collection !== collection.slug) {
|
|
504
555
|
return new Response(JSON.stringify({ error: "Invalid or expired reset token" }), { status: 400, headers: { "Content-Type": "application/json" } });
|
|
505
556
|
}
|
|
506
|
-
const hashedPassword = await
|
|
557
|
+
const hashedPassword = await hashPassword(data.password);
|
|
507
558
|
await cequre.secureUpdate(collection.slug, verified.userId, { password: hashedPassword });
|
|
508
559
|
await cequre.authEngine.revokeAllForUser(verified.userId);
|
|
509
560
|
const user = await cequre.secureFindById(collection.slug, verified.userId);
|
|
@@ -536,6 +587,7 @@ function setupAuthRoutes(cequre) {
|
|
|
536
587
|
}
|
|
537
588
|
var init_auth_routes = __esm(() => {
|
|
538
589
|
init_mfa();
|
|
590
|
+
init_password();
|
|
539
591
|
});
|
|
540
592
|
|
|
541
593
|
// src/auth.ts
|
|
@@ -1003,6 +1055,7 @@ class RateLimiter {
|
|
|
1003
1055
|
}
|
|
1004
1056
|
|
|
1005
1057
|
// src/index.ts
|
|
1058
|
+
init_password();
|
|
1006
1059
|
init_mfa();
|
|
1007
1060
|
|
|
1008
1061
|
// src/secrets.ts
|
|
@@ -1173,6 +1226,7 @@ function defaultSecurity() {
|
|
|
1173
1226
|
export {
|
|
1174
1227
|
verifyWebhookSignature,
|
|
1175
1228
|
verifyTOTP,
|
|
1229
|
+
verifyPassword,
|
|
1176
1230
|
verifyAuditHmac,
|
|
1177
1231
|
verifyAuditChain,
|
|
1178
1232
|
signWebhook,
|
|
@@ -1180,6 +1234,7 @@ export {
|
|
|
1180
1234
|
resolveClientIP,
|
|
1181
1235
|
resolveAuditSecret,
|
|
1182
1236
|
isEncrypted,
|
|
1237
|
+
hashPassword,
|
|
1183
1238
|
hashBackupCode,
|
|
1184
1239
|
generateTOTPSecret,
|
|
1185
1240
|
generateTOTP,
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime-agnostic password hashing.
|
|
3
|
+
*
|
|
4
|
+
* - On Bun: uses Bun.password (native bcrypt)
|
|
5
|
+
* - On Node/serverless: uses WebCrypto PBKDF2 (no native deps)
|
|
6
|
+
*
|
|
7
|
+
* This allows Cequre apps to run on both Bun (self-hosted) and
|
|
8
|
+
* Vercel serverless (Node.js) without changing auth code.
|
|
9
|
+
*/
|
|
10
|
+
export declare function hashPassword(plaintext: string): Promise<string>;
|
|
11
|
+
export declare function verifyPassword(plaintext: string, stored: string): Promise<boolean>;
|
|
12
|
+
//# sourceMappingURL=password.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"password.d.ts","sourceRoot":"","sources":["../src/password.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAQH,wBAAsB,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CA2BrE;AAED,wBAAsB,cAAc,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CA8CxF"}
|