@farm.js/auth 0.1.0-beta.10 → 0.1.0-beta.101

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/config.js CHANGED
@@ -48,11 +48,47 @@ export function resolveFarmAuthConfig(input) {
48
48
  return resolved;
49
49
  }
50
50
  function normalizeBasePath(value) {
51
- const path = (value || "/api/auth").trim();
52
- const withLeadingSlash = path.startsWith("/") ? path : `/${path}`;
51
+ const route = (value || "/api/auth").trim();
52
+ if (!route)
53
+ return "/api/auth";
54
+ assertStableBasePath(route);
55
+ const withLeadingSlash = route.startsWith("/") ? route : `/${route}`;
53
56
  const normalized = withLeadingSlash.replace(/\/+/g, "/").replace(/\/+$/, "");
54
57
  return normalized || "/api/auth";
55
58
  }
59
+ function assertStableBasePath(route) {
60
+ if (route.includes("?") || route.includes("#")) {
61
+ throw new Error("auth.basePath cannot contain a query string or fragment.");
62
+ }
63
+ if (route.startsWith("//") || /^[a-z][a-z\d+.-]*:\/\//i.test(route)) {
64
+ throw new Error('auth.basePath must be an application pathname such as "/api/auth".');
65
+ }
66
+ if (hasUnstablePathCharacters(route)) {
67
+ throw new Error("auth.basePath cannot contain backslashes or control characters.");
68
+ }
69
+ for (const segment of route.split("/")) {
70
+ let decoded = segment;
71
+ try {
72
+ decoded = decodeURIComponent(segment);
73
+ }
74
+ catch {
75
+ // Malformed escapes remain literal URL pathname segments.
76
+ }
77
+ if (hasUnstablePathCharacters(decoded) || decoded.includes("/")) {
78
+ throw new Error("auth.basePath cannot contain encoded path separators.");
79
+ }
80
+ if (decoded === "." || decoded === "..") {
81
+ throw new Error('auth.basePath cannot contain "." or ".." path segments.');
82
+ }
83
+ }
84
+ }
85
+ function hasUnstablePathCharacters(value) {
86
+ return (value.includes("\\") ||
87
+ Array.from(value).some((character) => {
88
+ const code = character.charCodeAt(0);
89
+ return code <= 31 || (code >= 127 && code <= 159);
90
+ }));
91
+ }
56
92
  function validateFarmAuthConfig(config) {
57
93
  const { minPasswordLength, maxPasswordLength } = config.emailAndPassword;
58
94
  if (!Number.isInteger(minPasswordLength) || minPasswordLength < 1) {
@@ -18,5 +18,6 @@ interface FarmAuthIntegration {
18
18
  }
19
19
  export declare function createFarmAuthIntegration(config: ResolvedFarmAuthConfig, options: FarmAuthIntegrationOptions): FarmAuthIntegration;
20
20
  export declare function migrateFarmAuth(): Promise<void>;
21
+ export declare function disposeFarmAuth(): Promise<void>;
21
22
  export {};
22
23
  //# sourceMappingURL=internal.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"internal.d.ts","sourceRoot":"","sources":["../src/internal.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAOzD,UAAU,0BAA0B;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,aAAa,GAAG,YAAY,CAAC;CACpC;AAED,UAAU,mBAAmB;IAC3B,QAAQ,CAAC,IAAI,EAAE,kBAAkB,CAAC;IAClC,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,WAAW,CAAC;IAClB,QAAQ,EAAE;QAAE,QAAQ,CAAC,IAAI,EAAE,mBAAmB,CAAA;KAAE,CAAC;IACjD,MAAM,EAAE,KAAK,CAAC;QACZ,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,SAAS,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;QAClC,OAAO,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;KAC9C,CAAC,CAAC;CACJ;AAED,wBAAgB,yBAAyB,CACvC,MAAM,EAAE,sBAAsB,EAC9B,OAAO,EAAE,0BAA0B,GAClC,mBAAmB,CAmBrB;AAED,wBAAsB,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC,CAErD"}
1
+ {"version":3,"file":"internal.d.ts","sourceRoot":"","sources":["../src/internal.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAQzD,UAAU,0BAA0B;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,aAAa,GAAG,YAAY,CAAC;CACpC;AAED,UAAU,mBAAmB;IAC3B,QAAQ,CAAC,IAAI,EAAE,kBAAkB,CAAC;IAClC,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,WAAW,CAAC;IAClB,QAAQ,EAAE;QAAE,QAAQ,CAAC,IAAI,EAAE,mBAAmB,CAAA;KAAE,CAAC;IACjD,MAAM,EAAE,KAAK,CAAC;QACZ,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,SAAS,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;QAClC,OAAO,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;KAC9C,CAAC,CAAC;CACJ;AAED,wBAAgB,yBAAyB,CACvC,MAAM,EAAE,sBAAsB,EAC9B,OAAO,EAAE,0BAA0B,GAClC,mBAAmB,CAmBrB;AAED,wBAAsB,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC,CAErD;AAED,wBAAsB,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC,CAErD"}
package/dist/internal.js CHANGED
@@ -1,4 +1,4 @@
1
- import { configureFarmAuth, getFarmAuthRuntime, migrateFarmAuth as runMigration, } from "./runtime.js";
1
+ import { configureFarmAuth, disposeFarmAuth as runDispose, getFarmAuthRuntime, migrateFarmAuth as runMigration, } from "./runtime.js";
2
2
  export function createFarmAuthIntegration(config, options) {
3
3
  configureFarmAuth(config, options);
4
4
  return {
@@ -21,3 +21,6 @@ export function createFarmAuthIntegration(config, options) {
21
21
  export async function migrateFarmAuth() {
22
22
  await runMigration();
23
23
  }
24
+ export async function disposeFarmAuth() {
25
+ await runDispose();
26
+ }
package/dist/runtime.d.ts CHANGED
@@ -14,6 +14,14 @@ interface BetterAuthRuntime {
14
14
  export declare function configureFarmAuth(config: ResolvedFarmAuthConfig, options: FarmAuthRuntimeOptions): void;
15
15
  export declare function getFarmAuthRuntime(): Promise<BetterAuthRuntime>;
16
16
  export declare function getFarmAuthRequest(request?: Request): Request;
17
+ /**
18
+ * Close the connection opened for the runtime and drop the cached instance.
19
+ *
20
+ * The runtime is cached on globalThis and its connection stays open for the life
21
+ * of the process, which leaves a file lock behind on SQLite and a pool open on
22
+ * every other driver. Call this from a teardown hook.
23
+ */
24
+ export declare function disposeFarmAuth(): Promise<void>;
17
25
  export declare function migrateFarmAuth(): Promise<void>;
18
26
  export {};
19
27
  //# sourceMappingURL=runtime.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAMzD,UAAU,sBAAsB;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,aAAa,GAAG,YAAY,CAAC;CACpC;AAED,UAAU,iBAAiB;IACzB,OAAO,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC7C,GAAG,EAAE;QACH,UAAU,CAAC,KAAK,EAAE;YAAE,OAAO,EAAE,OAAO,CAAA;SAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;KAC3D,CAAC;CACH;AAmBD,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,sBAAsB,EAC9B,OAAO,EAAE,sBAAsB,GAC9B,IAAI,CAYN;AAED,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,iBAAiB,CAAC,CAUrE;AAED,wBAAgB,kBAAkB,CAAC,OAAO,CAAC,EAAE,OAAO,GAAG,OAAO,CAU7D;AAED,wBAAsB,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC,CAgBrD"}
1
+ {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAMzD,UAAU,sBAAsB;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,aAAa,GAAG,YAAY,CAAC;CACpC;AAED,UAAU,iBAAiB;IACzB,OAAO,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC7C,GAAG,EAAE;QACH,UAAU,CAAC,KAAK,EAAE;YAAE,OAAO,EAAE,OAAO,CAAA;SAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;KAC3D,CAAC;CACH;AAqBD,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,sBAAsB,EAC9B,OAAO,EAAE,sBAAsB,GAC9B,IAAI,CAeN;AAED,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,iBAAiB,CAAC,CA6BrE;AAED,wBAAgB,kBAAkB,CAAC,OAAO,CAAC,EAAE,OAAO,GAAG,OAAO,CAU7D;AAED;;;;;;GAMG;AACH,wBAAsB,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC,CASrD;AAED,wBAAsB,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC,CAgBrD"}
package/dist/runtime.js CHANGED
@@ -17,7 +17,11 @@ export function configureFarmAuth(config, options) {
17
17
  state.config = config;
18
18
  state.options = options;
19
19
  if (changed) {
20
+ const previous = state.database;
20
21
  state.runtime = undefined;
22
+ state.database = undefined;
23
+ if (previous)
24
+ void closeDatabase(previous).catch(() => { });
21
25
  }
22
26
  }
23
27
  export async function getFarmAuthRuntime() {
@@ -25,7 +29,28 @@ export async function getFarmAuthRuntime() {
25
29
  if (!state.config?.enabled || !state.options) {
26
30
  throw new Error("Farm Auth is not enabled. Add `auth: true` to farm.config.ts before using @farm.js/auth.");
27
31
  }
28
- state.runtime ||= createRuntime(state.config, state.options);
32
+ if (!state.runtime) {
33
+ const runtime = createRuntime(state.config, state.options, (database) => {
34
+ // A dispose or reconfigure that raced this creation already closed what it
35
+ // knew about, so an orphaned connection closes itself instead of leaking.
36
+ if (state.runtime === runtime) {
37
+ state.database = database;
38
+ }
39
+ else {
40
+ void closeDatabase(database).catch(() => { });
41
+ }
42
+ });
43
+ state.runtime = runtime;
44
+ void runtime.catch(() => {
45
+ if (state.runtime === runtime) {
46
+ state.runtime = undefined;
47
+ const database = state.database;
48
+ state.database = undefined;
49
+ if (database)
50
+ void closeDatabase(database).catch(() => { });
51
+ }
52
+ });
53
+ }
29
54
  return state.runtime;
30
55
  }
31
56
  export function getFarmAuthRequest(request) {
@@ -37,6 +62,24 @@ export function getFarmAuthRequest(request) {
37
62
  }
38
63
  return current;
39
64
  }
65
+ /**
66
+ * Close the connection opened for the runtime and drop the cached instance.
67
+ *
68
+ * The runtime is cached on globalThis and its connection stays open for the life
69
+ * of the process, which leaves a file lock behind on SQLite and a pool open on
70
+ * every other driver. Call this from a teardown hook.
71
+ */
72
+ export async function disposeFarmAuth() {
73
+ const state = getState();
74
+ // Let an in-flight creation finish so its connection is registered and closed
75
+ // here, instead of resolving before the handle even exists.
76
+ if (state.runtime)
77
+ await state.runtime.catch(() => { });
78
+ const database = state.database;
79
+ state.runtime = undefined;
80
+ state.database = undefined;
81
+ await closeDatabase(database);
82
+ }
40
83
  export async function migrateFarmAuth() {
41
84
  const state = getState();
42
85
  if (!state.config?.enabled || !state.options) {
@@ -54,8 +97,9 @@ export async function migrateFarmAuth() {
54
97
  await closeDatabase(options.database);
55
98
  }
56
99
  }
57
- async function createRuntime(config, options) {
100
+ async function createRuntime(config, options, onDatabase) {
58
101
  const authOptions = await createBetterAuthOptions(config, options);
102
+ onDatabase(authOptions.database);
59
103
  if (options.mode === "development" && config.database.migrateInDevelopment) {
60
104
  const { getMigrations } = await import("better-auth/db/migration");
61
105
  const migrations = await getMigrations(authOptions);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@farm.js/auth",
3
- "version": "0.1.0-beta.10",
3
+ "version": "0.1.0-beta.101",
4
4
  "description": "Farm-native authentication with email/password, server helpers, and React hooks",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -50,7 +50,7 @@
50
50
  "@types/react": "^18.2.45",
51
51
  "react": "19.2.8",
52
52
  "typescript": "^5.3.3",
53
- "vitest": "^1.1.0"
53
+ "vitest": "^3.2.7"
54
54
  },
55
55
  "peerDependencies": {
56
56
  "react": "^18.2.0 || ^19.0.0"