@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 +2 -8
- package/src/auth.ts +17 -31
- package/src/config.ts +8 -15
- package/src/schema/auth-schema-sqlite.ts +12 -6
- package/src/schema/meta-schema-sqlite.ts +2 -4
- package/src/signup.test.ts +2 -3
- package/src/verifier.ts +6 -6
- package/drizzle.config.ts +0 -24
- package/src/auth.gen.ts +0 -35
- package/src/schema/auth-schema.ts +0 -101
- package/src/schema/meta-schema.ts +0 -12
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@remit/auth-service",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"description": "better-auth identity service for the
|
|
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
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
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
|
|
31
|
-
//
|
|
32
|
-
//
|
|
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
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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
|
|
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
|
|
38
|
-
*
|
|
39
|
-
*
|
|
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
|
-
|
|
44
|
-
|
|
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
|
|
54
|
-
*
|
|
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
|
-
//
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
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
|
-
//
|
|
2
|
-
//
|
|
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
|
|
package/src/signup.test.ts
CHANGED
|
@@ -29,7 +29,7 @@ describe("resolveSelfSignUpEnabled", () => {
|
|
|
29
29
|
});
|
|
30
30
|
|
|
31
31
|
const baseConfig = {
|
|
32
|
-
connectionString: "
|
|
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("
|
|
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
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
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
|
-
);
|