@remit/auth-service 0.0.10 → 0.0.11

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@remit/auth-service",
3
- "version": "0.0.10",
4
- "description": "better-auth identity service for the Postgres-parity stack (pg-parity)",
3
+ "version": "0.0.11",
4
+ "description": "better-auth identity service for the self-host stack",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
7
7
  "types": "src/index.ts",
@@ -23,14 +23,9 @@
23
23
  "test:typecheck": "tsgo --noEmit -p tsconfig.json",
24
24
  "test:run": "node --import tsx --experimental-test-coverage --test-coverage-include='src/**' --test-coverage-exclude='src/**/*.test.ts' --test-coverage-exclude='src/**/*.test.tsx' --test-coverage-lines=94 --test 'src/**/*.test.ts'",
25
25
  "test": "npm run test:typecheck && npm run test:run",
26
- "auth:schema:generate": "npx @better-auth/cli generate --config src/auth.gen.ts --output src/schema/auth-schema.ts -y",
27
- "auth:schema:push": "npx drizzle-kit push",
28
26
  "fix": "biome check --write src"
29
27
  },
30
28
  "devDependencies": {
31
- "@better-auth/cli": "^1.4.21",
32
- "drizzle-kit": "^0.31.1",
33
- "embedded-postgres": "^18.4.0-beta.17",
34
29
  "tsx": "*"
35
30
  },
36
31
  "dependencies": {
@@ -38,7 +33,6 @@
38
33
  "better-sqlite3": "^12.11.1",
39
34
  "drizzle-orm": "^0.45.2",
40
35
  "jose": "^6.2.3",
41
- "pg": "^8.0.0",
42
36
  "@types/better-sqlite3": "^7.6.13"
43
37
  },
44
38
  "license": "MIT",
package/src/auth.ts CHANGED
@@ -2,19 +2,14 @@ import type { Auth as BetterAuthInstance } from "better-auth";
2
2
  import { betterAuth } from "better-auth";
3
3
  import { drizzleAdapter } from "better-auth/adapters/drizzle";
4
4
  import { jwt } from "better-auth/plugins";
5
- import { drizzle as drizzlePg } from "drizzle-orm/node-postgres";
6
- import pg from "pg";
7
- import * as authSchema from "./schema/auth-schema.js";
8
5
  import * as authSchemaSqlite from "./schema/auth-schema-sqlite.js";
9
6
 
10
7
  export interface AuthConfig {
11
8
  /**
12
- * The relational backend better-auth runs on, off `DATA_BACKEND` (RFC 036).
13
- * `pg` (the default) reads `connectionString` as a Postgres URL; `sqlite`
14
- * reads it as the path to the shared database file (D3), and drives the
15
- * SQLite identity schema through the drizzle adapter's `sqlite` provider.
9
+ * Path to the shared database file (RFC 036 D3). The identity tables live
10
+ * beside the app tables and are driven through the drizzle adapter's
11
+ * `sqlite` provider.
16
12
  */
17
- provider?: "pg" | "sqlite";
18
13
  connectionString: string;
19
14
  secret: string;
20
15
  baseURL: string;
@@ -27,31 +22,22 @@ export interface AuthConfig {
27
22
  selfSignUpEnabled: boolean;
28
23
  }
29
24
 
30
- // The drizzle adapter better-auth runs against, chosen by backend. better-sqlite3
31
- // and its drizzle driver are imported dynamically so the Postgres path never
32
- // loads the native binding, and the module stays out of any bundle that never
33
- // runs on SQLite. On SQLite the auth connection sets the same WAL/busy_timeout
25
+ // The drizzle adapter better-auth runs against. better-sqlite3 and its drizzle
26
+ // driver are imported dynamically so the module stays out of any bundle that
27
+ // never opens the database. The auth connection sets the same WAL/busy_timeout
34
28
  // coordination every writer uses (RFC 036 D3) — it shares the file with the app
35
29
  // tables through its own connection.
36
30
  const buildAuthAdapter = async (config: AuthConfig) => {
37
- if (config.provider === "sqlite") {
38
- const { default: Database } = await import("better-sqlite3");
39
- const { drizzle } = await import("drizzle-orm/better-sqlite3");
40
- const sqlite = new Database(config.connectionString);
41
- sqlite.pragma("journal_mode = WAL");
42
- sqlite.pragma("busy_timeout = 5000");
43
- sqlite.pragma("synchronous = NORMAL");
44
- sqlite.pragma("foreign_keys = ON");
45
- return drizzleAdapter(drizzle(sqlite, { schema: authSchemaSqlite }), {
46
- provider: "sqlite",
47
- schema: authSchemaSqlite,
48
- });
49
- }
50
-
51
- const pool = new pg.Pool({ connectionString: config.connectionString });
52
- return drizzleAdapter(drizzlePg(pool, { schema: authSchema }), {
53
- provider: "pg",
54
- schema: authSchema,
31
+ const { default: Database } = await import("better-sqlite3");
32
+ const { drizzle } = await import("drizzle-orm/better-sqlite3");
33
+ const sqlite = new Database(config.connectionString);
34
+ sqlite.pragma("journal_mode = WAL");
35
+ sqlite.pragma("busy_timeout = 5000");
36
+ sqlite.pragma("synchronous = NORMAL");
37
+ sqlite.pragma("foreign_keys = ON");
38
+ return drizzleAdapter(drizzle(sqlite, { schema: authSchemaSqlite }), {
39
+ provider: "sqlite",
40
+ schema: authSchemaSqlite,
55
41
  });
56
42
  };
57
43
 
@@ -63,7 +49,7 @@ const intEnv = (name: string, fallback: number): number => {
63
49
  };
64
50
 
65
51
  /**
66
- * Build a better-auth instance bound to the pg-parity Postgres.
52
+ * Build a better-auth instance bound to the self-host database file.
67
53
  *
68
54
  * RS256 is deliberate: the edge tier (APISIX, or the dev-server verifier)
69
55
  * follows JWKS key rotation offline, so signing must be asymmetric. The JWKS is
package/src/config.ts CHANGED
@@ -29,29 +29,22 @@ export const resolveSelfSignUpEnabled = (
29
29
  };
30
30
 
31
31
  export interface DataConnectionConfig {
32
- provider: "pg" | "sqlite";
33
32
  connectionString: string;
34
33
  }
35
34
 
36
35
  /**
37
- * Resolve which relational backend identity data lives on, from the same env
38
- * vars the rest of the self-host stack reads (RFC 036 D5). On
39
- * `DATA_BACKEND=sqlite` the identity tables share the app database file, so
40
- * the locator is `SQLITE_DB_PATH`; otherwise it is the Postgres URL.
36
+ * Resolve where identity data lives, from the same env vars the rest of the
37
+ * self-host stack reads (RFC 036 D5). The identity tables share the app
38
+ * database file, so the locator is `SQLITE_DB_PATH`.
41
39
  */
42
- export const resolveDataConnectionConfig = (): DataConnectionConfig => {
43
- const provider = process.env.DATA_BACKEND === "sqlite" ? "sqlite" : "pg";
44
- const connectionString =
45
- provider === "sqlite"
46
- ? required("SQLITE_DB_PATH", process.env.SQLITE_DB_PATH)
47
- : required("PG_CONNECTION_URL", process.env.PG_CONNECTION_URL);
48
- return { provider, connectionString };
49
- };
40
+ export const resolveDataConnectionConfig = (): DataConnectionConfig => ({
41
+ connectionString: required("SQLITE_DB_PATH", process.env.SQLITE_DB_PATH),
42
+ });
50
43
 
51
44
  /**
52
45
  * Resolve the better-auth config from the environment. Callers reach this only
53
- * on the self-host relational backends (Postgres or SQLite); the Cognito/AWS
54
- * path never touches better-auth.
46
+ * on the self-host relational backend; the Cognito/AWS path never touches
47
+ * better-auth.
55
48
  */
56
49
  export const resolveAuthConfig = (): AuthConfig => {
57
50
  const baseURL = required("BETTER_AUTH_URL", process.env.BETTER_AUTH_URL);
@@ -1,9 +1,15 @@
1
- // SQLite twin of auth-schema.ts (RFC 036 D5). Same five better-auth identity
2
- // tables in the SQLite dialect — the shape better-auth's drizzle adapter emits
3
- // with `provider: "sqlite"`: `sqliteTable`, `text` unchanged, `boolean` and
4
- // `timestamp` mapped to `integer` (mode boolean / timestamp). Used only to
5
- // generate the committed sqlite migrations (deploy/vps/migrations-sqlite/auth);
6
- // the Postgres schema stays the source for the pg migrations.
1
+ // The five better-auth identity tables, in the shape better-auth's drizzle
2
+ // adapter emits with `provider: "sqlite"`. Used to generate the committed
3
+ // sqlite migrations (deploy/vps/migrations-sqlite/auth).
4
+ //
5
+ // Hand-maintained, deliberately. The `@better-auth/cli generate` command no
6
+ // longer emits this shape: it writes `mode: "timestamp_ms"` where these columns
7
+ // store seconds, and a SQL `DEFAULT (cast(unixepoch('subsecond') * 1000 as
8
+ // integer))` where these use a JS-side `$defaultFn` and the committed migration
9
+ // has no DEFAULT clause. Taking its output would reinterpret every timestamp on
10
+ // every deployed instance and rewrite all five tables. Match better-auth's
11
+ // runtime expectations by hand when a version bump changes them, and let
12
+ // vps-migrations-drift.sqlite.test.ts catch the migration side.
7
13
  import { relations } from "drizzle-orm";
8
14
  import { index, integer, sqliteTable, text } from "drizzle-orm/sqlite-core";
9
15
 
@@ -1,7 +1,5 @@
1
- // SQLite twin of meta-schema.ts. Same shape in the SQLite dialect — used only
2
- // to generate the committed SQLite migrations
3
- // (deploy/vps/migrations-sqlite/meta); the Postgres schema stays the source
4
- // for the pg migrations.
1
+ // The instance-owner singleton, used to generate the committed SQLite
2
+ // migrations (deploy/vps/migrations-sqlite/meta).
5
3
  import { sql } from "drizzle-orm";
6
4
  import { check, integer, sqliteTable, text } from "drizzle-orm/sqlite-core";
7
5
 
@@ -29,7 +29,7 @@ describe("resolveSelfSignUpEnabled", () => {
29
29
  });
30
30
 
31
31
  const baseConfig = {
32
- connectionString: "postgresql://remit:remit@localhost:5432/remit_test",
32
+ connectionString: ":memory:",
33
33
  secret: "signup-test-secret-value-32chars-minimum",
34
34
  baseURL: "http://localhost:3000",
35
35
  };
@@ -45,9 +45,8 @@ describe("createAuth self-signup gate", () => {
45
45
  assert.equal(closed.options.emailAndPassword?.disableSignUp, true);
46
46
  });
47
47
 
48
- it("selects the sqlite drizzle provider when configured", async () => {
48
+ it("drives the sqlite drizzle provider", async () => {
49
49
  const auth = await createAuth({
50
- provider: "sqlite",
51
50
  connectionString: ":memory:",
52
51
  secret: baseConfig.secret,
53
52
  baseURL: baseConfig.baseURL,
package/src/verifier.ts CHANGED
@@ -1,10 +1,10 @@
1
1
  // JWT-verification-only slice of this package's barrel. The full barrel
2
- // (`index.ts`) re-exports `createAuth` from `./auth.js`, which constructs a
3
- // `pg.Pool` + `drizzle-orm/node-postgres` at module scope, and `toNodeHandler`
4
- // from `better-auth/node` — neither needed to verify a token, both dragging a
5
- // Postgres client into any Lambda bundle that only checks a bearer token (see
6
- // #1242's FAQ, #1244/#1247). `config.ts`'s `AuthConfig` import from `./auth.js`
7
- // is type-only and erases at build, so this stays free of that chain.
2
+ // (`index.ts`) re-exports `createAuth` from `./auth.js` and `toNodeHandler`
3
+ // from `better-auth/node` — neither needed to verify a token, both dragging the
4
+ // whole better-auth server graph into any Lambda bundle that only checks a
5
+ // bearer token (see #1242's FAQ, #1244/#1247). `config.ts`'s `AuthConfig`
6
+ // import from `./auth.js` is type-only and erases at build, so this stays free
7
+ // of that chain.
8
8
 
9
9
  export {
10
10
  AUTH_BASE_PATH,
package/drizzle.config.ts DELETED
@@ -1,24 +0,0 @@
1
- const url =
2
- process.env.DATABASE_URL ??
3
- process.env.PG_CONNECTION_URL ??
4
- "postgresql://remit:remit@localhost:5432/remit_dev";
5
-
6
- /**
7
- * The better-auth identity tables share the pg-parity database with the entity
8
- * schema (remit-drizzle-service). `tablesFilter` scopes drizzle-kit to only the
9
- * auth tables so a push here never proposes dropping the entity tables it does
10
- * not know about.
11
- */
12
- export default {
13
- dialect: "postgresql",
14
- schema: "./src/schema/auth-schema.ts",
15
- out: "./.drizzle",
16
- dbCredentials: { url },
17
- tablesFilter: [
18
- "auth_user",
19
- "auth_session",
20
- "auth_account",
21
- "auth_verification",
22
- "auth_jwks",
23
- ],
24
- };
package/src/auth.gen.ts DELETED
@@ -1,35 +0,0 @@
1
- import type { Auth as BetterAuthInstance } from "better-auth";
2
- import { betterAuth } from "better-auth";
3
- import { drizzleAdapter } from "better-auth/adapters/drizzle";
4
- import { jwt } from "better-auth/plugins";
5
- import { drizzle } from "drizzle-orm/node-postgres";
6
- import pg from "pg";
7
-
8
- /**
9
- * Schema-generation-only config for `@better-auth/cli generate`. It mirrors the
10
- * plugin set of the real `createAuth` so the emitted Drizzle schema matches what
11
- * runtime expects, but omits the schema import to break the generate-time
12
- * chicken-and-egg (the schema file is this command's output).
13
- */
14
- const pool = new pg.Pool({
15
- connectionString:
16
- process.env.PG_CONNECTION_URL ??
17
- "postgresql://remit:remit@localhost:5432/remit_dev",
18
- });
19
-
20
- export const auth = betterAuth({
21
- secret: "schema-generation-only",
22
- baseURL: "http://localhost",
23
- database: drizzleAdapter(drizzle(pool), { provider: "pg" }),
24
- emailAndPassword: { enabled: true },
25
- user: { modelName: "auth_user" },
26
- session: { modelName: "auth_session" },
27
- account: { modelName: "auth_account" },
28
- verification: { modelName: "auth_verification" },
29
- plugins: [
30
- jwt({
31
- schema: { jwks: { modelName: "auth_jwks" } },
32
- jwks: { keyPairConfig: { alg: "RS256", modulusLength: 2048 } },
33
- }),
34
- ],
35
- }) as unknown as BetterAuthInstance;
@@ -1,101 +0,0 @@
1
- import { relations } from "drizzle-orm";
2
- import { boolean, index, pgTable, text, timestamp } from "drizzle-orm/pg-core";
3
-
4
- export const auth_user = pgTable("auth_user", {
5
- id: text("id").primaryKey(),
6
- name: text("name").notNull(),
7
- email: text("email").notNull().unique(),
8
- emailVerified: boolean("email_verified").default(false).notNull(),
9
- image: text("image"),
10
- createdAt: timestamp("created_at").defaultNow().notNull(),
11
- updatedAt: timestamp("updated_at")
12
- .defaultNow()
13
- .$onUpdate(() => /* @__PURE__ */ new Date())
14
- .notNull(),
15
- });
16
-
17
- export const auth_session = pgTable(
18
- "auth_session",
19
- {
20
- id: text("id").primaryKey(),
21
- expiresAt: timestamp("expires_at").notNull(),
22
- token: text("token").notNull().unique(),
23
- createdAt: timestamp("created_at").defaultNow().notNull(),
24
- updatedAt: timestamp("updated_at")
25
- .$onUpdate(() => /* @__PURE__ */ new Date())
26
- .notNull(),
27
- ipAddress: text("ip_address"),
28
- userAgent: text("user_agent"),
29
- userId: text("user_id")
30
- .notNull()
31
- .references(() => auth_user.id, { onDelete: "cascade" }),
32
- },
33
- (table) => [index("auth_session_userId_idx").on(table.userId)],
34
- );
35
-
36
- export const auth_account = pgTable(
37
- "auth_account",
38
- {
39
- id: text("id").primaryKey(),
40
- accountId: text("account_id").notNull(),
41
- providerId: text("provider_id").notNull(),
42
- userId: text("user_id")
43
- .notNull()
44
- .references(() => auth_user.id, { onDelete: "cascade" }),
45
- accessToken: text("access_token"),
46
- refreshToken: text("refresh_token"),
47
- idToken: text("id_token"),
48
- accessTokenExpiresAt: timestamp("access_token_expires_at"),
49
- refreshTokenExpiresAt: timestamp("refresh_token_expires_at"),
50
- scope: text("scope"),
51
- password: text("password"),
52
- createdAt: timestamp("created_at").defaultNow().notNull(),
53
- updatedAt: timestamp("updated_at")
54
- .$onUpdate(() => /* @__PURE__ */ new Date())
55
- .notNull(),
56
- },
57
- (table) => [index("auth_account_userId_idx").on(table.userId)],
58
- );
59
-
60
- export const auth_verification = pgTable(
61
- "auth_verification",
62
- {
63
- id: text("id").primaryKey(),
64
- identifier: text("identifier").notNull(),
65
- value: text("value").notNull(),
66
- expiresAt: timestamp("expires_at").notNull(),
67
- createdAt: timestamp("created_at").defaultNow().notNull(),
68
- updatedAt: timestamp("updated_at")
69
- .defaultNow()
70
- .$onUpdate(() => /* @__PURE__ */ new Date())
71
- .notNull(),
72
- },
73
- (table) => [index("auth_verification_identifier_idx").on(table.identifier)],
74
- );
75
-
76
- export const auth_jwks = pgTable("auth_jwks", {
77
- id: text("id").primaryKey(),
78
- publicKey: text("public_key").notNull(),
79
- privateKey: text("private_key").notNull(),
80
- createdAt: timestamp("created_at").notNull(),
81
- expiresAt: timestamp("expires_at"),
82
- });
83
-
84
- export const auth_userRelations = relations(auth_user, ({ many }) => ({
85
- auth_sessions: many(auth_session),
86
- auth_accounts: many(auth_account),
87
- }));
88
-
89
- export const auth_sessionRelations = relations(auth_session, ({ one }) => ({
90
- auth_user: one(auth_user, {
91
- fields: [auth_session.userId],
92
- references: [auth_user.id],
93
- }),
94
- }));
95
-
96
- export const auth_accountRelations = relations(auth_account, ({ one }) => ({
97
- auth_user: one(auth_user, {
98
- fields: [auth_account.userId],
99
- references: [auth_user.id],
100
- }),
101
- }));
@@ -1,12 +0,0 @@
1
- import { sql } from "drizzle-orm";
2
- import { check, integer, pgTable, text, timestamp } from "drizzle-orm/pg-core";
3
-
4
- export const instance_owner = pgTable(
5
- "instance_owner",
6
- {
7
- id: integer("id").primaryKey().default(1),
8
- userId: text("user_id").notNull(),
9
- claimedAt: timestamp("claimed_at").defaultNow().notNull(),
10
- },
11
- (table) => [check("instance_owner_singleton", sql`${table.id} = 1`)],
12
- );