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

@@ -13,7 +13,10 @@ import { returnFunc } from "./src/pkg/utils/return.js";
import { initializeProdPool } from "./src/pkg/prodSql/prodSqlConnect.js"; import { initializeProdPool } from "./src/pkg/prodSql/prodSqlConnect.js";
import { tryCatch } from "./src/pkg/utils/tryCatch.js"; import { tryCatch } from "./src/pkg/utils/tryCatch.js";
import os from "os"; import os from "os";
import cors from "cors";
import { sendNotify } from "./src/pkg/utils/notify.js"; import { sendNotify } from "./src/pkg/utils/notify.js";
import { toNodeHandler } from "better-auth/node";
import { auth } from "./src/pkg/auth/auth.js";
const main = async () => { const main = async () => {
const env = validateEnv(process.env); const env = validateEnv(process.env);
@@ -59,9 +62,6 @@ const main = async () => {
// express app // express app
const app = express(); const app = express();
// global middleware
app.use(express.json());
// global env that run only in dev // global env that run only in dev
if (process.env.NODE_ENV?.trim() !== "production") { if (process.env.NODE_ENV?.trim() !== "production") {
app.use(morgan("tiny")); app.use(morgan("tiny"));
@@ -73,6 +73,33 @@ const main = async () => {
); );
} }
// global middleware
app.all(basePath + "/api/auth/*splat", toNodeHandler(auth)); // sign-in sign-out
app.use(express.json());
const allowedOrigins = [
"http://localhost:5173", // dev
"http://localhost:4200",
env.BETTER_AUTH_URL, // prod
];
app.use(
cors({
origin: (origin, callback) => {
// allow requests with no origin (like curl, service workers, PWAs)
if (!origin) return callback(null, true);
if (allowedOrigins.includes(origin)) {
return callback(null, true);
} else {
return callback(new Error("Not allowed by CORS"));
}
},
methods: ["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"],
credentials: true,
})
);
// docs and api stuff // docs and api stuff
app.use( app.use(
basePath + "/d", basePath + "/d",

View File

@@ -0,0 +1,15 @@
import { Router } from "express";
import { auth } from "../../../pkg/auth/auth.js";
import { fromNodeHeaders } from "better-auth/node";
const router = Router();
// GET /health
router.get("/", async (req, res) => {
const session = await auth.api.getSession({
headers: fromNodeHeaders(req.headers),
});
return res.json(session);
});
export default router;

View File

@@ -0,0 +1,44 @@
import { Router } from "express";
import type { Request, Response } from "express";
import z from "zod";
import { auth } from "../../../pkg/auth/auth.js";
const router = Router();
const registerSchema = z.object({
email: z.email(),
name: z.string().min(2).max(100),
password: z.string().min(8, "Password must be at least 8 characters"),
username: z
.string()
.min(3)
.max(32)
.regex(/^[a-zA-Z0-9_]+$/, "Only alphanumeric + underscores"),
displayUsername: z.string().min(2).max(100).optional(), // optional in your API, but supported
});
router.post("/", async (req: Request, res: Response) => {
try {
// Parse + validate incoming JSON against Zod schema
const validated = registerSchema.parse(req.body);
// Call Better Auth signUp
const user = await auth.api.signUpEmail({
body: validated,
});
return res.status(201).json(user);
} catch (err) {
if (err instanceof z.ZodError) {
const flattened = z.flattenError(err);
return res.status(400).json({
error: "Validation failed",
details: flattened,
});
}
console.error(err);
return res.status(500).json({ error: "Internal server error" });
}
});
export default router;

View File

@@ -0,0 +1,8 @@
import type { Express, Request, Response } from "express";
import me from "./me.js";
import register from "./register.js";
export const setupAuthRoutes = (app: Express, basePath: string) => {
app.use(basePath + "/api/me", me);
app.use(basePath + "/api/auth/register", register);
};

View File

@@ -1,10 +1,14 @@
import type { Express, Request, Response } from "express"; import type { Express, Request, Response } from "express";
import healthRoutes from "./routes/healthRoutes.js"; import healthRoutes from "./routes/healthRoutes.js";
import { setupAuthRoutes } from "../auth/routes/routes.js";
export const setupRoutes = (app: Express, basePath: string) => { export const setupRoutes = (app: Express, basePath: string) => {
// Root / health check // Root / health check
app.use(basePath + "/health", healthRoutes); app.use(basePath + "/api/system/health", healthRoutes);
// all routes
setupAuthRoutes(app, basePath);
// always try to go to the app weather we are in dev or in production. // always try to go to the app weather we are in dev or in production.
app.get(basePath + "/", (req: Request, res: Response) => { app.get(basePath + "/", (req: Request, res: Response) => {

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_USER: z.string(),
PROD_PASSWORD: z.string(), PROD_PASSWORD: z.string(),
// auth stuff
BETTER_AUTH_URL: z.string(),
BETTER_AUTH_SECRET: z.string(),
// docker specifc // docker specifc
RUNNING_IN_DOCKER: z.string().default("false"), RUNNING_IN_DOCKER: z.string().default("false"),
}); });

View File

@@ -1,3 +1,3 @@
# What type of deploy ment do we have for the frontend # What type of deploy ment do we have for the frontend
NODE_ENV=dev APP_MODE=dev

View File

@@ -78,8 +78,10 @@
<button id="USMCD1VMS036">USMCD1VMS036</button> <button id="USMCD1VMS036">USMCD1VMS036</button>
<button id="USBET1VMS006">USBET1VMS006</button> <button id="USBET1VMS006">USBET1VMS006</button>
<button id="USBOW1VMS006">USBOW1VMS006</button> <button id="USBOW1VMS006">USBOW1VMS006</button>
<button id="USKSC1VMS006">USKSC1VMS006</button>
<button id="USSLC1VMS006">USSLC1VMS006</button> <button id="USSLC1VMS006">USSLC1VMS006</button>
<button id="USSTP1VMS006">USSTP1VMS006</button> <button id="USSTP1VMS006">USSTP1VMS006</button>
<button id="USDAY1VMS006">USDAY1VMS006</button>
</div> </div>
<script> <script>
@@ -168,23 +170,23 @@
const updateServerButton = const updateServerButton =
document.getElementById("updateServerButton"); document.getElementById("updateServerButton");
button.onclick = () => { // button.onclick = () => {
const fromMyInput = input.value; // const fromMyInput = input.value;
if (!fromMyInput) return; // if (!fromMyInput) return;
// Emit to backend (adjust event name as required) // // Emit to backend (adjust event name as required)
socket.emit("update", { // socket.emit("update", {
action: "copy", // action: "copy",
target: fromMyInput, // target: fromMyInput,
}); // });
// You can define your own logMessage function // // You can define your own logMessage function
logMessage("info", `Copying to ${fromMyInput}`); // logMessage("info", `Copying to ${fromMyInput}`);
input.value = ""; // input.value = "";
input.focus(); // input.focus();
}; // };
updateServerButton.onclick = () => { updateServerButton.onclick = () => {
const fromMyInput = updateServerInput.value; const fromMyInput = updateServerInput.value;
@@ -246,7 +248,23 @@
target: "USSTP1VMS006", target: "USSTP1VMS006",
drive: "d", drive: "d",
}); // "frontend" = payload target }); // "frontend" = payload target
logMessage("info", "Copying to USSLC1VMS006"); logMessage("info", "Copying to USSTP1VMS006");
};
document.getElementById("USKSC1VMS006").onclick = () => {
socket.emit("update", {
action: "copy",
target: "USksc1VMS006",
drive: "e",
}); // "frontend" = payload target
logMessage("info", "Copying to USKSC1VMS006");
};
document.getElementById("USDAY1VMS006").onclick = () => {
socket.emit("update", {
action: "copy",
target: "USDAY1VMS006",
drive: "e",
}); // "frontend" = payload target
logMessage("info", "Copying to USDAY1VMS006");
}; };
socket.on("logs", (msg) => logMessage("logs", msg)); socket.on("logs", (msg) => logMessage("logs", msg));

View File

@@ -4,7 +4,7 @@ const dbURL = `postgres://${process.env.DATABASE_USER}:${process.env.DATABASE_PA
export default defineConfig({ export default defineConfig({
dialect: "postgresql", dialect: "postgresql",
schema: "./app/src/pkg/db/schema", schema: "./dist/src/pkg/db/schema",
out: "./migrations", out: "./migrations",
dbCredentials: { url: dbURL }, dbCredentials: { url: dbURL },
// verbose: true, // optional, logs more details // verbose: true, // optional, logs more details

302
package-lock.json generated
View File

@@ -10,6 +10,9 @@
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"@dotenvx/dotenvx": "^1.49.0", "@dotenvx/dotenvx": "^1.49.0",
"@types/cors": "^2.8.19",
"better-auth": "^1.3.9",
"cors": "^2.8.5",
"drizzle-kit": "^0.31.4", "drizzle-kit": "^0.31.4",
"drizzle-orm": "^0.44.5", "drizzle-orm": "^0.44.5",
"drizzle-zod": "^0.8.3", "drizzle-zod": "^0.8.3",
@@ -318,6 +321,20 @@
"node": ">=6.9.0" "node": ">=6.9.0"
} }
}, },
"node_modules/@better-auth/utils": {
"version": "0.2.6",
"resolved": "https://registry.npmjs.org/@better-auth/utils/-/utils-0.2.6.tgz",
"integrity": "sha512-3y/vaL5Ox33dBwgJ6ub3OPkVqr6B5xL2kgxNHG8eHZuryLyG/4JSPGqjbdRSgjuy9kALUZYDFl+ORIAxlWMSuA==",
"license": "MIT",
"dependencies": {
"uncrypto": "^0.1.3"
}
},
"node_modules/@better-fetch/fetch": {
"version": "1.1.18",
"resolved": "https://registry.npmjs.org/@better-fetch/fetch/-/fetch-1.1.18.tgz",
"integrity": "sha512-rEFOE1MYIsBmoMJtQbl32PGHHXuG2hDxvEd7rUHE0vCBoFQVSDqaVs9hkZEtHCxRoY+CljXKFCOuJ8uxqw1LcA=="
},
"node_modules/@commitlint/config-validator": { "node_modules/@commitlint/config-validator": {
"version": "19.8.1", "version": "19.8.1",
"resolved": "https://registry.npmjs.org/@commitlint/config-validator/-/config-validator-19.8.1.tgz", "resolved": "https://registry.npmjs.org/@commitlint/config-validator/-/config-validator-19.8.1.tgz",
@@ -1339,6 +1356,12 @@
"node": ">=18" "node": ">=18"
} }
}, },
"node_modules/@hexagon/base64": {
"version": "1.1.28",
"resolved": "https://registry.npmjs.org/@hexagon/base64/-/base64-1.1.28.tgz",
"integrity": "sha512-lhqDEAvWixy3bZ+UOYbPwUbBkwBq5C1LAJ/xPC8Oi+lL54oyakv/npbA0aU2hgCsx/1NUd4IBvV03+aUBWxerw==",
"license": "MIT"
},
"node_modules/@hutson/parse-repository-url": { "node_modules/@hutson/parse-repository-url": {
"version": "3.0.2", "version": "3.0.2",
"resolved": "https://registry.npmjs.org/@hutson/parse-repository-url/-/parse-repository-url-3.0.2.tgz", "resolved": "https://registry.npmjs.org/@hutson/parse-repository-url/-/parse-repository-url-3.0.2.tgz",
@@ -1377,6 +1400,12 @@
"integrity": "sha512-3zwefSMwHpu8iVUW8YYz227sIv6UFqO31p1Bf1ZH/Vom7CmNyUsXjDBlnNzcuhmOL1XfxZ3nvND42kR23XlbcQ==", "integrity": "sha512-3zwefSMwHpu8iVUW8YYz227sIv6UFqO31p1Bf1ZH/Vom7CmNyUsXjDBlnNzcuhmOL1XfxZ3nvND42kR23XlbcQ==",
"license": "BSD-3-Clause" "license": "BSD-3-Clause"
}, },
"node_modules/@levischuck/tiny-cbor": {
"version": "0.2.11",
"resolved": "https://registry.npmjs.org/@levischuck/tiny-cbor/-/tiny-cbor-0.2.11.tgz",
"integrity": "sha512-llBRm4dT4Z89aRsm6u2oEZ8tfwL/2l6BwpZ7JcyieouniDECM5AqNgr/y08zalEIvW3RSK4upYyybDcmjXqAow==",
"license": "MIT"
},
"node_modules/@noble/ciphers": { "node_modules/@noble/ciphers": {
"version": "1.3.0", "version": "1.3.0",
"resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-1.3.0.tgz", "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-1.3.0.tgz",
@@ -1416,6 +1445,88 @@
"url": "https://paulmillr.com/funding/" "url": "https://paulmillr.com/funding/"
} }
}, },
"node_modules/@peculiar/asn1-android": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/@peculiar/asn1-android/-/asn1-android-2.5.0.tgz",
"integrity": "sha512-t8A83hgghWQkcneRsgGs2ebAlRe54ns88p7ouv8PW2tzF1nAW4yHcL4uZKrFpIU+uszIRzTkcCuie37gpkId0A==",
"license": "MIT",
"dependencies": {
"@peculiar/asn1-schema": "^2.5.0",
"asn1js": "^3.0.6",
"tslib": "^2.8.1"
}
},
"node_modules/@peculiar/asn1-ecc": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/@peculiar/asn1-ecc/-/asn1-ecc-2.5.0.tgz",
"integrity": "sha512-t4eYGNhXtLRxaP50h3sfO6aJebUCDGQACoeexcelL4roMFRRVgB20yBIu2LxsPh/tdW9I282gNgMOyg3ywg/mg==",
"license": "MIT",
"dependencies": {
"@peculiar/asn1-schema": "^2.5.0",
"@peculiar/asn1-x509": "^2.5.0",
"asn1js": "^3.0.6",
"tslib": "^2.8.1"
}
},
"node_modules/@peculiar/asn1-rsa": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/@peculiar/asn1-rsa/-/asn1-rsa-2.5.0.tgz",
"integrity": "sha512-qMZ/vweiTHy9syrkkqWFvbT3eLoedvamcUdnnvwyyUNv5FgFXA3KP8td+ATibnlZ0EANW5PYRm8E6MJzEB/72Q==",
"license": "MIT",
"dependencies": {
"@peculiar/asn1-schema": "^2.5.0",
"@peculiar/asn1-x509": "^2.5.0",
"asn1js": "^3.0.6",
"tslib": "^2.8.1"
}
},
"node_modules/@peculiar/asn1-schema": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/@peculiar/asn1-schema/-/asn1-schema-2.5.0.tgz",
"integrity": "sha512-YM/nFfskFJSlHqv59ed6dZlLZqtZQwjRVJ4bBAiWV08Oc+1rSd5lDZcBEx0lGDHfSoH3UziI2pXt2UM33KerPQ==",
"license": "MIT",
"dependencies": {
"asn1js": "^3.0.6",
"pvtsutils": "^1.3.6",
"tslib": "^2.8.1"
}
},
"node_modules/@peculiar/asn1-x509": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/@peculiar/asn1-x509/-/asn1-x509-2.5.0.tgz",
"integrity": "sha512-CpwtMCTJvfvYTFMuiME5IH+8qmDe3yEWzKHe7OOADbGfq7ohxeLaXwQo0q4du3qs0AII3UbLCvb9NF/6q0oTKQ==",
"license": "MIT",
"dependencies": {
"@peculiar/asn1-schema": "^2.5.0",
"asn1js": "^3.0.6",
"pvtsutils": "^1.3.6",
"tslib": "^2.8.1"
}
},
"node_modules/@simplewebauthn/browser": {
"version": "13.1.2",
"resolved": "https://registry.npmjs.org/@simplewebauthn/browser/-/browser-13.1.2.tgz",
"integrity": "sha512-aZnW0KawAM83fSBUgglP5WofbrLbLyr7CoPqYr66Eppm7zO86YX6rrCjRB3hQKPrL7ATvY4FVXlykZ6w6FwYYw==",
"license": "MIT"
},
"node_modules/@simplewebauthn/server": {
"version": "13.1.2",
"resolved": "https://registry.npmjs.org/@simplewebauthn/server/-/server-13.1.2.tgz",
"integrity": "sha512-VwoDfvLXSCaRiD+xCIuyslU0HLxVggeE5BL06+GbsP2l1fGf5op8e0c3ZtKoi+vSg1q4ikjtAghC23ze2Q3H9g==",
"license": "MIT",
"dependencies": {
"@hexagon/base64": "^1.1.27",
"@levischuck/tiny-cbor": "^0.2.2",
"@peculiar/asn1-android": "^2.3.10",
"@peculiar/asn1-ecc": "^2.3.8",
"@peculiar/asn1-rsa": "^2.3.8",
"@peculiar/asn1-schema": "^2.3.8",
"@peculiar/asn1-x509": "^2.3.8"
},
"engines": {
"node": ">=20.0.0"
}
},
"node_modules/@tediousjs/connection-string": { "node_modules/@tediousjs/connection-string": {
"version": "0.5.0", "version": "0.5.0",
"resolved": "https://registry.npmjs.org/@tediousjs/connection-string/-/connection-string-0.5.0.tgz", "resolved": "https://registry.npmjs.org/@tediousjs/connection-string/-/connection-string-0.5.0.tgz",
@@ -1470,6 +1581,15 @@
"@types/node": "*" "@types/node": "*"
} }
}, },
"node_modules/@types/cors": {
"version": "2.8.19",
"resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz",
"integrity": "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==",
"license": "MIT",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/express": { "node_modules/@types/express": {
"version": "5.0.3", "version": "5.0.3",
"dev": true, "dev": true,
@@ -1764,6 +1884,20 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/asn1js": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/asn1js/-/asn1js-3.0.6.tgz",
"integrity": "sha512-UOCGPYbl0tv8+006qks/dTgV9ajs97X2p0FAbyS2iyCRrmLSRolDaHdp+v/CLgnzHc3fVB+CwYiUmei7ndFcgA==",
"license": "BSD-3-Clause",
"dependencies": {
"pvtsutils": "^1.3.6",
"pvutils": "^1.1.3",
"tslib": "^2.8.1"
},
"engines": {
"node": ">=12.0.0"
}
},
"node_modules/at-least-node": { "node_modules/at-least-node": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz",
@@ -1822,6 +1956,77 @@
"version": "5.1.2", "version": "5.1.2",
"license": "MIT" "license": "MIT"
}, },
"node_modules/better-auth": {
"version": "1.3.9",
"resolved": "https://registry.npmjs.org/better-auth/-/better-auth-1.3.9.tgz",
"integrity": "sha512-Ty6BHzuShlqSs7I4RMlBRQ3duOWNB7WWriIu2FJVGjQAOtTVvamzFCR4/j5ROFLoNkpvNTRF7BJozsrMICL1gw==",
"license": "MIT",
"dependencies": {
"@better-auth/utils": "0.2.6",
"@better-fetch/fetch": "^1.1.18",
"@noble/ciphers": "^2.0.0",
"@noble/hashes": "^2.0.0",
"@simplewebauthn/browser": "^13.1.2",
"@simplewebauthn/server": "^13.1.2",
"better-call": "1.0.18",
"defu": "^6.1.4",
"jose": "^6.1.0",
"kysely": "^0.28.5",
"nanostores": "^0.11.4",
"zod": "^4.1.5"
},
"peerDependencies": {
"@lynx-js/react": "*",
"react": "^18.0.0 || ^19.0.0",
"react-dom": "^18.0.0 || ^19.0.0"
},
"peerDependenciesMeta": {
"@lynx-js/react": {
"optional": true
},
"react": {
"optional": true
},
"react-dom": {
"optional": true
}
}
},
"node_modules/better-auth/node_modules/@noble/ciphers": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-2.0.0.tgz",
"integrity": "sha512-j/l6jpnpaIBM87cAYPJzi/6TgqmBv9spkqPyCXvRYsu5uxqh6tPJZDnD85yo8VWqzTuTQPgfv7NgT63u7kbwAQ==",
"license": "MIT",
"engines": {
"node": ">= 20.19.0"
},
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/better-auth/node_modules/@noble/hashes": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.0.0.tgz",
"integrity": "sha512-h8VUBlE8R42+XIDO229cgisD287im3kdY6nbNZJFjc6ZvKIXPYXe6Vc/t+kyjFdMFyt5JpapzTsEg8n63w5/lw==",
"license": "MIT",
"engines": {
"node": ">= 20.19.0"
},
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/better-call": {
"version": "1.0.18",
"resolved": "https://registry.npmjs.org/better-call/-/better-call-1.0.18.tgz",
"integrity": "sha512-Ojyck3P3fs/egBmCW50tvfbCJorNV5KphfPOKrkCxPfOr8Brth1ruDtAJuhHVHEUiWrXv+vpEgWQk7m7FzhbbQ==",
"dependencies": {
"@better-fetch/fetch": "^1.1.4",
"rou3": "^0.5.1",
"set-cookie-parser": "^2.7.1",
"uncrypto": "^0.1.3"
}
},
"node_modules/binary-extensions": { "node_modules/binary-extensions": {
"version": "2.3.0", "version": "2.3.0",
"dev": true, "dev": true,
@@ -2730,6 +2935,19 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/cors": {
"version": "2.8.5",
"resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
"integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
"license": "MIT",
"dependencies": {
"object-assign": "^4",
"vary": "^1"
},
"engines": {
"node": ">= 0.10"
}
},
"node_modules/cosmiconfig": { "node_modules/cosmiconfig": {
"version": "9.0.0", "version": "9.0.0",
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz",
@@ -2970,6 +3188,12 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/defu": {
"version": "6.1.4",
"resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz",
"integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==",
"license": "MIT"
},
"node_modules/depd": { "node_modules/depd": {
"version": "2.0.0", "version": "2.0.0",
"license": "MIT", "license": "MIT",
@@ -4729,6 +4953,15 @@
"jiti": "lib/jiti-cli.mjs" "jiti": "lib/jiti-cli.mjs"
} }
}, },
"node_modules/jose": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/jose/-/jose-6.1.0.tgz",
"integrity": "sha512-TTQJyoEoKcC1lscpVDCSsVgYzUDg/0Bt3WE//WiTPK6uOCQC2KZS4MpugbMWt/zyjkopgZoXhZuCi00gLudfUA==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/panva"
}
},
"node_modules/joycon": { "node_modules/joycon": {
"version": "3.1.1", "version": "3.1.1",
"resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz", "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz",
@@ -4887,6 +5120,15 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/kysely": {
"version": "0.28.5",
"resolved": "https://registry.npmjs.org/kysely/-/kysely-0.28.5.tgz",
"integrity": "sha512-rlB0I/c6FBDWPcQoDtkxi9zIvpmnV5xoIalfCMSMCa7nuA6VGA3F54TW9mEgX4DVf10sXAWCF5fDbamI/5ZpKA==",
"license": "MIT",
"engines": {
"node": ">=20.0.0"
}
},
"node_modules/lines-and-columns": { "node_modules/lines-and-columns": {
"version": "1.2.4", "version": "1.2.4",
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
@@ -5557,6 +5799,21 @@
"dev": true, "dev": true,
"license": "ISC" "license": "ISC"
}, },
"node_modules/nanostores": {
"version": "0.11.4",
"resolved": "https://registry.npmjs.org/nanostores/-/nanostores-0.11.4.tgz",
"integrity": "sha512-k1oiVNN4hDK8NcNERSZLQiMfRzEGtfnvZvdBvey3SQbgn8Dcrk0h1I6vpxApjb10PFUflZrgJ2WEZyJQ+5v7YQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"license": "MIT",
"engines": {
"node": "^18.0.0 || >=20.0.0"
}
},
"node_modules/native-duplexpair": { "node_modules/native-duplexpair": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/native-duplexpair/-/native-duplexpair-1.0.0.tgz", "resolved": "https://registry.npmjs.org/native-duplexpair/-/native-duplexpair-1.0.0.tgz",
@@ -5613,6 +5870,15 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/object-inspect": { "node_modules/object-inspect": {
"version": "1.13.4", "version": "1.13.4",
"license": "MIT", "license": "MIT",
@@ -6284,6 +6550,24 @@
"once": "^1.3.1" "once": "^1.3.1"
} }
}, },
"node_modules/pvtsutils": {
"version": "1.3.6",
"resolved": "https://registry.npmjs.org/pvtsutils/-/pvtsutils-1.3.6.tgz",
"integrity": "sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg==",
"license": "MIT",
"dependencies": {
"tslib": "^2.8.1"
}
},
"node_modules/pvutils": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/pvutils/-/pvutils-1.1.3.tgz",
"integrity": "sha512-pMpnA0qRdFp32b1sJl1wOJNxZLQ2cbQx+k6tjNtZ8CpvVhNqEPRgivZ2WOUev2YMajecdH7ctUPDvEe87nariQ==",
"license": "MIT",
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/q": { "node_modules/q": {
"version": "1.5.1", "version": "1.5.1",
"resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz",
@@ -6631,6 +6915,12 @@
"rimraf": "bin.js" "rimraf": "bin.js"
} }
}, },
"node_modules/rou3": {
"version": "0.5.1",
"resolved": "https://registry.npmjs.org/rou3/-/rou3-0.5.1.tgz",
"integrity": "sha512-OXMmJ3zRk2xeXFGfA3K+EOPHC5u7RDFG7lIOx0X1pdnhUkI8MdVrbV+sNsD80ElpUZ+MRHdyxPnFthq9VHs8uQ==",
"license": "MIT"
},
"node_modules/router": { "node_modules/router": {
"version": "2.2.0", "version": "2.2.0",
"license": "MIT", "license": "MIT",
@@ -6769,6 +7059,12 @@
"node": ">= 18" "node": ">= 18"
} }
}, },
"node_modules/set-cookie-parser": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz",
"integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==",
"license": "MIT"
},
"node_modules/setprototypeof": { "node_modules/setprototypeof": {
"version": "1.2.0", "version": "1.2.0",
"license": "ISC" "license": "ISC"
@@ -7446,6 +7742,12 @@
"node": ">=0.8.0" "node": ">=0.8.0"
} }
}, },
"node_modules/uncrypto": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/uncrypto/-/uncrypto-0.1.3.tgz",
"integrity": "sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==",
"license": "MIT"
},
"node_modules/undici-types": { "node_modules/undici-types": {
"version": "7.10.0", "version": "7.10.0",
"license": "MIT" "license": "MIT"

View File

@@ -8,8 +8,8 @@
"dev:app": "dotenvx run -f .env -- tsx watch app/main.ts", "dev:app": "dotenvx run -f .env -- tsx watch app/main.ts",
"dev:docs": "npm run translateDocs && cd lstDocs && npm start", "dev:docs": "npm run translateDocs && cd lstDocs && npm start",
"dev:front": "cd frontend && npm run dev", "dev:front": "cd frontend && npm run dev",
"dev:db:migrate": "npx drizzle-kit push --config=drizzle-dev.config.ts", "dev:db:migrate": "npx drizzle-kit push",
"dev:db:generate": "npx drizzle-kit generate --config=drizzle-dev.config.ts", "dev:db:generate": "tsc && npx drizzle-kit generate --config=drizzle-dev.config.ts",
"dev": "concurrently -n \"server,frontend,docs\" -c \"#007755,#2f6da3,#DB4FE0\" \"npm run dev:app\" \"npm run dev:front\" \"npm run dev:docs\"", "dev": "concurrently -n \"server,frontend,docs\" -c \"#007755,#2f6da3,#DB4FE0\" \"npm run dev:app\" \"npm run dev:front\" \"npm run dev:docs\"",
"copy:docs": "node scripts/lstDocCopy.mjs", "copy:docs": "node scripts/lstDocCopy.mjs",
"build:app": "rimraf dist && npx tsc", "build:app": "rimraf dist && npx tsc",
@@ -29,7 +29,8 @@
"deploy": "standard-version --conventional-commits && npm run translateDocs && npm run build", "deploy": "standard-version --conventional-commits && npm run translateDocs && npm run build",
"db:migrate": "npx drizzle-kit push", "db:migrate": "npx drizzle-kit push",
"db:generate": "npx drizzle-kit generate", "db:generate": "npx drizzle-kit generate",
"translateDocs": "cd scripts && node translateScript.js" "translateDocs": "cd scripts && node translateScript.js",
"auth:generate": "npx @better-auth/cli generate --config ./app/src/pkg/auth/auth.ts"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
@@ -41,6 +42,9 @@
"type": "module", "type": "module",
"dependencies": { "dependencies": {
"@dotenvx/dotenvx": "^1.49.0", "@dotenvx/dotenvx": "^1.49.0",
"@types/cors": "^2.8.19",
"better-auth": "^1.3.9",
"cors": "^2.8.5",
"drizzle-kit": "^0.31.4", "drizzle-kit": "^0.31.4",
"drizzle-orm": "^0.44.5", "drizzle-orm": "^0.44.5",
"drizzle-zod": "^0.8.3", "drizzle-zod": "^0.8.3",

View File

@@ -6,7 +6,7 @@
"strict": true, "strict": true,
"verbatimModuleSyntax": true, "verbatimModuleSyntax": true,
"skipLibCheck": true, "skipLibCheck": true,
"types": ["node"], "types": ["node", "better-auth"],
"jsx": "react-jsx", "jsx": "react-jsx",
"outDir": "./dist", "outDir": "./dist",
"removeComments": true, "removeComments": true,
@@ -18,7 +18,8 @@
"app/src", "app/src",
"database/testFiles/test-tiPostOrders.ts", "database/testFiles/test-tiPostOrders.ts",
"scripts/translateScript.js", "scripts/translateScript.js",
"app/main.ts" "app/main.ts",
"app/src/types"
], ],
"exclude": [ "exclude": [
"node_modules", "node_modules",