feat(app): added better auth for better auth stuff vs my home written broken one

This commit is contained in:
2025-09-18 21:46:01 -05:00
parent 21608b0171
commit caf2315191
16 changed files with 648 additions and 25 deletions

View File

@@ -0,0 +1,19 @@
import { createAuthClient } from "better-auth/client";
import {
inferAdditionalFields,
usernameClient,
adminClient,
apiKeyClient,
} from "better-auth/client/plugins";
import type { auth } from "./auth.js";
export const authClient = createAuthClient({
baseURL: "http://localhost:3000",
plugins: [
inferAdditionalFields<typeof auth>(),
usernameClient(),
adminClient(),
apiKeyClient(),
],
});

52
app/src/pkg/auth/auth.ts Normal file
View File

@@ -0,0 +1,52 @@
import { drizzleAdapter } from "better-auth/adapters/drizzle";
import { db } from "../db/db.js";
import { username, admin, apiKey, jwt } from "better-auth/plugins";
import { betterAuth } from "better-auth";
import * as rawSchema from "../db/schema/auth-schema.js";
import type { User } from "better-auth/types";
import { eq } from "drizzle-orm";
export const schema = {
user: rawSchema.user,
session: rawSchema.session,
account: rawSchema.account,
verification: rawSchema.verification,
jwks: rawSchema.jwks,
apiKey: rawSchema.apikey, // 🔑 rename to apiKey
};
export const auth = betterAuth({
database: drizzleAdapter(db, {
provider: "pg",
schema,
}),
appName: "lst",
emailAndPassword: {
enabled: true,
minPasswordLength: 8, // optional config
},
plugins: [
jwt({ jwt: { expirationTime: "1h" } }),
apiKey(),
admin(),
username(),
],
session: {
expiresIn: 60 * 60,
updateAge: 60 * 1,
cookieCache: {
enabled: true,
maxAge: 5 * 60, // Cache duration in seconds
},
},
events: {
async onSignInSuccess({ user }: { user: User }) {
await db
.update(schema.user)
.set({ lastLogin: new Date() })
.where(eq(schema.user.id, user.id));
},
},
});
export type Auth = typeof auth;

View File

@@ -0,0 +1,108 @@
import {
pgTable,
text,
timestamp,
boolean,
integer,
} from "drizzle-orm/pg-core";
export const user = pgTable("user", {
id: text("id").primaryKey(),
name: text("name").notNull(),
email: text("email").notNull().unique(),
emailVerified: boolean("email_verified").default(false).notNull(),
image: text("image"),
createdAt: timestamp("created_at").defaultNow().notNull(),
updatedAt: timestamp("updated_at")
.defaultNow()
.$onUpdate(() => /* @__PURE__ */ new Date())
.notNull(),
role: text("role"),
banned: boolean("banned").default(false),
banReason: text("ban_reason"),
banExpires: timestamp("ban_expires"),
username: text("username").unique(),
displayUsername: text("display_username"),
lastLogin: timestamp("last_login"),
});
export const session = pgTable("session", {
id: text("id").primaryKey(),
expiresAt: timestamp("expires_at").notNull(),
token: text("token").notNull().unique(),
createdAt: timestamp("created_at").defaultNow().notNull(),
updatedAt: timestamp("updated_at")
.$onUpdate(() => /* @__PURE__ */ new Date())
.notNull(),
ipAddress: text("ip_address"),
userAgent: text("user_agent"),
userId: text("user_id")
.notNull()
.references(() => user.id, { onDelete: "cascade" }),
impersonatedBy: text("impersonated_by"),
});
export const account = pgTable("account", {
id: text("id").primaryKey(),
accountId: text("account_id").notNull(),
providerId: text("provider_id").notNull(),
userId: text("user_id")
.notNull()
.references(() => user.id, { onDelete: "cascade" }),
accessToken: text("access_token"),
refreshToken: text("refresh_token"),
idToken: text("id_token"),
accessTokenExpiresAt: timestamp("access_token_expires_at"),
refreshTokenExpiresAt: timestamp("refresh_token_expires_at"),
scope: text("scope"),
password: text("password"),
createdAt: timestamp("created_at").defaultNow().notNull(),
updatedAt: timestamp("updated_at")
.$onUpdate(() => /* @__PURE__ */ new Date())
.notNull(),
});
export const verification = pgTable("verification", {
id: text("id").primaryKey(),
identifier: text("identifier").notNull(),
value: text("value").notNull(),
expiresAt: timestamp("expires_at").notNull(),
createdAt: timestamp("created_at").defaultNow().notNull(),
updatedAt: timestamp("updated_at")
.defaultNow()
.$onUpdate(() => /* @__PURE__ */ new Date())
.notNull(),
});
export const jwks = pgTable("jwks", {
id: text("id").primaryKey(),
publicKey: text("public_key").notNull(),
privateKey: text("private_key").notNull(),
createdAt: timestamp("created_at").notNull(),
});
export const apikey = pgTable("apikey", {
id: text("id").primaryKey(),
name: text("name"),
start: text("start"),
prefix: text("prefix"),
key: text("key").notNull(),
userId: text("user_id")
.notNull()
.references(() => user.id, { onDelete: "cascade" }),
refillInterval: integer("refill_interval"),
refillAmount: integer("refill_amount"),
lastRefillAt: timestamp("last_refill_at"),
enabled: boolean("enabled").default(true),
rateLimitEnabled: boolean("rate_limit_enabled").default(true),
rateLimitTimeWindow: integer("rate_limit_time_window").default(86400000),
rateLimitMax: integer("rate_limit_max").default(10),
requestCount: integer("request_count").default(0),
remaining: integer("remaining"),
lastRequest: timestamp("last_request"),
expiresAt: timestamp("expires_at"),
createdAt: timestamp("created_at").notNull(),
updatedAt: timestamp("updated_at").notNull(),
permissions: text("permissions"),
metadata: text("metadata"),
});

View File

@@ -0,0 +1,17 @@
import { pgTable, text, uuid, uniqueIndex } from "drizzle-orm/pg-core";
import { user } from "./auth-schema.js";
export const userRole = pgTable(
"user_role",
{
userRoleId: uuid("user_role_id").defaultRandom().primaryKey(),
userId: text("user_id")
.notNull()
.references(() => user.id, { onDelete: "cascade" }),
module: text("module").notNull(), // e.g. "siloAdjustments"
role: text("role").notNull(), // e.g. "admin" | "manager" | "viewer"
},
(table) => [
uniqueIndex("unique_user_module").on(table.userId, table.module),
]
);

View File

@@ -18,6 +18,10 @@ const envSchema = z.object({
PROD_USER: z.string(),
PROD_PASSWORD: z.string(),
// auth stuff
BETTER_AUTH_URL: z.string(),
BETTER_AUTH_SECRET: z.string(),
// docker specifc
RUNNING_IN_DOCKER: z.string().default("false"),
});