mirror of
https://github.com/open-reception/appointment-booking-software.git
synced 2026-09-10 03:07:39 +02:00
224 remove sensitive information from logs (#225)
* Logging adapted * Fix log mocks * Lint fixes
This commit is contained in:
@@ -208,11 +208,7 @@ export class UnifiedAppointmentCrypto {
|
||||
// Note: clientKeyShare will be used during actual appointment creation
|
||||
await this.encryptTunnelKeyForClient();
|
||||
|
||||
console.log("✅ New client initialized", {
|
||||
tunnelId: this.tunnelId,
|
||||
emailHashPrefix: this.emailHash.slice(0, 8),
|
||||
staffCount: staffPublicKeys.length,
|
||||
});
|
||||
console.log("✅ New client initialized", {});
|
||||
|
||||
this.clientAuthenticated = true;
|
||||
} catch (error) {
|
||||
|
||||
@@ -13,9 +13,7 @@ export class AuthorizationService {
|
||||
}
|
||||
|
||||
if (user.role !== requiredRole) {
|
||||
logger.warn(
|
||||
`Access denied: User ${user.email} has role ${user.role}, required ${requiredRole}`,
|
||||
);
|
||||
logger.warn(`Access denied: User ${user.id} has role ${user.role}, required ${requiredRole}`);
|
||||
throw new AuthorizationError();
|
||||
}
|
||||
|
||||
@@ -29,7 +27,7 @@ export class AuthorizationService {
|
||||
|
||||
if (!allowedRoles.includes(user.role as UserRole)) {
|
||||
logger.warn(
|
||||
`Access denied: User ${user.email} has role ${user.role}, allowed roles: ${allowedRoles.join(", ")}`,
|
||||
`Access denied: User ${user.id} has role ${user.role}, allowed roles: ${allowedRoles.join(", ")}`,
|
||||
);
|
||||
throw new AuthorizationError();
|
||||
}
|
||||
@@ -51,13 +49,13 @@ export class AuthorizationService {
|
||||
|
||||
if (user.role === "TENANT_ADMIN" || user.role === "STAFF") {
|
||||
if (!user.tenantId) {
|
||||
logger.warn(`Access denied: User ${user.email} has no tenant assigned`);
|
||||
logger.warn(`Access denied: User ${user.id} has no tenant assigned`);
|
||||
throw new AuthorizationError("No tenant access");
|
||||
}
|
||||
|
||||
if (user.tenantId !== tenantId) {
|
||||
logger.warn(
|
||||
`Access denied: User ${user.email} trying to access tenant ${tenantId}, but belongs to ${user.tenantId}`,
|
||||
`Access denied: User ${user.id} trying to access tenant ${tenantId}, but belongs to ${user.tenantId}`,
|
||||
);
|
||||
throw new AuthorizationError("Tenant access denied");
|
||||
}
|
||||
@@ -66,7 +64,7 @@ export class AuthorizationService {
|
||||
return;
|
||||
}
|
||||
|
||||
logger.warn(`Access denied: User ${user.email} has invalid role ${user.role}`);
|
||||
logger.warn(`Access denied: User ${user.id} has invalid role ${user.role}`);
|
||||
throw new AuthorizationError("Invalid role");
|
||||
}
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ export class SessionService {
|
||||
userAgent?: string,
|
||||
passkeyId?: string,
|
||||
): Promise<SessionData> {
|
||||
logger.info(`Creating session for user: ${userId}`);
|
||||
logger.debug(`Creating session for user: ${userId}`);
|
||||
|
||||
const existingUser = await db.select().from(user).where(eq(user.id, userId)).limit(1);
|
||||
|
||||
@@ -88,7 +88,7 @@ export class SessionService {
|
||||
|
||||
await db.update(user).set({ lastLoginAt: new Date() }).where(eq(user.id, userId));
|
||||
|
||||
logger.info(`Session created successfully for user: ${userId}`, { passkeyId });
|
||||
logger.debug(`Session created successfully for user: ${userId}`, { passkeyId });
|
||||
|
||||
return {
|
||||
sessionToken: updatedSession.sessionToken,
|
||||
@@ -246,7 +246,7 @@ export class SessionService {
|
||||
})
|
||||
.where(eq(userSession.id, session.user_session.id));
|
||||
|
||||
logger.info("Session tokens refreshed successfully");
|
||||
logger.debug("Session tokens refreshed successfully");
|
||||
|
||||
return {
|
||||
accessToken: newTokens.accessToken,
|
||||
@@ -256,27 +256,27 @@ export class SessionService {
|
||||
}
|
||||
|
||||
static async logout(sessionToken: string): Promise<void> {
|
||||
logger.info(`Logging out session: ${sessionToken}`);
|
||||
logger.debug(`Logging out session: ${sessionToken}`);
|
||||
|
||||
await db.delete(userSession).where(eq(userSession.sessionToken, sessionToken));
|
||||
|
||||
logger.info(`Session logged out successfully: ${sessionToken}`);
|
||||
logger.debug(`Session logged out successfully: ${sessionToken}`);
|
||||
}
|
||||
|
||||
static async logoutAllSessions(userId: string): Promise<void> {
|
||||
logger.info(`Logging out all sessions for user: ${userId}`);
|
||||
logger.debug(`Logging out all sessions for user: ${userId}`);
|
||||
|
||||
await db.delete(userSession).where(eq(userSession.userId, userId));
|
||||
|
||||
logger.info(`All sessions logged out for user: ${userId}`);
|
||||
logger.debug(`All sessions logged out for user: ${userId}`);
|
||||
}
|
||||
|
||||
static async cleanupExpiredSessions(): Promise<void> {
|
||||
logger.info("Cleaning up expired sessions");
|
||||
logger.debug("Cleaning up expired sessions");
|
||||
|
||||
await db.delete(userSession).where(lt(userSession.expiresAt, new Date()));
|
||||
|
||||
logger.info("Expired sessions cleaned up");
|
||||
logger.debug("Expired sessions cleaned up");
|
||||
}
|
||||
|
||||
static async getUserSession(sessionId: string): Promise<SelectUserSession | null> {
|
||||
@@ -309,10 +309,10 @@ export class SessionService {
|
||||
}
|
||||
|
||||
static async revokeSession(sessionId: string): Promise<void> {
|
||||
logger.info(`Revoking session: ${sessionId}`);
|
||||
logger.debug(`Revoking session: ${sessionId}`);
|
||||
|
||||
await db.delete(userSession).where(eq(userSession.id, sessionId));
|
||||
|
||||
logger.info(`Session revoked: ${sessionId}`);
|
||||
logger.debug(`Session revoked: ${sessionId}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -155,8 +155,7 @@ export class WebAuthnService {
|
||||
})
|
||||
.where(eq(userPasskey.id, normalizedCredentialId));
|
||||
|
||||
logger.info("WebAuthn authentication successful", {
|
||||
credentialId: normalizedCredentialId.substring(0, 20) + "...",
|
||||
logger.debug("WebAuthn authentication successful", {
|
||||
userId: passkey.userId,
|
||||
counterUpdated: `${passkey.counter} → ${newCounter}`,
|
||||
});
|
||||
@@ -244,12 +243,9 @@ export class WebAuthnService {
|
||||
"base64",
|
||||
);
|
||||
|
||||
logger.info("Registration verified successfully", {
|
||||
credentialId: normalizedCredentialId.substring(0, 20) + "...",
|
||||
credentialIdLength: normalizedCredentialId.length,
|
||||
logger.debug("Registration verified successfully", {
|
||||
counter: registrationInfo.credential.counter,
|
||||
publicKeyLength: registrationInfo.credential.publicKey.length,
|
||||
publicKeyBytesFirst10: Array.from(registrationInfo.credential.publicKey.slice(0, 10)),
|
||||
});
|
||||
|
||||
return {
|
||||
|
||||
@@ -2,6 +2,7 @@ import nodemailer from "nodemailer";
|
||||
import { env } from "$env/dynamic/private";
|
||||
import type Mail from "nodemailer/lib/mailer";
|
||||
import type { SelectUser } from "../db/central-schema";
|
||||
import logger from "$lib/logger";
|
||||
|
||||
/**
|
||||
* Simple client data type for email sending
|
||||
@@ -92,10 +93,11 @@ export async function sendEmail(
|
||||
textContent: string,
|
||||
): Promise<void> {
|
||||
const transporter = createTransporter();
|
||||
logger.setContext("sendMail");
|
||||
|
||||
if (!recipient.email) {
|
||||
// Recipient has not stored an email address, so no mail will be send
|
||||
// TODO: Log this event
|
||||
logger.info("Recipient has no email address, skipping email sending");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -121,9 +123,9 @@ export async function sendEmail(
|
||||
|
||||
try {
|
||||
await transporter.sendMail(mailOptions);
|
||||
console.log(`Email sent successfully to ${recipient.email}`);
|
||||
logger.debug(`Email sent successfully to ${recipient.email}`);
|
||||
} catch (error) {
|
||||
console.error("Failed to send email:", error);
|
||||
logger.error("Failed to send email:", { error });
|
||||
throw new Error(`Failed to send email to ${recipient.email}`);
|
||||
}
|
||||
}
|
||||
@@ -134,11 +136,12 @@ export async function sendEmail(
|
||||
*/
|
||||
export async function testEmailConnection(): Promise<boolean> {
|
||||
try {
|
||||
logger.setContext("testEmailConnection");
|
||||
const transporter = createTransporter();
|
||||
await transporter.verify();
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error("SMTP connection test failed:", error);
|
||||
logger.error("SMTP connection test failed:", { error });
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -513,11 +513,10 @@ export class AppointmentService {
|
||||
): Promise<AppointmentResponse> {
|
||||
const log = logger.setContext("AppointmentService");
|
||||
|
||||
log.info("Adding appointment to existing tunnel", {
|
||||
log.debug("Adding appointment to existing tunnel", {
|
||||
tenantId: this.tenantId,
|
||||
tunnelId: appointmentData.tunnelId,
|
||||
appointmentDate: appointmentData.appointmentDate,
|
||||
emailHashPrefix: appointmentData.emailHash.slice(0, 8),
|
||||
});
|
||||
|
||||
const db = await this.getDb();
|
||||
@@ -593,7 +592,7 @@ export class AppointmentService {
|
||||
requiresConfirmation,
|
||||
};
|
||||
|
||||
log.info("Successfully added appointment to tunnel", {
|
||||
log.debug("Successfully added appointment to tunnel", {
|
||||
tenantId: this.tenantId,
|
||||
tunnelId: appointmentData.tunnelId,
|
||||
appointmentId: result.id,
|
||||
@@ -611,11 +610,10 @@ export class AppointmentService {
|
||||
): Promise<AppointmentResponse> {
|
||||
const log = logger.setContext("AppointmentService");
|
||||
|
||||
log.info("Creating new client appointment tunnel", {
|
||||
log.debug("Creating new client appointment tunnel", {
|
||||
tenantId: this.tenantId,
|
||||
tunnelId: clientData.tunnelId,
|
||||
appointmentDate: clientData.appointmentDate,
|
||||
emailHashPrefix: clientData.emailHash.slice(0, 8),
|
||||
});
|
||||
|
||||
// Check if there are any authorized users (ACCESS_GRANTED) in this tenant
|
||||
@@ -764,7 +762,7 @@ export class AppointmentService {
|
||||
requiresConfirmation: result.requiresConfirmation,
|
||||
};
|
||||
|
||||
log.info("Successfully created new client appointment tunnel", {
|
||||
log.debug("Successfully created new client appointment tunnel", {
|
||||
tenantId: this.tenantId,
|
||||
tunnelId: clientData.tunnelId,
|
||||
appointmentId: result.appointment.id,
|
||||
@@ -955,10 +953,9 @@ export class AppointmentService {
|
||||
challengeResponse: string,
|
||||
): Promise<void> {
|
||||
const log = logger.setContext("AppointmentService");
|
||||
log.info("Client deleting appointment with authentication", {
|
||||
log.debug("Client deleting appointment with authentication", {
|
||||
tenantId: this.tenantId,
|
||||
appointmentId,
|
||||
emailHashPrefix: emailHash.slice(0, 8),
|
||||
});
|
||||
|
||||
// 1. Verify challenge-response
|
||||
@@ -1058,7 +1055,7 @@ export class AppointmentService {
|
||||
// 4. Delete the appointment
|
||||
await db.delete(tenantSchema.appointment).where(eq(tenantSchema.appointment.id, appointmentId));
|
||||
|
||||
log.info("Appointment deleted successfully by client", {
|
||||
log.debug("Appointment deleted successfully by client", {
|
||||
tenantId: this.tenantId,
|
||||
appointmentId,
|
||||
emailHashPrefix: emailHash.slice(0, 8),
|
||||
@@ -1073,7 +1070,7 @@ export class AppointmentService {
|
||||
},
|
||||
});
|
||||
|
||||
log.info("Staff notification sent for client-initiated deletion", {
|
||||
log.debug("Staff notification sent for client-initiated deletion", {
|
||||
tenantId: this.tenantId,
|
||||
appointmentId,
|
||||
channelId: appointment.channelId,
|
||||
|
||||
@@ -194,8 +194,6 @@ export class CentralDatabaseMigrationService {
|
||||
|
||||
logger.info("Central database created and initialized", {
|
||||
database: config.database,
|
||||
host: config.host,
|
||||
port: config.port,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -90,8 +90,6 @@ export class ClientPinResetService {
|
||||
.returning({ token: tenantSchema.clientPinResetToken.token });
|
||||
|
||||
log.info("PIN reset token created", {
|
||||
emailHash: emailHash.slice(0, 8),
|
||||
tokenId: resetToken.token.slice(0, 8),
|
||||
expiresAt,
|
||||
});
|
||||
|
||||
@@ -226,9 +224,7 @@ export class ClientPinResetService {
|
||||
.where(eq(tenantSchema.clientPinResetToken.token, token));
|
||||
|
||||
log.info("PIN reset completed successfully", {
|
||||
tokenId: token.slice(0, 8),
|
||||
tunnelId: tunnel.id,
|
||||
emailHash: emailHash.slice(0, 8),
|
||||
});
|
||||
|
||||
return tunnel.id;
|
||||
|
||||
@@ -39,13 +39,10 @@ export class InviteService {
|
||||
|
||||
const [createdInvite] = await db.insert(userInvite).values(inviteData).returning();
|
||||
|
||||
logger.info("User invitation created", {
|
||||
logger.debug("User invitation created", {
|
||||
inviteId: createdInvite.id,
|
||||
inviteCode: createdInvite.inviteCode,
|
||||
email: createdInvite.email,
|
||||
tenantId: createdInvite.tenantId,
|
||||
role: createdInvite.role,
|
||||
invitedBy: createdInvite.invitedBy,
|
||||
});
|
||||
|
||||
return createdInvite;
|
||||
@@ -124,7 +121,6 @@ export class InviteService {
|
||||
logger.info("Invitation marked as used", {
|
||||
inviteCode,
|
||||
createdUserId,
|
||||
email: updatedInvite.email,
|
||||
});
|
||||
|
||||
return updatedInvite;
|
||||
|
||||
@@ -97,7 +97,6 @@ export class StaffCryptoService {
|
||||
log.info("Staff keypair stored successfully", {
|
||||
tenantId,
|
||||
userId,
|
||||
passkeyId,
|
||||
hasPublicKey: !!publicKey,
|
||||
hasPrivateKeyShare: !!privateKeyShare,
|
||||
});
|
||||
@@ -321,7 +320,6 @@ export class StaffCryptoService {
|
||||
log.info("Staff crypto deletion completed", {
|
||||
tenantId,
|
||||
userId,
|
||||
passkeyId,
|
||||
deleted,
|
||||
});
|
||||
|
||||
|
||||
@@ -250,7 +250,7 @@ export class StaffService {
|
||||
deletedKeySharesCount,
|
||||
};
|
||||
|
||||
logger.info("Staff member deleted successfully", {
|
||||
logger.debug("Staff member deleted successfully", {
|
||||
staffId,
|
||||
tenantId,
|
||||
deletedUser: userDeletionResult.deletedUser,
|
||||
|
||||
@@ -660,7 +660,6 @@ export class UserService {
|
||||
|
||||
log.info("User deleted successfully", {
|
||||
userId,
|
||||
deletedUser: deletedUsers[0],
|
||||
deletedPasskeysCount,
|
||||
tenantId: user.tenantId,
|
||||
});
|
||||
|
||||
@@ -36,8 +36,8 @@ const createPinThrottleStore = () => {
|
||||
// Throttle expired, clear storage
|
||||
localStorage.removeItem(STORAGE_KEY);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Failed to parse throttle state:", e);
|
||||
} catch (error) {
|
||||
console.error("Failed to parse throttle state:", error);
|
||||
localStorage.removeItem(STORAGE_KEY);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import AppointmentBooked from "$lib/emails/AppointmentBooked.svelte";
|
||||
import { renderOutputToHtml, htmlToText } from "$lib/emails/utils";
|
||||
import { renderOutputToHtml } from "$lib/emails/utils";
|
||||
import type { SelectTenant } from "$lib/server/db/central-schema";
|
||||
import type { SelectAppointment } from "$lib/server/db/tenant-schema";
|
||||
import type { RequestHandler } from "@sveltejs/kit";
|
||||
@@ -30,10 +30,6 @@ export const GET: RequestHandler = async () => {
|
||||
},
|
||||
});
|
||||
const html = renderOutputToHtml(emailRender);
|
||||
const text = htmlToText(html);
|
||||
|
||||
// Output for testing purposes
|
||||
console.log(text);
|
||||
return new Response(html, {
|
||||
headers: {
|
||||
"Content-Type": "text/html",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import AppointmentReminder from "$lib/emails/AppointmentReminder.svelte";
|
||||
import { renderOutputToHtml, htmlToText } from "$lib/emails/utils";
|
||||
import { renderOutputToHtml } from "$lib/emails/utils";
|
||||
import type { SelectTenant } from "$lib/server/db/central-schema";
|
||||
import type { SelectAppointment } from "$lib/server/db/tenant-schema";
|
||||
import type { RequestHandler } from "@sveltejs/kit";
|
||||
@@ -30,10 +30,6 @@ export const GET: RequestHandler = async () => {
|
||||
},
|
||||
});
|
||||
const html = renderOutputToHtml(emailRender);
|
||||
const text = htmlToText(html);
|
||||
|
||||
// Output for testing purposes
|
||||
console.log(text);
|
||||
return new Response(html, {
|
||||
headers: {
|
||||
"Content-Type": "text/html",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import AppointmentRequested from "$lib/emails/AppointmentRequest.svelte";
|
||||
import { renderOutputToHtml, htmlToText } from "$lib/emails/utils";
|
||||
import { renderOutputToHtml } from "$lib/emails/utils";
|
||||
import type { SelectTenant } from "$lib/server/db/central-schema";
|
||||
import type { SelectAppointment } from "$lib/server/db/tenant-schema";
|
||||
import type { RequestHandler } from "@sveltejs/kit";
|
||||
@@ -29,10 +29,6 @@ export const GET: RequestHandler = async () => {
|
||||
},
|
||||
});
|
||||
const html = renderOutputToHtml(emailRender);
|
||||
const text = htmlToText(html);
|
||||
|
||||
// Output for testing purposes
|
||||
console.log(text);
|
||||
return new Response(html, {
|
||||
headers: {
|
||||
"Content-Type": "text/html",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import Confirmation from "$lib/emails/Confirmation.svelte";
|
||||
import { htmlToText, renderOutputToHtml } from "$lib/emails/utils";
|
||||
import { renderOutputToHtml } from "$lib/emails/utils";
|
||||
import type { RequestHandler } from "@sveltejs/kit";
|
||||
import { render } from "svelte/server";
|
||||
|
||||
@@ -17,10 +17,6 @@ export const GET: RequestHandler = async () => {
|
||||
},
|
||||
});
|
||||
const html = renderOutputToHtml(emailRender);
|
||||
const text = htmlToText(html);
|
||||
|
||||
// Output for testing purposes
|
||||
console.log(text);
|
||||
return new Response(html, {
|
||||
headers: {
|
||||
"Content-Type": "text/html",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import PinReset from "$lib/emails/PinReset.svelte";
|
||||
import { renderOutputToHtml, htmlToText } from "$lib/emails/utils";
|
||||
import { renderOutputToHtml } from "$lib/emails/utils";
|
||||
import type { SelectTenant } from "$lib/server/db/central-schema";
|
||||
import type { RequestHandler } from "@sveltejs/kit";
|
||||
import { render } from "svelte/server";
|
||||
@@ -17,10 +17,7 @@ export const GET: RequestHandler = async () => {
|
||||
},
|
||||
});
|
||||
const html = renderOutputToHtml(emailRender);
|
||||
const text = htmlToText(html);
|
||||
|
||||
// Output for testing purposes
|
||||
console.log(text);
|
||||
return new Response(html, {
|
||||
headers: {
|
||||
"Content-Type": "text/html",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import UserInvite from "$lib/emails/UserInvite.svelte";
|
||||
import { htmlToText, renderOutputToHtml } from "$lib/emails/utils";
|
||||
import { renderOutputToHtml } from "$lib/emails/utils";
|
||||
import type { SelectTenant } from "$lib/server/db/central-schema";
|
||||
import type { RequestHandler } from "@sveltejs/kit";
|
||||
import { render } from "svelte/server";
|
||||
@@ -19,10 +19,6 @@ export const GET: RequestHandler = async () => {
|
||||
},
|
||||
});
|
||||
const html = renderOutputToHtml(emailRender);
|
||||
const text = htmlToText(html);
|
||||
|
||||
// Output for testing purposes
|
||||
console.log(text);
|
||||
return new Response(html, {
|
||||
headers: {
|
||||
"Content-Type": "text/html",
|
||||
|
||||
@@ -148,14 +148,12 @@
|
||||
|
||||
prfOutput = prfOutputResp;
|
||||
logger.info("PRF output retrieved successfully", {
|
||||
passkeyId: passkeyResp.id,
|
||||
prfOutputLength: prfOutputResp.byteLength,
|
||||
});
|
||||
} catch (error) {
|
||||
$passkeyLoading = "error";
|
||||
logger.error("Failed to get PRF output", {
|
||||
email: $formData.email,
|
||||
passkeyId: passkeyResp.id,
|
||||
error,
|
||||
});
|
||||
toast.error(m["setupPasskey.errorGettingPrfOutput"]());
|
||||
|
||||
@@ -74,7 +74,7 @@
|
||||
staffKeyShare: item.appointment.staffKeyShare,
|
||||
});
|
||||
} catch (err) {
|
||||
console.error("Error decrypting appointment:", err);
|
||||
console.error("Error decrypting appointment:", item.id);
|
||||
error = err instanceof Error ? err.message : "Decryption failed";
|
||||
}
|
||||
};
|
||||
|
||||
@@ -137,13 +137,11 @@
|
||||
if (credentialResp.prfOutput) {
|
||||
prfOutputBase64 = arrayBufferToBase64(credentialResp.prfOutput);
|
||||
logger.info("PRF output retrieved from login", {
|
||||
passkeyId,
|
||||
prfOutputLength: credentialResp.prfOutput.byteLength,
|
||||
});
|
||||
} else {
|
||||
logger.warn("No PRF output in login response - crypto features may not work", {
|
||||
email: $formData.email,
|
||||
passkeyId,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -206,8 +206,7 @@ export const POST: RequestHandler = async ({ request, locals }) => {
|
||||
// Send invitation email
|
||||
await sendUserInviteEmail(email, name, tenant, role, registrationUrl, language);
|
||||
|
||||
logger.info("User invitation sent successfully", {
|
||||
invitedBy: locals.user.id,
|
||||
logger.debug("User invitation sent successfully", {
|
||||
invitedEmail: email,
|
||||
tenantId,
|
||||
role,
|
||||
|
||||
@@ -284,7 +284,6 @@ export const POST: RequestHandler = async ({ request, cookies, getClientAddress,
|
||||
|
||||
logger.info("Login successful", {
|
||||
userId: sessionData.user.id,
|
||||
email: sessionData.user.email,
|
||||
role: sessionData.user.role,
|
||||
authMethod: body.passphrase ? "passphrase" : "webauthn",
|
||||
});
|
||||
|
||||
@@ -149,7 +149,6 @@ export async function DELETE({ params, locals }: RequestEvent) {
|
||||
log.info("Passkey deleted successfully", {
|
||||
passkeyId,
|
||||
userId,
|
||||
deviceName: deletedPasskey.deviceName,
|
||||
});
|
||||
|
||||
// If user has a tenant (STAFF or TENANT_ADMIN), also delete associated crypto data (CASCADE)
|
||||
@@ -165,7 +164,6 @@ export async function DELETE({ params, locals }: RequestEvent) {
|
||||
);
|
||||
|
||||
log.info("Staff crypto data cascaded deletion completed", {
|
||||
passkeyId,
|
||||
userId,
|
||||
tenantId,
|
||||
deleted,
|
||||
|
||||
@@ -262,11 +262,9 @@ export async function POST({ request, params, locals }: RequestEvent) {
|
||||
privateKeyShare,
|
||||
);
|
||||
|
||||
log.info("Crypto keys stored successfully", {
|
||||
passkeyId,
|
||||
log.debug("Crypto keys stored successfully", {
|
||||
userId,
|
||||
tenantId,
|
||||
prfHash: prfHash.substring(0, 16) + "...",
|
||||
});
|
||||
|
||||
return json({
|
||||
|
||||
Vendored
-2
@@ -11,8 +11,6 @@ export async function GET() {
|
||||
envOkay = false;
|
||||
if (!process.env.DATABASE_URL?.startsWith("postgres:")) envOkay = false;
|
||||
|
||||
console.warn("Environment is okay", envOkay);
|
||||
|
||||
return json({
|
||||
envOkay,
|
||||
});
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { dev } from "$app/environment";
|
||||
import logger from "$lib/logger";
|
||||
import { db } from "$lib/server/db";
|
||||
import { tenant } from "$lib/server/db/central-schema";
|
||||
import { eq } from "drizzle-orm";
|
||||
@@ -30,7 +31,9 @@ export const getTenantIdByDomain = async (
|
||||
.limit(1);
|
||||
return tenants[0]?.id || null;
|
||||
} catch (error) {
|
||||
console.log("error", error);
|
||||
logger
|
||||
.setContext("API.Public.Utils")
|
||||
.error("Failed to get tenant ID by domain", { domain, error });
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -497,7 +497,7 @@ export const DELETE: RequestHandler = async ({ params, locals }) => {
|
||||
throw new ValidationError(ERRORS.TENANTS.NO_TENANT_ID);
|
||||
}
|
||||
|
||||
log.info("Attempting tenant deletion", {
|
||||
log.debug("Attempting tenant deletion", {
|
||||
tenantId,
|
||||
requestedBy: locals.user?.id,
|
||||
userRole: locals.user?.role,
|
||||
|
||||
+2
-3
@@ -133,7 +133,7 @@ export const DELETE: RequestHandler = async ({ request, params }) => {
|
||||
const body = await request.json();
|
||||
const { emailHash, challengeId, challengeResponse } = requestSchema.parse(body);
|
||||
|
||||
log.info("Client deleting appointment", {
|
||||
log.debug("Client deleting appointment", {
|
||||
tenantId,
|
||||
appointmentId,
|
||||
emailHashPrefix: emailHash.slice(0, 8),
|
||||
@@ -148,10 +148,9 @@ export const DELETE: RequestHandler = async ({ request, params }) => {
|
||||
challengeResponse,
|
||||
);
|
||||
|
||||
log.info("Appointment deleted successfully by client", {
|
||||
log.debug("Appointment deleted successfully by client", {
|
||||
tenantId,
|
||||
appointmentId,
|
||||
emailHashPrefix: emailHash.slice(0, 8),
|
||||
});
|
||||
|
||||
return json({
|
||||
|
||||
@@ -123,14 +123,13 @@ export const POST: RequestHandler = async ({ params, request, locals }) => {
|
||||
const body = await request.json();
|
||||
const { clientEmail, clientLanguage } = requestSchema.parse(body);
|
||||
|
||||
log.info("Denying appointment", {
|
||||
log.debug("Denying appointment", {
|
||||
tenantId,
|
||||
appointmentId,
|
||||
clientEmailPrefix: clientEmail ? clientEmail.slice(0, 3) : undefined,
|
||||
});
|
||||
|
||||
await appointmentService.denyAppointment(appointmentId, clientEmail, clientLanguage);
|
||||
log.info("Appointment denied successfully", {
|
||||
log.debug("Appointment denied successfully", {
|
||||
tenantId,
|
||||
appointmentId,
|
||||
});
|
||||
|
||||
+1
@@ -16,6 +16,7 @@ vi.mock("$lib/server/utils/permissions", () => ({
|
||||
vi.mock("$lib/logger", () => ({
|
||||
default: {
|
||||
setContext: vi.fn(() => ({
|
||||
debug: vi.fn(),
|
||||
info: vi.fn(),
|
||||
error: vi.fn(),
|
||||
})),
|
||||
|
||||
@@ -208,11 +208,10 @@ export const POST: RequestHandler = async ({ request, params }) => {
|
||||
throw new ValidationError("Bees incoming");
|
||||
}
|
||||
|
||||
logger.info("Adding appointment to existing tunnel", {
|
||||
logger.debug("Adding appointment to existing tunnel", {
|
||||
tenantId,
|
||||
tunnelId: validatedData.tunnelId,
|
||||
appointmentDate: validatedData.appointmentDate,
|
||||
emailHashPrefix: validatedData.emailHash.slice(0, 8),
|
||||
});
|
||||
|
||||
const db = await getTenantDb(tenantId);
|
||||
@@ -230,7 +229,6 @@ export const POST: RequestHandler = async ({ request, params }) => {
|
||||
logger.warn("Client tunnel not found", {
|
||||
tenantId,
|
||||
tunnelId: validatedData.tunnelId,
|
||||
emailHashPrefix: validatedData.emailHash.slice(0, 8),
|
||||
});
|
||||
throw new NotFoundError("Tunnel not found or access denied");
|
||||
}
|
||||
@@ -335,14 +333,14 @@ export const POST: RequestHandler = async ({ request, params }) => {
|
||||
|
||||
if (requiresConfirmation) {
|
||||
await sendAppointmentRequestEmail(clientData, tenant, result, channelTitle);
|
||||
logger.info("Appointment request email sent", {
|
||||
logger.debug("Appointment request email sent", {
|
||||
tunnelId: validatedData.tunnelId,
|
||||
appointmentId: result.id,
|
||||
tenantId,
|
||||
});
|
||||
} else {
|
||||
await sendAppointmentCreatedEmail(clientData, tenant, result, channelTitle);
|
||||
logger.info("Appointment confirmation email sent", {
|
||||
logger.debug("Appointment confirmation email sent", {
|
||||
tunnelId: validatedData.tunnelId,
|
||||
appointmentId: result.id,
|
||||
tenantId,
|
||||
@@ -352,7 +350,6 @@ export const POST: RequestHandler = async ({ request, params }) => {
|
||||
}
|
||||
} catch (emailError) {
|
||||
logger.error("Failed to send appointment notification email", {
|
||||
tunnelId: validatedData.tunnelId,
|
||||
appointmentId: result.id,
|
||||
tenantId,
|
||||
error: String(emailError),
|
||||
|
||||
@@ -144,7 +144,6 @@ export const POST: RequestHandler = async ({ request, params }) => {
|
||||
if (!throttleResult.allowed) {
|
||||
logger.warn("PIN challenge throttled", {
|
||||
tenantId,
|
||||
emailHashPrefix: emailHash.slice(0, 8),
|
||||
retryAfterMs: throttleResult.retryAfterMs,
|
||||
failedAttempts: throttleResult.failedAttempts,
|
||||
});
|
||||
@@ -163,7 +162,7 @@ export const POST: RequestHandler = async ({ request, params }) => {
|
||||
);
|
||||
}
|
||||
|
||||
logger.info("Creating challenge for existing client", {
|
||||
logger.debug("Creating challenge for existing client", {
|
||||
tenantId,
|
||||
emailHashPrefix: emailHash.slice(0, 8),
|
||||
});
|
||||
@@ -184,7 +183,6 @@ export const POST: RequestHandler = async ({ request, params }) => {
|
||||
if (tunnelResult.length === 0) {
|
||||
logger.warn("Client tunnel not found for challenge", {
|
||||
tenantId,
|
||||
emailHashPrefix: emailHash.slice(0, 8),
|
||||
});
|
||||
return json({ error: "Client not found" }, { status: 404 });
|
||||
}
|
||||
@@ -223,7 +221,7 @@ export const POST: RequestHandler = async ({ request, params }) => {
|
||||
privateKeyShare: tunnel.privateKeyShare,
|
||||
};
|
||||
|
||||
logger.info("Successfully created challenge", {
|
||||
logger.debug("Successfully created challenge", {
|
||||
tenantId,
|
||||
tunnelId: tunnel.id,
|
||||
challengeLength: challenge.length,
|
||||
|
||||
@@ -233,12 +233,9 @@ export const POST: RequestHandler = async ({ request, params }) => {
|
||||
throw new ValidationError("Bees incoming");
|
||||
}
|
||||
|
||||
logger.info("Creating new client appointment tunnel", {
|
||||
logger.debug("Creating new client appointment tunnel", {
|
||||
tenantId,
|
||||
tunnelId: validatedData.tunnelId,
|
||||
appointmentDate: validatedData.appointmentDate,
|
||||
duration: validatedData.duration,
|
||||
emailHashPrefix: validatedData.emailHash.slice(0, 8),
|
||||
});
|
||||
|
||||
const appointmentService = await AppointmentService.forTenant(tenantId);
|
||||
|
||||
+4
-12
@@ -13,6 +13,7 @@ vi.mock("$lib/server/services/appointment-service", () => ({
|
||||
|
||||
vi.mock("$lib/logger", () => ({
|
||||
logger: {
|
||||
debug: vi.fn(),
|
||||
info: vi.fn(),
|
||||
warn: vi.fn(),
|
||||
error: vi.fn(),
|
||||
@@ -96,12 +97,9 @@ describe("Create New Client API Route", () => {
|
||||
expect(response.status).toBe(200);
|
||||
expect(data).toEqual(mockAppointment);
|
||||
expect(mockService.createNewClientWithAppointment).toHaveBeenCalledWith(validRequestBody);
|
||||
expect(logger.info).toHaveBeenCalledWith("Creating new client appointment tunnel", {
|
||||
expect(logger.debug).toHaveBeenCalledWith("Creating new client appointment tunnel", {
|
||||
tenantId: mockTenantId,
|
||||
tunnelId: mockTunnelId,
|
||||
appointmentDate: validRequestBody.appointmentDate,
|
||||
duration: validRequestBody.duration,
|
||||
emailHashPrefix: "test-ema",
|
||||
});
|
||||
});
|
||||
|
||||
@@ -195,12 +193,9 @@ describe("Create New Client API Route", () => {
|
||||
expect(data.error).toBe(
|
||||
"Cannot create client appointments: No authorized users found in tenant",
|
||||
);
|
||||
expect(logger.info).toHaveBeenCalledWith("Creating new client appointment tunnel", {
|
||||
expect(logger.debug).toHaveBeenCalledWith("Creating new client appointment tunnel", {
|
||||
tenantId: mockTenantId,
|
||||
tunnelId: mockTunnelId,
|
||||
appointmentDate: validRequestBody.appointmentDate,
|
||||
duration: validRequestBody.duration,
|
||||
emailHashPrefix: "test-ema",
|
||||
});
|
||||
});
|
||||
|
||||
@@ -256,12 +251,9 @@ describe("Create New Client API Route", () => {
|
||||
const event = createMockRequestEvent();
|
||||
await POST(event);
|
||||
|
||||
expect(logger.info).toHaveBeenCalledWith("Creating new client appointment tunnel", {
|
||||
expect(logger.debug).toHaveBeenCalledWith("Creating new client appointment tunnel", {
|
||||
tenantId: mockTenantId,
|
||||
tunnelId: mockTunnelId,
|
||||
appointmentDate: validRequestBody.appointmentDate,
|
||||
duration: 10,
|
||||
emailHashPrefix: "test-ema", // First 8 chars of "test-email-hash"
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -376,7 +376,6 @@ export const POST: RequestHandler = async ({ params, request, locals }) => {
|
||||
|
||||
logger.info("PIN reset token created for new client", {
|
||||
tenantId,
|
||||
tokenId: pinResetToken.slice(0, 8),
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error("Failed to create PIN reset token for new client", {
|
||||
|
||||
@@ -246,8 +246,6 @@ export const POST: RequestHandler = async ({ params, locals, request }) => {
|
||||
log.info("No new key shares to add - all already exist", {
|
||||
tenantId,
|
||||
staffUserId,
|
||||
totalRequested: keyShares.length,
|
||||
previousConfirmationState: staffUser[0].confirmationState,
|
||||
});
|
||||
|
||||
await ensureAccessGranted();
|
||||
@@ -284,12 +282,11 @@ export const POST: RequestHandler = async ({ params, locals, request }) => {
|
||||
|
||||
await ensureAccessGranted();
|
||||
|
||||
log.info("Staff key shares added successfully", {
|
||||
log.debug("Staff key shares added successfully", {
|
||||
tenantId,
|
||||
staffUserId,
|
||||
addedCount: result.length,
|
||||
skippedCount: duplicateKeyShares.length,
|
||||
requesterId: locals.user?.id,
|
||||
});
|
||||
|
||||
return json({
|
||||
|
||||
@@ -133,7 +133,7 @@ export const POST: RequestHandler = async ({ request, params }) => {
|
||||
const body = await request.json();
|
||||
const { challengeId, challengeResponse } = requestSchema.parse(body);
|
||||
|
||||
logger.info("Verifying challenge for existing client", {
|
||||
logger.debug("Verifying challenge for existing client", {
|
||||
tenantId,
|
||||
challengeId,
|
||||
});
|
||||
@@ -160,7 +160,6 @@ export const POST: RequestHandler = async ({ request, params }) => {
|
||||
logger.warn("Challenge response mismatch", {
|
||||
tenantId,
|
||||
challengeId,
|
||||
emailHashPrefix: storedChallenge.emailHash.slice(0, 8),
|
||||
});
|
||||
|
||||
// Record failed attempt for throttling
|
||||
@@ -185,14 +184,13 @@ export const POST: RequestHandler = async ({ request, params }) => {
|
||||
logger.warn("Client tunnel not found for verification", {
|
||||
tenantId,
|
||||
challengeId,
|
||||
emailHashPrefix: storedChallenge.emailHash.slice(0, 8),
|
||||
});
|
||||
throw new NotFoundError("Client not found");
|
||||
}
|
||||
|
||||
const tunnel = tunnelResult[0];
|
||||
|
||||
logger.info("Challenge response validated successfully", {
|
||||
logger.debug("Challenge response validated successfully", {
|
||||
tenantId,
|
||||
challengeId,
|
||||
tunnelId: tunnel.id,
|
||||
@@ -207,7 +205,7 @@ export const POST: RequestHandler = async ({ request, params }) => {
|
||||
tunnelId: tunnel.id,
|
||||
};
|
||||
|
||||
logger.info("Successfully verified challenge", {
|
||||
logger.debug("Successfully verified challenge", {
|
||||
tenantId,
|
||||
tunnelId: tunnel.id,
|
||||
});
|
||||
|
||||
@@ -157,10 +157,9 @@ export const POST: RequestHandler = async ({ params, request }) => {
|
||||
validatedData.newClientEncryptedTunnelKey,
|
||||
);
|
||||
|
||||
logger.info("PIN reset completed successfully", {
|
||||
logger.debug("PIN reset completed successfully", {
|
||||
tenantId,
|
||||
tunnelId,
|
||||
tokenId: validatedData.token.slice(0, 8),
|
||||
});
|
||||
|
||||
return json({
|
||||
|
||||
@@ -152,10 +152,9 @@ export const POST: RequestHandler = async ({ params, request, locals }) => {
|
||||
|
||||
const expiresAt = new Date(Date.now() + expirationMinutes * 60 * 1000);
|
||||
|
||||
logger.info("PIN reset token created for QR code", {
|
||||
logger.debug("PIN reset token created for QR code", {
|
||||
tenantId,
|
||||
emailHash: validatedData.emailHash.slice(0, 8),
|
||||
tokenId: token.slice(0, 8),
|
||||
initiatedBy: locals.user?.id,
|
||||
});
|
||||
|
||||
|
||||
@@ -166,12 +166,10 @@ export const POST: RequestHandler = async ({ params, request, locals }) => {
|
||||
const baseUrl = env.PUBLIC_APP_URL || "http://localhost:5173";
|
||||
const resetUrl = `${baseUrl}/reset-pin/${token}`;
|
||||
|
||||
logger.info("PIN reset token created for email", {
|
||||
logger.debug("PIN reset token created for email", {
|
||||
tenantId,
|
||||
emailHash: validatedData.emailHash.slice(0, 8),
|
||||
tokenId: token.slice(0, 8),
|
||||
initiatedBy: locals.user?.id,
|
||||
resetUrl,
|
||||
});
|
||||
|
||||
// TODO: Send actual email once we have a way to get the client's email
|
||||
|
||||
@@ -243,7 +243,7 @@ export const DELETE: RequestHandler = async ({ params, url, locals }) => {
|
||||
const notificationService = await NotificationService.forTenant(tenantId);
|
||||
const deletedCount = await notificationService.deleteAllNotifications(locals.user.id, readOnly);
|
||||
|
||||
log.info("Notifications deleted successfully", {
|
||||
log.debug("Notifications deleted successfully", {
|
||||
tenantId,
|
||||
staffId: locals.user.id,
|
||||
deletedCount,
|
||||
|
||||
@@ -182,7 +182,7 @@ export const DELETE: RequestHandler = async ({ params, locals }) => {
|
||||
const notificationService = await NotificationService.forTenant(tenantId);
|
||||
await notificationService.deleteNotification(notificationId, locals.user.id);
|
||||
|
||||
log.info("Notification deleted successfully", {
|
||||
log.debug("Notification deleted successfully", {
|
||||
tenantId,
|
||||
staffId: locals.user.id,
|
||||
notificationId,
|
||||
@@ -233,7 +233,7 @@ export const PUT: RequestHandler = async ({ params, locals }) => {
|
||||
const notificationService = await NotificationService.forTenant(tenantId);
|
||||
await notificationService.markAsRead(notificationId, locals.user.id);
|
||||
|
||||
log.info("Notification marked as read successfully", {
|
||||
log.debug("Notification marked as read successfully", {
|
||||
tenantId,
|
||||
staffId: locals.user.id,
|
||||
notificationId,
|
||||
|
||||
@@ -131,7 +131,6 @@ export const POST: RequestHandler = async ({ params, locals, request, cookies })
|
||||
const validation = requestSchema.safeParse(body);
|
||||
|
||||
if (!validation.success) {
|
||||
console.error("Validation error:", validation.error.issues);
|
||||
throw new ValidationError(
|
||||
"Invalid request data: " + validation.error.issues.map((e) => e.message).join(", "),
|
||||
);
|
||||
@@ -174,7 +173,6 @@ export const POST: RequestHandler = async ({ params, locals, request, cookies })
|
||||
log.info("Staff crypto keys stored successfully", {
|
||||
tenantId,
|
||||
staffId,
|
||||
passkeyId,
|
||||
});
|
||||
|
||||
return json({
|
||||
|
||||
@@ -91,7 +91,7 @@ export const apiAuthHandle: Handle = async ({ event, resolve }) => {
|
||||
// Protected auth paths require any authenticated user (no specific role required)
|
||||
// The authentication check above is sufficient
|
||||
|
||||
logger.debug(`User authenticated: ${sessionData?.user.email ?? "unauthenticated"} for ${path}`);
|
||||
logger.debug(`User authenticated: ${sessionData?.user.id ?? "unauthenticated"} for ${path}`);
|
||||
|
||||
return resolve(event);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user