@fonderie/auth 3.0.0 → 4.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/index.d.cts CHANGED
@@ -1,9 +1,9 @@
1
1
  export { IMfaChallenge, ISession, IUser } from './types.cjs';
2
- import { IFonderieModule, IFonderieApp } from '@fonderie/core';
2
+ import { IFonderieModule, IReadinessProblem, IFonderieApp } from '@fonderie/core';
3
3
  import { IStoreAdapter } from '@fonderie/store';
4
4
  import { EventBus } from '@fonderie/events';
5
- import { I as IAuthConfig } from './session-BoJjRB7t.cjs';
6
- export { A as AUTH_CONFIG_KEYS, a as AuthLimitedRoute, b as AuthMessageKey, c as IAuthRateLimitConfig, d as IAuthRuntimeConfig, e as IAuthSecrets, M as MESSAGE_KEYS, f as buildAuthAccountLimiter, g as buildAuthIpLimiter, w as withSession } from './session-BoJjRB7t.cjs';
5
+ import { I as IAuthConfig } from './session-DxsmQjXX.cjs';
6
+ export { A as AUTH_CONFIG_KEYS, a as AuthLimitedRoute, b as AuthMessageKey, c as IAuthRateLimitConfig, d as IAuthRuntimeConfig, e as IAuthSecrets, M as MESSAGE_KEYS, f as buildAuthAccountLimiter, g as buildAuthIpLimiter, w as withSession } from './session-DxsmQjXX.cjs';
7
7
  export { IUserDTO, toUserDTO } from './dtos/user.cjs';
8
8
  export { requireAuth, validate } from '@fonderie/core/middlewares';
9
9
  import { z } from 'zod';
@@ -15,6 +15,7 @@ declare class AuthModule implements IFonderieModule {
15
15
  private bus?;
16
16
  readonly name = "@fonderie/auth";
17
17
  constructor(store: IStoreAdapter, config: IAuthConfig, bus?: EventBus | undefined);
18
+ checkReadiness(): IReadinessProblem[];
18
19
  install(app: IFonderieApp): void;
19
20
  }
20
21
 
@@ -99,4 +100,24 @@ declare namespace schemas {
99
100
  declare function normalizeEmail(email: string): string;
100
101
  declare function normalizeEmailSafe(email: string): string | null;
101
102
 
102
- export { AuthModule, type ChangePasswordInput, IAuthConfig, type LoginInput, type RegisterInput, type ResetPasswordInput, normalizeEmail, normalizeEmailSafe, schemas };
103
+ interface IImportUser {
104
+ id?: string;
105
+ email: string;
106
+ passwordHash?: string | null;
107
+ firstName?: string | null;
108
+ lastName?: string | null;
109
+ phone?: string | null;
110
+ profileImageUrl?: string | null;
111
+ locale?: string;
112
+ timezone?: string;
113
+ emailVerifiedAt?: Date | null;
114
+ mfaEnabled?: boolean;
115
+ createdAt?: Date;
116
+ }
117
+ declare function importUser(store: IStoreAdapter, user: IImportUser): Promise<{
118
+ id: string;
119
+ }>;
120
+
121
+ declare function validateAuthConfig(config: IAuthConfig): void;
122
+
123
+ export { AuthModule, type ChangePasswordInput, IAuthConfig, type IImportUser, type LoginInput, type RegisterInput, type ResetPasswordInput, importUser, normalizeEmail, normalizeEmailSafe, schemas, validateAuthConfig };
package/dist/index.d.ts CHANGED
@@ -1,9 +1,9 @@
1
1
  export { IMfaChallenge, ISession, IUser } from './types.js';
2
- import { IFonderieModule, IFonderieApp } from '@fonderie/core';
2
+ import { IFonderieModule, IReadinessProblem, IFonderieApp } from '@fonderie/core';
3
3
  import { IStoreAdapter } from '@fonderie/store';
4
4
  import { EventBus } from '@fonderie/events';
5
- import { I as IAuthConfig } from './session-BoJjRB7t.js';
6
- export { A as AUTH_CONFIG_KEYS, a as AuthLimitedRoute, b as AuthMessageKey, c as IAuthRateLimitConfig, d as IAuthRuntimeConfig, e as IAuthSecrets, M as MESSAGE_KEYS, f as buildAuthAccountLimiter, g as buildAuthIpLimiter, w as withSession } from './session-BoJjRB7t.js';
5
+ import { I as IAuthConfig } from './session-DxsmQjXX.js';
6
+ export { A as AUTH_CONFIG_KEYS, a as AuthLimitedRoute, b as AuthMessageKey, c as IAuthRateLimitConfig, d as IAuthRuntimeConfig, e as IAuthSecrets, M as MESSAGE_KEYS, f as buildAuthAccountLimiter, g as buildAuthIpLimiter, w as withSession } from './session-DxsmQjXX.js';
7
7
  export { IUserDTO, toUserDTO } from './dtos/user.js';
8
8
  export { requireAuth, validate } from '@fonderie/core/middlewares';
9
9
  import { z } from 'zod';
@@ -15,6 +15,7 @@ declare class AuthModule implements IFonderieModule {
15
15
  private bus?;
16
16
  readonly name = "@fonderie/auth";
17
17
  constructor(store: IStoreAdapter, config: IAuthConfig, bus?: EventBus | undefined);
18
+ checkReadiness(): IReadinessProblem[];
18
19
  install(app: IFonderieApp): void;
19
20
  }
20
21
 
@@ -99,4 +100,24 @@ declare namespace schemas {
99
100
  declare function normalizeEmail(email: string): string;
100
101
  declare function normalizeEmailSafe(email: string): string | null;
101
102
 
102
- export { AuthModule, type ChangePasswordInput, IAuthConfig, type LoginInput, type RegisterInput, type ResetPasswordInput, normalizeEmail, normalizeEmailSafe, schemas };
103
+ interface IImportUser {
104
+ id?: string;
105
+ email: string;
106
+ passwordHash?: string | null;
107
+ firstName?: string | null;
108
+ lastName?: string | null;
109
+ phone?: string | null;
110
+ profileImageUrl?: string | null;
111
+ locale?: string;
112
+ timezone?: string;
113
+ emailVerifiedAt?: Date | null;
114
+ mfaEnabled?: boolean;
115
+ createdAt?: Date;
116
+ }
117
+ declare function importUser(store: IStoreAdapter, user: IImportUser): Promise<{
118
+ id: string;
119
+ }>;
120
+
121
+ declare function validateAuthConfig(config: IAuthConfig): void;
122
+
123
+ export { AuthModule, type ChangePasswordInput, IAuthConfig, type IImportUser, type LoginInput, type RegisterInput, type ResetPasswordInput, importUser, normalizeEmail, normalizeEmailSafe, schemas, validateAuthConfig };
package/dist/index.js CHANGED
@@ -12,7 +12,8 @@ var __export = (target, all) => {
12
12
  var password_exports = {};
13
13
  __export(password_exports, {
14
14
  hashPassword: () => hashPassword,
15
- verifyPassword: () => verifyPassword
15
+ verifyPassword: () => verifyPassword,
16
+ verifyPasswordForLogin: () => verifyPasswordForLogin
16
17
  });
17
18
  import bcrypt from "bcryptjs";
18
19
  async function hashPassword(plain) {
@@ -21,6 +22,19 @@ async function hashPassword(plain) {
21
22
  async function verifyPassword(plain, hash) {
22
23
  return bcrypt.compare(plain, hash);
23
24
  }
25
+ async function verifyPasswordForLogin(plain, hash, legacyVerify) {
26
+ if (await verifyPassword(plain, hash)) return { valid: true, needsRehash: false };
27
+ if (legacyVerify) {
28
+ let legacyOk = false;
29
+ try {
30
+ legacyOk = await legacyVerify(plain, hash);
31
+ } catch {
32
+ legacyOk = false;
33
+ }
34
+ if (legacyOk) return { valid: true, needsRehash: true };
35
+ }
36
+ return { valid: false, needsRehash: false };
37
+ }
24
38
  var ROUNDS;
25
39
  var init_password = __esm({
26
40
  "src/services/password.ts"() {
@@ -1205,10 +1219,17 @@ function authController(store, config, bus) {
1205
1219
  if (!user || !user.passwordHash) {
1206
1220
  return setApiResponse3(HTTP3.UNAUTHORIZED, "INVALID_CREDENTIALS", "Invalid credentials");
1207
1221
  }
1208
- const valid = await verifyPassword(password2, user.passwordHash);
1222
+ const { valid, needsRehash } = await verifyPasswordForLogin(
1223
+ password2,
1224
+ user.passwordHash,
1225
+ config.legacyVerify
1226
+ );
1209
1227
  if (!valid) {
1210
1228
  return setApiResponse3(HTTP3.UNAUTHORIZED, "INVALID_CREDENTIALS", "Invalid credentials");
1211
1229
  }
1230
+ if (needsRehash) {
1231
+ await users.updatePassword(user.id, await hashPassword(password2));
1232
+ }
1212
1233
  if (user.suspended) {
1213
1234
  return setApiResponse3(
1214
1235
  HTTP3.FORBIDDEN,
@@ -1963,6 +1984,53 @@ function buildAuthRoutes(store, config, bus) {
1963
1984
  return routes;
1964
1985
  }
1965
1986
 
1987
+ // src/services/config-guard.ts
1988
+ var MODULE = "@fonderie/auth";
1989
+ var PLACEHOLDER_SECRET = /dev-secret|test-secret|changeme|change-me|your[-_]secret|placeholder|example|insecure|min-32-chars/i;
1990
+ var MIN_SECRET_LENGTH = 32;
1991
+ function collectAuthConfigProblems(config) {
1992
+ const problems = [];
1993
+ const secret = config.jwtSecret ?? "";
1994
+ if (secret.length < MIN_SECRET_LENGTH) {
1995
+ problems.push({
1996
+ module: MODULE,
1997
+ severity: "error",
1998
+ message: `jwtSecret must be at least ${MIN_SECRET_LENGTH} characters (got ${secret.length})`
1999
+ });
2000
+ } else if (PLACEHOLDER_SECRET.test(secret)) {
2001
+ problems.push({
2002
+ module: MODULE,
2003
+ severity: "error",
2004
+ message: "jwtSecret looks like a placeholder or dev-default value"
2005
+ });
2006
+ }
2007
+ if (config.secureCookies === false) {
2008
+ problems.push({
2009
+ module: MODULE,
2010
+ severity: "warning",
2011
+ message: "secureCookies is false \u2014 auth cookies may be sent over non-HTTPS connections in production"
2012
+ });
2013
+ }
2014
+ return problems;
2015
+ }
2016
+ function validateAuthConfig(config) {
2017
+ const isProduction = process.env["NODE_ENV"] === "production";
2018
+ const problems = collectAuthConfigProblems(config);
2019
+ const errors = problems.filter((p) => p.severity === "error");
2020
+ if (isProduction && errors.length > 0) {
2021
+ throw new Error(
2022
+ `[auth] insecure config \u2014 ${errors.map((e) => e.message).join("; ")}. Set a long, random jwtSecret (e.g. \`openssl rand -base64 32\`). Refusing to boot in production.`
2023
+ );
2024
+ }
2025
+ if (isProduction) {
2026
+ for (const p of problems) console.warn(`[auth] ${p.message}`);
2027
+ } else {
2028
+ for (const p of errors) {
2029
+ console.warn(`[auth] ${p.message} (insecure \u2014 would refuse to boot in production)`);
2030
+ }
2031
+ }
2032
+ }
2033
+
1966
2034
  // src/middlewares/session.ts
1967
2035
  function withSession(store, config) {
1968
2036
  const users = new UserModel(store);
@@ -2010,11 +2078,16 @@ var AuthModule = class {
2010
2078
  this.store = store;
2011
2079
  this.config = config;
2012
2080
  this.bus = bus;
2081
+ validateAuthConfig(config);
2013
2082
  }
2014
2083
  store;
2015
2084
  config;
2016
2085
  bus;
2017
2086
  name = "@fonderie/auth";
2087
+ // Report config problems for app.checkProductionReadiness() (data, not throw).
2088
+ checkReadiness() {
2089
+ return collectAuthConfigProblems(this.config);
2090
+ }
2018
2091
  install(app) {
2019
2092
  app.use(withSession(this.store, this.config));
2020
2093
  const routes = buildAuthRoutes(this.store, this.config, this.bus);
@@ -2026,18 +2099,52 @@ var AuthModule = class {
2026
2099
 
2027
2100
  // src/middlewares/require-auth.ts
2028
2101
  import { requireAuth as requireAuth2, requireAnyAuth as requireAnyAuth2 } from "@fonderie/core/middlewares";
2102
+
2103
+ // src/migrate.ts
2104
+ async function importUser(store, user) {
2105
+ const cols = [];
2106
+ const vals = [];
2107
+ const placeholders = [];
2108
+ const add = (col, val) => {
2109
+ cols.push(col);
2110
+ vals.push(val);
2111
+ placeholders.push(`$${vals.length}`);
2112
+ };
2113
+ if (user.id !== void 0) add("id", user.id);
2114
+ add("email", user.email.toLowerCase().trim());
2115
+ if (user.passwordHash !== void 0) add("password_hash", user.passwordHash);
2116
+ if (user.firstName !== void 0) add("first_name", user.firstName);
2117
+ if (user.lastName !== void 0) add("last_name", user.lastName);
2118
+ if (user.phone !== void 0) add("phone", user.phone);
2119
+ if (user.profileImageUrl !== void 0) add("profile_image_url", user.profileImageUrl);
2120
+ if (user.locale !== void 0) add("locale", user.locale);
2121
+ if (user.timezone !== void 0) add("timezone", user.timezone);
2122
+ if (user.emailVerifiedAt !== void 0) add("email_verified_at", user.emailVerifiedAt);
2123
+ if (user.mfaEnabled !== void 0) add("mfa_enabled", user.mfaEnabled);
2124
+ if (user.createdAt !== void 0) add("created_at", user.createdAt);
2125
+ const [row] = await store.query(
2126
+ `INSERT INTO fonderie_users (${cols.join(", ")})
2127
+ VALUES (${placeholders.join(", ")})
2128
+ RETURNING id`,
2129
+ vals
2130
+ );
2131
+ if (!row) throw new Error("[auth] importUser: insert returned no row");
2132
+ return row;
2133
+ }
2029
2134
  export {
2030
2135
  AUTH_CONFIG_KEYS,
2031
2136
  AuthModule,
2032
2137
  MESSAGE_KEYS,
2033
2138
  buildAuthAccountLimiter,
2034
2139
  buildAuthIpLimiter,
2140
+ importUser,
2035
2141
  normalizeEmail,
2036
2142
  normalizeEmailSafe,
2037
2143
  requireAuth2 as requireAuth,
2038
2144
  schemas_exports as schemas,
2039
2145
  toUserDTO,
2040
2146
  validate,
2147
+ validateAuthConfig,
2041
2148
  withSession
2042
2149
  };
2043
2150
  //# sourceMappingURL=index.js.map