@contentgrowth/content-auth 0.6.2 → 0.6.4

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.
@@ -47,6 +47,12 @@ declare function normalizeEmail(email: string): string;
47
47
  */
48
48
  declare function isGmailAddress(email: string): boolean;
49
49
 
50
+ declare function hashPassword(password: string): Promise<string>;
51
+ declare function verifyPassword(passwordOrData: string | {
52
+ password: string;
53
+ hash: string;
54
+ }, storedHash?: string): Promise<boolean>;
55
+
50
56
  declare const createUsersTable: (tableName?: string, fields?: Record<string, string>, additionalFields?: Record<string, any>) => drizzle_orm_sqlite_core.SQLiteTableWithColumns<{
51
57
  name: string;
52
58
  schema: undefined;
@@ -904,4 +910,4 @@ declare const createAuthApp: (config: AuthConfig) => {
904
910
  auth: better_auth.Auth<any>;
905
911
  };
906
912
 
907
- export { type AuthConfig, type EmailNormalizationConfig, type FieldAttribute, type SchemaMapping, type SignInTrackingConfig, type TableMapping, type TurnstileConfig, authMiddleware, createAuth, createAuthApp, getInvitationLink, getSessionToken, isGmailAddress, normalizeEmail, schema, verifyTurnstile };
913
+ export { type AuthConfig, type EmailNormalizationConfig, type FieldAttribute, type SchemaMapping, type SignInTrackingConfig, type TableMapping, type TurnstileConfig, authMiddleware, createAuth, createAuthApp, getInvitationLink, getSessionToken, hashPassword, isGmailAddress, normalizeEmail, schema, verifyPassword, verifyTurnstile };
@@ -5,11 +5,13 @@ import {
5
5
  createAuthApp,
6
6
  getInvitationLink,
7
7
  getSessionToken,
8
+ hashPassword,
8
9
  isGmailAddress,
9
10
  normalizeEmail,
10
11
  schema_exports,
12
+ verifyPassword,
11
13
  verifyTurnstile
12
- } from "../chunk-MDSQC2UU.js";
14
+ } from "../chunk-KUS3REJX.js";
13
15
  import "../chunk-R5U7XKVJ.js";
14
16
  export {
15
17
  Hono,
@@ -18,8 +20,10 @@ export {
18
20
  createAuthApp,
19
21
  getInvitationLink,
20
22
  getSessionToken,
23
+ hashPassword,
21
24
  isGmailAddress,
22
25
  normalizeEmail,
23
26
  schema_exports as schema,
27
+ verifyPassword,
24
28
  verifyTurnstile
25
29
  };
@@ -101,6 +101,80 @@ function isGmailAddress(email) {
101
101
  return lowerEmail.endsWith("@gmail.com") || lowerEmail.endsWith("@googlemail.com");
102
102
  }
103
103
 
104
+ // src/backend/native-hashing.ts
105
+ async function hashPassword(password) {
106
+ const salt = crypto.getRandomValues(new Uint8Array(16));
107
+ const enc = new TextEncoder();
108
+ const keyMaterial = await crypto.subtle.importKey(
109
+ "raw",
110
+ enc.encode(password),
111
+ { name: "PBKDF2" },
112
+ false,
113
+ ["deriveBits", "deriveKey"]
114
+ );
115
+ const hashBuffer = await crypto.subtle.deriveBits(
116
+ {
117
+ name: "PBKDF2",
118
+ salt,
119
+ iterations: 1e5,
120
+ hash: "SHA-256"
121
+ },
122
+ keyMaterial,
123
+ 256
124
+ // 32 bytes
125
+ );
126
+ const saltB64 = btoa(String.fromCharCode(...new Uint8Array(salt)));
127
+ const hashB64 = btoa(String.fromCharCode(...new Uint8Array(hashBuffer)));
128
+ return `pbkdf2:100000:${saltB64}:${hashB64}`;
129
+ }
130
+ async function verifyPassword(passwordOrData, storedHash) {
131
+ let password;
132
+ let hash;
133
+ if (typeof passwordOrData === "object" && passwordOrData !== null) {
134
+ password = passwordOrData.password;
135
+ hash = passwordOrData.hash;
136
+ } else {
137
+ password = passwordOrData;
138
+ hash = storedHash;
139
+ }
140
+ if (!hash) {
141
+ console.error("[Auth] verifyPassword called with empty/undefined hash");
142
+ return false;
143
+ }
144
+ const parts = hash.split(":");
145
+ if (parts.length !== 4) return false;
146
+ const [alg, iterationsStr, saltB64, hashB64] = parts;
147
+ if (alg !== "pbkdf2") return false;
148
+ const iterations = parseInt(iterationsStr, 10);
149
+ const salt = Uint8Array.from(atob(saltB64), (c) => c.charCodeAt(0));
150
+ const originalHash = Uint8Array.from(atob(hashB64), (c) => c.charCodeAt(0));
151
+ const enc = new TextEncoder();
152
+ const keyMaterial = await crypto.subtle.importKey(
153
+ "raw",
154
+ enc.encode(password),
155
+ { name: "PBKDF2" },
156
+ false,
157
+ ["deriveBits", "deriveKey"]
158
+ );
159
+ const hashBuffer = await crypto.subtle.deriveBits(
160
+ {
161
+ name: "PBKDF2",
162
+ salt,
163
+ iterations,
164
+ hash: "SHA-256"
165
+ },
166
+ keyMaterial,
167
+ 256
168
+ );
169
+ const newHash = new Uint8Array(hashBuffer);
170
+ if (newHash.length !== originalHash.length) return false;
171
+ let result = 0;
172
+ for (let i = 0; i < newHash.length; i++) {
173
+ result |= newHash[i] ^ originalHash[i];
174
+ }
175
+ return result === 0;
176
+ }
177
+
104
178
  // src/backend/schema.ts
105
179
  var schema_exports = {};
106
180
  __export(schema_exports, {
@@ -244,80 +318,6 @@ var verifications = createVerificationsTable();
244
318
  var members = createMembersTable("members", organizations, users);
245
319
  var invitations = createInvitationsTable("invitations", organizations, users);
246
320
 
247
- // src/backend/native-hashing.ts
248
- async function hashPassword(password) {
249
- const salt = crypto.getRandomValues(new Uint8Array(16));
250
- const enc = new TextEncoder();
251
- const keyMaterial = await crypto.subtle.importKey(
252
- "raw",
253
- enc.encode(password),
254
- { name: "PBKDF2" },
255
- false,
256
- ["deriveBits", "deriveKey"]
257
- );
258
- const hashBuffer = await crypto.subtle.deriveBits(
259
- {
260
- name: "PBKDF2",
261
- salt,
262
- iterations: 1e5,
263
- hash: "SHA-256"
264
- },
265
- keyMaterial,
266
- 256
267
- // 32 bytes
268
- );
269
- const saltB64 = btoa(String.fromCharCode(...new Uint8Array(salt)));
270
- const hashB64 = btoa(String.fromCharCode(...new Uint8Array(hashBuffer)));
271
- return `pbkdf2:100000:${saltB64}:${hashB64}`;
272
- }
273
- async function verifyPassword(passwordOrData, storedHash) {
274
- let password;
275
- let hash;
276
- if (typeof passwordOrData === "object" && passwordOrData !== null) {
277
- password = passwordOrData.password;
278
- hash = passwordOrData.hash;
279
- } else {
280
- password = passwordOrData;
281
- hash = storedHash;
282
- }
283
- if (!hash) {
284
- console.error("[Auth] verifyPassword called with empty/undefined hash");
285
- return false;
286
- }
287
- const parts = hash.split(":");
288
- if (parts.length !== 4) return false;
289
- const [alg, iterationsStr, saltB64, hashB64] = parts;
290
- if (alg !== "pbkdf2") return false;
291
- const iterations = parseInt(iterationsStr, 10);
292
- const salt = Uint8Array.from(atob(saltB64), (c) => c.charCodeAt(0));
293
- const originalHash = Uint8Array.from(atob(hashB64), (c) => c.charCodeAt(0));
294
- const enc = new TextEncoder();
295
- const keyMaterial = await crypto.subtle.importKey(
296
- "raw",
297
- enc.encode(password),
298
- { name: "PBKDF2" },
299
- false,
300
- ["deriveBits", "deriveKey"]
301
- );
302
- const hashBuffer = await crypto.subtle.deriveBits(
303
- {
304
- name: "PBKDF2",
305
- salt,
306
- iterations,
307
- hash: "SHA-256"
308
- },
309
- keyMaterial,
310
- 256
311
- );
312
- const newHash = new Uint8Array(hashBuffer);
313
- if (newHash.length !== originalHash.length) return false;
314
- let result = 0;
315
- for (let i = 0; i < newHash.length; i++) {
316
- result |= newHash[i] ^ originalHash[i];
317
- }
318
- return result === 0;
319
- }
320
-
321
321
  // src/backend/utils.ts
322
322
  function getInvitationLink(data, baseUrl) {
323
323
  let rawLink = data.link || data.url;
@@ -637,6 +637,44 @@ var createAuth = (config) => {
637
637
  console.log("[ContentAuth-DEBUG] Other options passed to betterAuth():", Object.keys(otherOptions).join(", "));
638
638
  console.log("[ContentAuth-DEBUG] ========================================");
639
639
  }
640
+ auth.createUser = async (options) => {
641
+ const { email, password, name, emailVerified = false } = options;
642
+ const signupRequest = new Request(`${baseUrl}/api/auth/sign-up/email`, {
643
+ method: "POST",
644
+ headers: { "Content-Type": "application/json" },
645
+ body: JSON.stringify({
646
+ email: email.toLowerCase().trim(),
647
+ password,
648
+ name: name || email.split("@")[0],
649
+ callbackURL: "/"
650
+ })
651
+ });
652
+ const signupResponse = await auth.handler(signupRequest);
653
+ if (!signupResponse.ok) {
654
+ const errorData = await signupResponse.json();
655
+ throw new Error(`Failed to create user: ${JSON.stringify(errorData)}`);
656
+ }
657
+ const signupData = await signupResponse.json();
658
+ if (emailVerified && signupData.user?.id && rawDb?.prepare) {
659
+ await rawDb.prepare(
660
+ `UPDATE ${userTableName} SET ${userIdColumn === "id" ? "emailVerified" : schemaMapping?.user?.fields?.emailVerified || "emailVerified"} = 1 WHERE ${userIdColumn} = ?`
661
+ ).bind(signupData.user.id).run();
662
+ }
663
+ return signupData.user;
664
+ };
665
+ auth.resetUserPassword = async (userId, newPassword) => {
666
+ if (!rawDb?.prepare) {
667
+ throw new Error("Password reset requires database access");
668
+ }
669
+ const hashedPassword = await hashPassword(newPassword);
670
+ const accountIdField = schemaMapping?.account?.fields?.userId || "userId";
671
+ const passwordField = schemaMapping?.account?.fields?.password || "password";
672
+ const updatedAtField = schemaMapping?.account?.fields?.updatedAt || "updatedAt";
673
+ await rawDb.prepare(
674
+ `UPDATE ${accountTableName} SET ${passwordField} = ?, ${updatedAtField} = ? WHERE ${accountIdField} = ? AND providerId = 'credential'`
675
+ ).bind(hashedPassword, Date.now(), userId).run();
676
+ return { success: true };
677
+ };
640
678
  return auth;
641
679
  };
642
680
  var authMiddleware = (auth) => {
@@ -657,6 +695,8 @@ export {
657
695
  verifyTurnstile,
658
696
  normalizeEmail,
659
697
  isGmailAddress,
698
+ hashPassword,
699
+ verifyPassword,
660
700
  schema_exports,
661
701
  getInvitationLink,
662
702
  getSessionToken,
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { AuthConfig, EmailNormalizationConfig, FieldAttribute, SchemaMapping, SignInTrackingConfig, TableMapping, TurnstileConfig, authMiddleware, createAuth, createAuthApp, getInvitationLink, getSessionToken, isGmailAddress, normalizeEmail, schema, verifyTurnstile } from './backend/index.js';
1
+ export { AuthConfig, EmailNormalizationConfig, FieldAttribute, SchemaMapping, SignInTrackingConfig, TableMapping, TurnstileConfig, authMiddleware, createAuth, createAuthApp, getInvitationLink, getSessionToken, hashPassword, isGmailAddress, normalizeEmail, schema, verifyPassword, verifyTurnstile } from './backend/index.js';
2
2
  export { AuthForm, CreateOrganizationForm, ForgotPasswordForm, InviteMemberForm, OrganizationSwitcher, PasswordChanger, PasswordChangerProps, ProfileEditor, ProfileEditorProps, ResetPasswordForm } from './frontend/index.js';
3
3
  export { ContentAuthClientOptions, authClient, createClient } from './frontend/client.js';
4
4
  export * from 'better-auth';
package/dist/index.js CHANGED
@@ -5,11 +5,13 @@ import {
5
5
  createAuthApp,
6
6
  getInvitationLink,
7
7
  getSessionToken,
8
+ hashPassword,
8
9
  isGmailAddress,
9
10
  normalizeEmail,
10
11
  schema_exports,
12
+ verifyPassword,
11
13
  verifyTurnstile
12
- } from "./chunk-MDSQC2UU.js";
14
+ } from "./chunk-KUS3REJX.js";
13
15
  import {
14
16
  AuthForm,
15
17
  CreateOrganizationForm,
@@ -42,8 +44,10 @@ export {
42
44
  createClient,
43
45
  getInvitationLink,
44
46
  getSessionToken,
47
+ hashPassword,
45
48
  isGmailAddress,
46
49
  normalizeEmail,
47
50
  schema_exports as schema,
51
+ verifyPassword,
48
52
  verifyTurnstile
49
53
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contentgrowth/content-auth",
3
- "version": "0.6.2",
3
+ "version": "0.6.4",
4
4
  "description": "Better Auth wrapper with UI components for Cloudflare Workers & Pages. Includes custom schema mapping, Turnstile bot protection, and email normalization.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",