@uln/auth 0.4.2 → 0.6.1

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/src/users.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { hashPassword, verifyPassword } from "./password.ts";
1
+ import { hashPassword, MAX_PASSWORD_LENGTH, needsRehash, verifyPassword } from "./password.ts";
2
2
  import type { User, UserStore } from "./types.ts";
3
3
 
4
4
  const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
@@ -13,6 +13,8 @@ export async function createUser(
13
13
  input: { email: string; name: string; password?: string; isAdmin?: boolean },
14
14
  ): Promise<User> {
15
15
  if (!isValidEmail(input.email)) throw new Error("Invalid email address");
16
+ if (input.password !== undefined && input.password.length > MAX_PASSWORD_LENGTH)
17
+ throw new Error("Password is too long");
16
18
  const existing = await store.getUserByEmail(input.email);
17
19
  if (existing) throw new Error(`User with email ${input.email} already exists`);
18
20
 
@@ -35,12 +37,27 @@ export async function createUser(
35
37
  return user;
36
38
  }
37
39
 
40
+ let dummyHash: Promise<string> | null = null;
41
+
42
+ /**
43
+ * Check an email/password pair. When the user doesn't exist a dummy hash is still verified, so response timing
44
+ * doesn't reveal which emails have accounts. A correct password stored under older hashing parameters is re-hashed
45
+ * with the current ones.
46
+ */
38
47
  export async function verifyUserCredentials(store: UserStore, email: string, password: string): Promise<User | null> {
39
48
  const user = await store.getUserByEmail(email.toLowerCase());
40
- if (!user) return null;
49
+ if (!user) {
50
+ dummyHash ??= hashPassword(crypto.randomUUID());
51
+ await verifyPassword(password, await dummyHash);
52
+ return null;
53
+ }
41
54
 
42
55
  const valid = await verifyPassword(password, user.passwordHash);
43
- return valid ? user : null;
56
+ if (!valid) return null;
57
+ if (!needsRehash(user.passwordHash)) return user;
58
+ const upgraded: User = { ...user, passwordHash: await hashPassword(password), updatedAt: new Date().toISOString() };
59
+ await store.saveUser(upgraded);
60
+ return upgraded;
44
61
  }
45
62
 
46
63
  export async function updateName(store: UserStore, userId: string, name: string): Promise<void> {
@@ -56,6 +73,7 @@ export async function updateName(store: UserStore, userId: string, name: string)
56
73
  }
57
74
 
58
75
  export async function changePassword(store: UserStore, userId: string, newPassword: string): Promise<void> {
76
+ if (newPassword.length > MAX_PASSWORD_LENGTH) throw new Error("Password is too long");
59
77
  const user = await store.getUser(userId);
60
78
  if (!user) throw new Error(`User ${userId} not found`);
61
79
 
package/src/version.ts ADDED
@@ -0,0 +1,6 @@
1
+ /**
2
+ * This library's version. Sent to the auth service on every login (`uln_v` on the return URL) so Admin → Sites can show
3
+ * which version each site runs and open upgrade PRs for the ones behind. `src/version.test.ts` keeps it equal to
4
+ * `package.json`.
5
+ */
6
+ export const LIBRARY_VERSION = "0.6.1";