feat(auth): implement default user ID handling when auth is disabled across routes
This commit is contained in:
@@ -1,21 +1,20 @@
|
||||
import { createClient } from "@libsql/client";
|
||||
import { drizzle } from "drizzle-orm/libsql";
|
||||
import { existsSync, mkdirSync } from "fs";
|
||||
import { dirname } from "path";
|
||||
import { dirname, resolve } from "path";
|
||||
import dotenv from "dotenv";
|
||||
|
||||
dotenv.config({ path: process.env.DOTENV_PATH || ".env" });
|
||||
|
||||
const url = "file:./data/medassist-ng.db";
|
||||
// Use absolute path to ensure it works in Docker
|
||||
const dataDir = resolve(process.cwd(), "data");
|
||||
const dbPath = resolve(dataDir, "medassist-ng.db");
|
||||
const url = `file:${dbPath}`;
|
||||
|
||||
// Ensure data directory exists before creating database
|
||||
if (url.startsWith("file:")) {
|
||||
const dbPath = url.replace("file:", "");
|
||||
const dataDir = dirname(dbPath);
|
||||
if (!existsSync(dataDir)) {
|
||||
mkdirSync(dataDir, { recursive: true });
|
||||
console.log(`[DB] Created data directory: ${dataDir}`);
|
||||
}
|
||||
if (!existsSync(dataDir)) {
|
||||
mkdirSync(dataDir, { recursive: true });
|
||||
console.log(`[DB] Created data directory: ${dataDir}`);
|
||||
}
|
||||
|
||||
const client = createClient({ url });
|
||||
@@ -145,6 +144,23 @@ async function runMigrations() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If auth is disabled, ensure a default user exists (ID=1)
|
||||
const authEnabled = process.env.AUTH_ENABLED === "true";
|
||||
if (!authEnabled) {
|
||||
try {
|
||||
// Check if default user exists
|
||||
const result = await client.execute("SELECT id FROM users WHERE id = 1");
|
||||
if (result.rows.length === 0) {
|
||||
await client.execute(
|
||||
"INSERT INTO users (id, username, auth_provider) VALUES (1, 'default', 'local')"
|
||||
);
|
||||
console.log(`[DB] Created default user for auth-disabled mode`);
|
||||
}
|
||||
} catch (e: any) {
|
||||
console.error(`[DB] Error creating default user:`, e.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Export promise so server can await it before starting
|
||||
|
||||
+24
-16
@@ -4,6 +4,7 @@ import { db } from "../db/client.js";
|
||||
import { doseTracking, shareTokens } from "../db/schema.js";
|
||||
import { eq, and, gte } from "drizzle-orm";
|
||||
import { requireAuth } from "../plugins/auth.js";
|
||||
import { env } from "../plugins/env.js";
|
||||
import type { AuthUser } from "../types/fastify.js";
|
||||
|
||||
// =============================================================================
|
||||
@@ -17,6 +18,22 @@ const shareDoseSchema = z.object({
|
||||
doseId: z.string().min(1, "doseId is required"),
|
||||
});
|
||||
|
||||
// Helper to get user ID from request
|
||||
// Returns a default user ID when auth is disabled
|
||||
function getUserId(request: any, reply: any): number {
|
||||
// If auth is disabled, use a default user ID (1)
|
||||
if (!env.AUTH_ENABLED) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
const authUser = request.user as unknown as AuthUser | null;
|
||||
if (!authUser) {
|
||||
reply.status(401).send({ error: "Not authenticated" });
|
||||
throw new Error("AUTH_REQUIRED");
|
||||
}
|
||||
return authUser.id;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Dose Tracking Routes
|
||||
// =============================================================================
|
||||
@@ -28,10 +45,7 @@ export async function doseRoutes(app: FastifyInstance) {
|
||||
"/doses/taken",
|
||||
{ preHandler: requireAuth },
|
||||
async (request, reply) => {
|
||||
const authUser = request.user as unknown as AuthUser | null;
|
||||
if (!authUser) {
|
||||
return reply.status(401).send({ error: "Not authenticated" });
|
||||
}
|
||||
const userId = getUserId(request, reply);
|
||||
|
||||
// Get doses from last 30 days (to avoid loading too much data)
|
||||
const thirtyDaysAgo = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000);
|
||||
@@ -40,7 +54,7 @@ export async function doseRoutes(app: FastifyInstance) {
|
||||
.from(doseTracking)
|
||||
.where(
|
||||
and(
|
||||
eq(doseTracking.userId, authUser.id),
|
||||
eq(doseTracking.userId, userId),
|
||||
gte(doseTracking.takenAt, thirtyDaysAgo)
|
||||
)
|
||||
);
|
||||
@@ -62,10 +76,7 @@ export async function doseRoutes(app: FastifyInstance) {
|
||||
"/doses/taken",
|
||||
{ preHandler: requireAuth },
|
||||
async (request, reply) => {
|
||||
const authUser = request.user as unknown as AuthUser | null;
|
||||
if (!authUser) {
|
||||
return reply.status(401).send({ error: "Not authenticated" });
|
||||
}
|
||||
const userId = getUserId(request, reply);
|
||||
|
||||
const parsed = markDoseSchema.safeParse(request.body);
|
||||
if (!parsed.success) {
|
||||
@@ -81,7 +92,7 @@ export async function doseRoutes(app: FastifyInstance) {
|
||||
.from(doseTracking)
|
||||
.where(
|
||||
and(
|
||||
eq(doseTracking.userId, authUser.id),
|
||||
eq(doseTracking.userId, userId),
|
||||
eq(doseTracking.doseId, doseId)
|
||||
)
|
||||
);
|
||||
@@ -92,7 +103,7 @@ export async function doseRoutes(app: FastifyInstance) {
|
||||
|
||||
// Insert new record
|
||||
await db.insert(doseTracking).values({
|
||||
userId: authUser.id,
|
||||
userId,
|
||||
doseId,
|
||||
markedBy: null, // Marked by the user themselves
|
||||
});
|
||||
@@ -108,16 +119,13 @@ export async function doseRoutes(app: FastifyInstance) {
|
||||
"/doses/taken/:doseId",
|
||||
{ preHandler: requireAuth },
|
||||
async (request, reply) => {
|
||||
const authUser = request.user as unknown as AuthUser | null;
|
||||
if (!authUser) {
|
||||
return reply.status(401).send({ error: "Not authenticated" });
|
||||
}
|
||||
const userId = getUserId(request, reply);
|
||||
|
||||
const { doseId } = request.params;
|
||||
|
||||
await db.delete(doseTracking).where(
|
||||
and(
|
||||
eq(doseTracking.userId, authUser.id),
|
||||
eq(doseTracking.userId, userId),
|
||||
eq(doseTracking.doseId, doseId)
|
||||
)
|
||||
);
|
||||
|
||||
@@ -58,16 +58,24 @@ export async function medicationRoutes(app: FastifyInstance) {
|
||||
app.addHook("preHandler", requireAuth);
|
||||
|
||||
// Helper to get user ID from request
|
||||
function getUserId(request: any): number {
|
||||
// Returns a default user ID when auth is disabled
|
||||
function getUserId(request: any, reply: any): number {
|
||||
// If auth is disabled, use a default user ID (1)
|
||||
if (!env.AUTH_ENABLED) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
const authUser = request.user as unknown as AuthUser | null;
|
||||
if (!authUser) {
|
||||
throw new Error("User not authenticated");
|
||||
// This should never happen if requireAuth worked, but be safe
|
||||
reply.status(401).send({ error: "User not authenticated", code: "AUTH_REQUIRED" });
|
||||
throw new Error("AUTH_REQUIRED");
|
||||
}
|
||||
return authUser.id;
|
||||
}
|
||||
|
||||
app.get("/medications", async (request, reply) => {
|
||||
const userId = getUserId(request);
|
||||
const userId = getUserId(request, reply);
|
||||
const rows = await db.select().from(medications).where(eq(medications.userId, userId)).orderBy(medications.id);
|
||||
return rows.map((row) => ({
|
||||
id: row.id,
|
||||
@@ -95,7 +103,7 @@ export async function medicationRoutes(app: FastifyInstance) {
|
||||
const parsed = medicationSchema.safeParse(req.body);
|
||||
if (!parsed.success) return reply.status(400).send(parsed.error.format());
|
||||
|
||||
const userId = getUserId(req);
|
||||
const userId = getUserId(req, reply);
|
||||
const { name, genericName, takenBy, packCount, stripsPerPack, tabsPerStrip, looseTablets, pillWeightMg, expiryDate, notes, intakeRemindersEnabled, slices } = parsed.data;
|
||||
const usageJson = JSON.stringify(slices.map((s) => s.usage));
|
||||
const everyJson = JSON.stringify(slices.map((s) => s.every));
|
||||
@@ -155,7 +163,7 @@ export async function medicationRoutes(app: FastifyInstance) {
|
||||
const idNum = Number(req.params.id);
|
||||
if (Number.isNaN(idNum)) return reply.badRequest("Invalid id");
|
||||
|
||||
const userId = getUserId(req);
|
||||
const userId = getUserId(req, reply);
|
||||
|
||||
// Verify ownership
|
||||
const [existing] = await db.select().from(medications).where(and(eq(medications.id, idNum), eq(medications.userId, userId)));
|
||||
@@ -221,7 +229,7 @@ export async function medicationRoutes(app: FastifyInstance) {
|
||||
const idNum = Number(req.params.id);
|
||||
if (Number.isNaN(idNum)) return reply.badRequest("Invalid id");
|
||||
|
||||
const userId = getUserId(req);
|
||||
const userId = getUserId(req, reply);
|
||||
|
||||
// Delete associated image if exists (with ownership check)
|
||||
const [existing] = await db.select().from(medications).where(and(eq(medications.id, idNum), eq(medications.userId, userId)));
|
||||
@@ -242,7 +250,7 @@ export async function medicationRoutes(app: FastifyInstance) {
|
||||
const idNum = Number(req.params.id);
|
||||
if (Number.isNaN(idNum)) return reply.badRequest("Invalid id");
|
||||
|
||||
const userId = getUserId(req);
|
||||
const userId = getUserId(req, reply);
|
||||
const [existing] = await db.select().from(medications).where(and(eq(medications.id, idNum), eq(medications.userId, userId)));
|
||||
if (!existing) return reply.notFound();
|
||||
|
||||
@@ -276,7 +284,7 @@ export async function medicationRoutes(app: FastifyInstance) {
|
||||
const idNum = Number(req.params.id);
|
||||
if (Number.isNaN(idNum)) return reply.badRequest("Invalid id");
|
||||
|
||||
const userId = getUserId(req);
|
||||
const userId = getUserId(req, reply);
|
||||
const [existing] = await db.select().from(medications).where(and(eq(medications.id, idNum), eq(medications.userId, userId)));
|
||||
if (!existing) return reply.notFound();
|
||||
|
||||
@@ -300,7 +308,7 @@ export async function medicationRoutes(app: FastifyInstance) {
|
||||
return reply.badRequest("Invalid date range");
|
||||
}
|
||||
|
||||
const userId = getUserId(req);
|
||||
const userId = getUserId(req, reply);
|
||||
const rows = await db.select().from(medications).where(eq(medications.userId, userId)).orderBy(medications.id);
|
||||
const now = new Date();
|
||||
|
||||
|
||||
@@ -145,14 +145,27 @@ export async function settingsRoutes(app: FastifyInstance) {
|
||||
// All settings routes require auth
|
||||
app.addHook("preHandler", requireAuth);
|
||||
|
||||
// Get settings for current user
|
||||
app.get("/settings", async (request, reply) => {
|
||||
// Helper to get user ID from request
|
||||
// Returns a default user ID when auth is disabled
|
||||
function getUserId(request: any, reply: any): number {
|
||||
// If auth is disabled, use a default user ID (1)
|
||||
if (!env.AUTH_ENABLED) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
const authUser = request.user as unknown as AuthUser | null;
|
||||
if (!authUser) {
|
||||
return reply.status(401).send({ error: "Not authenticated" });
|
||||
reply.status(401).send({ error: "Not authenticated" });
|
||||
throw new Error("AUTH_REQUIRED");
|
||||
}
|
||||
return authUser.id;
|
||||
}
|
||||
|
||||
const settings = await getOrCreateUserSettings(authUser.id);
|
||||
// Get settings for current user
|
||||
app.get("/settings", async (request, reply) => {
|
||||
const userId = getUserId(request, reply);
|
||||
|
||||
const settings = await getOrCreateUserSettings(userId);
|
||||
|
||||
return reply.send({
|
||||
// User notification settings (from DB)
|
||||
@@ -188,10 +201,7 @@ export async function settingsRoutes(app: FastifyInstance) {
|
||||
|
||||
// Update settings for current user
|
||||
app.put<{ Body: SettingsBody }>("/settings", async (request, reply) => {
|
||||
const authUser = request.user as unknown as AuthUser | null;
|
||||
if (!authUser) {
|
||||
return reply.status(401).send({ error: "Not authenticated" });
|
||||
}
|
||||
const userId = getUserId(request, reply);
|
||||
|
||||
const body = request.body;
|
||||
|
||||
@@ -204,7 +214,7 @@ export async function settingsRoutes(app: FastifyInstance) {
|
||||
const repeatDailyReminders = hasAnyStockReminder ? (body.repeatDailyReminders ?? false) : false;
|
||||
|
||||
// Update or insert user settings
|
||||
const existingSettings = await db.select().from(userSettings).where(eq(userSettings.userId, authUser.id));
|
||||
const existingSettings = await db.select().from(userSettings).where(eq(userSettings.userId, userId));
|
||||
|
||||
const settingsData = {
|
||||
emailEnabled: body.emailEnabled,
|
||||
@@ -227,10 +237,10 @@ export async function settingsRoutes(app: FastifyInstance) {
|
||||
if (existingSettings.length > 0) {
|
||||
await db.update(userSettings)
|
||||
.set(settingsData)
|
||||
.where(eq(userSettings.userId, authUser.id));
|
||||
.where(eq(userSettings.userId, userId));
|
||||
} else {
|
||||
await db.insert(userSettings).values({
|
||||
userId: authUser.id,
|
||||
userId: userId,
|
||||
...settingsData,
|
||||
});
|
||||
}
|
||||
|
||||
+22
-11
@@ -5,6 +5,7 @@ import { db } from "../db/client.js";
|
||||
import { medications, shareTokens } from "../db/schema.js";
|
||||
import { eq, and } from "drizzle-orm";
|
||||
import { requireAuth, optionalAuth } from "../plugins/auth.js";
|
||||
import { env } from "../plugins/env.js";
|
||||
import type { AuthUser } from "../types/fastify.js";
|
||||
|
||||
// =============================================================================
|
||||
@@ -15,6 +16,22 @@ const createShareSchema = z.object({
|
||||
scheduleDays: z.number().int().min(1).max(365).default(30),
|
||||
});
|
||||
|
||||
// Helper to get user ID from request
|
||||
// Returns a default user ID when auth is disabled
|
||||
function getUserId(request: any, reply: any): number {
|
||||
// If auth is disabled, use a default user ID (1)
|
||||
if (!env.AUTH_ENABLED) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
const authUser = request.user as unknown as AuthUser | null;
|
||||
if (!authUser) {
|
||||
reply.status(401).send({ error: "Not authenticated" });
|
||||
throw new Error("AUTH_REQUIRED");
|
||||
}
|
||||
return authUser.id;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Share Routes
|
||||
// =============================================================================
|
||||
@@ -79,10 +96,7 @@ export async function shareRoutes(app: FastifyInstance) {
|
||||
"/share",
|
||||
{ preHandler: requireAuth },
|
||||
async (request, reply) => {
|
||||
const authUser = request.user as unknown as AuthUser | null;
|
||||
if (!authUser) {
|
||||
return reply.status(401).send({ error: "Not authenticated" });
|
||||
}
|
||||
const userId = getUserId(request, reply);
|
||||
|
||||
const parsed = createShareSchema.safeParse(request.body);
|
||||
if (!parsed.success) {
|
||||
@@ -97,7 +111,7 @@ export async function shareRoutes(app: FastifyInstance) {
|
||||
// Check if user has medications for this takenBy
|
||||
const [existingMed] = await db.select().from(medications).where(
|
||||
and(
|
||||
eq(medications.userId, authUser.id),
|
||||
eq(medications.userId, userId),
|
||||
eq(medications.takenBy, takenBy)
|
||||
)
|
||||
);
|
||||
@@ -114,7 +128,7 @@ export async function shareRoutes(app: FastifyInstance) {
|
||||
|
||||
// Create share token
|
||||
await db.insert(shareTokens).values({
|
||||
userId: authUser.id,
|
||||
userId: userId,
|
||||
token,
|
||||
takenBy,
|
||||
scheduleDays,
|
||||
@@ -134,15 +148,12 @@ export async function shareRoutes(app: FastifyInstance) {
|
||||
"/share/people",
|
||||
{ preHandler: requireAuth },
|
||||
async (request, reply) => {
|
||||
const authUser = request.user as unknown as AuthUser | null;
|
||||
if (!authUser) {
|
||||
return reply.status(401).send({ error: "Not authenticated" });
|
||||
}
|
||||
const userId = getUserId(request, reply);
|
||||
|
||||
// Get all unique takenBy values for this user
|
||||
const meds = await db.select({ takenBy: medications.takenBy })
|
||||
.from(medications)
|
||||
.where(eq(medications.userId, authUser.id));
|
||||
.where(eq(medications.userId, userId));
|
||||
|
||||
const uniquePeople = [...new Set(meds.map((m) => m.takenBy).filter(Boolean))] as string[];
|
||||
|
||||
|
||||
Reference in New Issue
Block a user