@remit/auth-service 0.0.10 → 0.0.12
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 +34 -31
- package/src/config.ts +8 -15
- package/src/index.ts +6 -1
- package/src/rate-limit.test.ts +106 -0
- 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.12",
|
|
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,34 +22,36 @@ 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
|
|
|
44
|
+
/**
|
|
45
|
+
* The header carrying the client address, set by the edge proxy from the
|
|
46
|
+
* connection it terminates (deploy/vps/caddy/routes.caddy). It is deliberately
|
|
47
|
+
* not `X-Forwarded-For`: that header arrives with an appended hop per proxy,
|
|
48
|
+
* which resolves to no single client and collapses rate limiting onto one
|
|
49
|
+
* shared bucket for everybody. Nothing else in the chain writes this name, and
|
|
50
|
+
* the edge replaces any value a client sends, so it is single-valued and
|
|
51
|
+
* trustworthy at the point better-auth reads it.
|
|
52
|
+
*/
|
|
53
|
+
export const CLIENT_IP_HEADER = "x-remit-client-ip";
|
|
54
|
+
|
|
58
55
|
const intEnv = (name: string, fallback: number): number => {
|
|
59
56
|
const raw = process.env[name];
|
|
60
57
|
if (!raw) return fallback;
|
|
@@ -63,7 +60,7 @@ const intEnv = (name: string, fallback: number): number => {
|
|
|
63
60
|
};
|
|
64
61
|
|
|
65
62
|
/**
|
|
66
|
-
* Build a better-auth instance bound to the
|
|
63
|
+
* Build a better-auth instance bound to the self-host database file.
|
|
67
64
|
*
|
|
68
65
|
* RS256 is deliberate: the edge tier (APISIX, or the dev-server verifier)
|
|
69
66
|
* follows JWKS key rotation offline, so signing must be asymmetric. The JWKS is
|
|
@@ -91,6 +88,12 @@ export const createAuth = async (
|
|
|
91
88
|
autoSignIn: true,
|
|
92
89
|
disableSignUp: !config.selfSignUpEnabled,
|
|
93
90
|
},
|
|
91
|
+
// Listing one header, and not better-auth's `x-forwarded-for` default,
|
|
92
|
+
// is what keeps the resolved address out of a caller's reach: an
|
|
93
|
+
// `X-Forwarded-For` a client sends is never read at all.
|
|
94
|
+
advanced: {
|
|
95
|
+
ipAddress: { ipAddressHeaders: [CLIENT_IP_HEADER] },
|
|
96
|
+
},
|
|
94
97
|
rateLimit: {
|
|
95
98
|
enabled: true,
|
|
96
99
|
window: intEnv("BETTER_AUTH_RATE_LIMIT_WINDOW", 60),
|
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);
|
package/src/index.ts
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
export { toNodeHandler } from "better-auth/node";
|
|
2
|
-
export {
|
|
2
|
+
export {
|
|
3
|
+
type Auth,
|
|
4
|
+
type AuthConfig,
|
|
5
|
+
CLIENT_IP_HEADER,
|
|
6
|
+
createAuth,
|
|
7
|
+
} from "./auth.js";
|
|
3
8
|
export {
|
|
4
9
|
AUTH_BASE_PATH,
|
|
5
10
|
AUTH_JWKS_PATH,
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { mkdtempSync, rmSync } from "node:fs";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join, resolve } from "node:path";
|
|
5
|
+
import { after, before, describe, it } from "node:test";
|
|
6
|
+
import type { Auth } from "./auth.js";
|
|
7
|
+
import { CLIENT_IP_HEADER, createAuth } from "./auth.js";
|
|
8
|
+
|
|
9
|
+
const SIGN_IN_MAX = 2;
|
|
10
|
+
const MIGRATIONS = resolve(
|
|
11
|
+
import.meta.dirname,
|
|
12
|
+
"../../../deploy/vps/migrations-sqlite/auth",
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
const createDatabase = async (dir: string, name: string): Promise<string> => {
|
|
16
|
+
const path = join(dir, `${name}.db`);
|
|
17
|
+
const { default: Database } = await import("better-sqlite3");
|
|
18
|
+
const { drizzle } = await import("drizzle-orm/better-sqlite3");
|
|
19
|
+
const { migrate } = await import("drizzle-orm/better-sqlite3/migrator");
|
|
20
|
+
const sqlite = new Database(path);
|
|
21
|
+
migrate(drizzle(sqlite), { migrationsFolder: MIGRATIONS });
|
|
22
|
+
sqlite.close();
|
|
23
|
+
return path;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const signIn = (auth: Auth, headers: Record<string, string>) =>
|
|
27
|
+
auth.handler(
|
|
28
|
+
new Request("http://localhost:3000/api/auth/sign-in/email", {
|
|
29
|
+
method: "POST",
|
|
30
|
+
headers: { "content-type": "application/json", ...headers },
|
|
31
|
+
body: JSON.stringify({
|
|
32
|
+
email: "nobody@example.com",
|
|
33
|
+
password: "a-sufficiently-long-password",
|
|
34
|
+
}),
|
|
35
|
+
}),
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Spend one client's sign-in budget, returning the attempt number that was
|
|
40
|
+
* turned away. `null` means the budget never ran out.
|
|
41
|
+
*/
|
|
42
|
+
const exhaust = async (
|
|
43
|
+
auth: Auth,
|
|
44
|
+
headers: Record<string, string>,
|
|
45
|
+
): Promise<number | null> => {
|
|
46
|
+
for (let attempt = 0; attempt <= SIGN_IN_MAX; attempt++) {
|
|
47
|
+
const response = await signIn(auth, headers);
|
|
48
|
+
if (response.status === 429) return attempt;
|
|
49
|
+
}
|
|
50
|
+
return null;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
describe("sign-in rate limiting is per client", () => {
|
|
54
|
+
const previousMax = process.env.BETTER_AUTH_RATE_LIMIT_SIGN_IN_MAX;
|
|
55
|
+
let dir: string;
|
|
56
|
+
|
|
57
|
+
before(() => {
|
|
58
|
+
process.env.BETTER_AUTH_RATE_LIMIT_SIGN_IN_MAX = String(SIGN_IN_MAX);
|
|
59
|
+
dir = mkdtempSync(join(tmpdir(), "remit-rate-limit-"));
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
after(() => {
|
|
63
|
+
if (previousMax === undefined) {
|
|
64
|
+
delete process.env.BETTER_AUTH_RATE_LIMIT_SIGN_IN_MAX;
|
|
65
|
+
} else {
|
|
66
|
+
process.env.BETTER_AUTH_RATE_LIMIT_SIGN_IN_MAX = previousMax;
|
|
67
|
+
}
|
|
68
|
+
rmSync(dir, { recursive: true, force: true });
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
const build = async (name: string): Promise<Auth> =>
|
|
72
|
+
createAuth({
|
|
73
|
+
connectionString: await createDatabase(dir, name),
|
|
74
|
+
secret: "rate-limit-test-secret-value-32chars-min",
|
|
75
|
+
baseURL: "http://localhost:3000",
|
|
76
|
+
selfSignUpEnabled: true,
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("locks out only the client that spent the budget", async () => {
|
|
80
|
+
const auth = await build("per-client");
|
|
81
|
+
|
|
82
|
+
assert.equal(
|
|
83
|
+
await exhaust(auth, { [CLIENT_IP_HEADER]: "203.0.113.10" }),
|
|
84
|
+
SIGN_IN_MAX,
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
const other = await signIn(auth, { [CLIENT_IP_HEADER]: "198.51.100.20" });
|
|
88
|
+
assert.equal(other.status, 401);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it("ignores a forwarded address the client supplies itself", async () => {
|
|
92
|
+
const auth = await build("spoofed");
|
|
93
|
+
const client = { [CLIENT_IP_HEADER]: "203.0.113.30" };
|
|
94
|
+
|
|
95
|
+
assert.equal(
|
|
96
|
+
await exhaust(auth, { ...client, "x-forwarded-for": "203.0.113.31" }),
|
|
97
|
+
SIGN_IN_MAX,
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
const spoofed = await signIn(auth, {
|
|
101
|
+
...client,
|
|
102
|
+
"x-forwarded-for": "198.51.100.40",
|
|
103
|
+
});
|
|
104
|
+
assert.equal(spoofed.status, 429);
|
|
105
|
+
});
|
|
106
|
+
});
|
|
@@ -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
|
-
);
|