@cequrebackends/plugin-security 0.13.0 → 0.15.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.
@@ -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;AAoD/D,wBAAgB,eAAe,CAAC,MAAM,EAAE,aAAa,CAAC,GAAG,CAAC,QA8azD"}
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;
@@ -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 Bun.password.hash(data.password, "bcrypt");
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 Bun.password.verify(data.password, user.password);
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 Bun.password.hash(data.password, "bcrypt");
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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cequrebackends/plugin-security",
3
- "version": "0.13.0",
3
+ "version": "0.15.0",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.js",
@@ -14,7 +14,7 @@
14
14
  "devDependencies": {
15
15
  "tsup": "^8.5.1",
16
16
  "typescript": "^5.0.0",
17
- "@cequrebackends/cequre-ts": "0.13.0"
17
+ "@cequrebackends/cequre-ts": "0.14.0"
18
18
  },
19
19
  "sideEffects": false,
20
20
  "exports": {
@@ -63,6 +63,6 @@
63
63
  "url": "https://github.com/cequrebackends/cequre.git"
64
64
  },
65
65
  "peerDependencies": {
66
- "@cequrebackends/cequre-ts": "0.13.0"
66
+ "@cequrebackends/cequre-ts": "0.14.0"
67
67
  }
68
68
  }