mirror of
https://github.com/open-reception/appointment-booking-software.git
synced 2026-09-27 03:14:48 +02:00
Auto-decrypt appointments after added keys + removing arbitrary key expiry + slow down login to give browser time to write cookies
This commit is contained in:
@@ -153,9 +153,7 @@ export class UnifiedAppointmentCrypto {
|
||||
// Staff-specific properties
|
||||
private staffKeyPair: StaffKeyPair | null = null;
|
||||
private staffId: string | null = null;
|
||||
private tenantId: string | null = null;
|
||||
private staffAuthenticated: boolean = false;
|
||||
private keyExpiry: number | null = null;
|
||||
|
||||
// Shared crypto utilities
|
||||
private kyberCrypto: KyberCrypto = new KyberCrypto();
|
||||
@@ -786,9 +784,7 @@ export class UnifiedAppointmentCrypto {
|
||||
};
|
||||
|
||||
this.staffId = staffId;
|
||||
this.tenantId = tenantId;
|
||||
this.staffAuthenticated = true;
|
||||
this.keyExpiry = Date.now() + 10 * 60 * 1000; // 10 minutes
|
||||
|
||||
console.log("✅ Staff authentication successful with PRF");
|
||||
} catch (error) {
|
||||
@@ -820,10 +816,6 @@ export class UnifiedAppointmentCrypto {
|
||||
throw new Error("Staff not authenticated");
|
||||
}
|
||||
|
||||
if (this.keyExpiry && Date.now() > this.keyExpiry) {
|
||||
throw new Error("Staff session expired - please authenticate again");
|
||||
}
|
||||
|
||||
try {
|
||||
// Parse the staffKeyShare which now contains: encapsulatedSecret || iv || encryptedTunnelKey
|
||||
const staffKeyShareBytes = this.hexToUint8Array(encryptedData.staffKeyShare);
|
||||
@@ -958,9 +950,7 @@ export class UnifiedAppointmentCrypto {
|
||||
logoutStaff(): void {
|
||||
this.staffKeyPair = null;
|
||||
this.staffId = null;
|
||||
this.tenantId = null;
|
||||
this.staffAuthenticated = false;
|
||||
this.keyExpiry = null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1727,10 +1717,6 @@ export class UnifiedAppointmentCrypto {
|
||||
throw new Error("Staff not authenticated");
|
||||
}
|
||||
|
||||
if (this.keyExpiry && Date.now() > this.keyExpiry) {
|
||||
throw new Error("Staff session expired - please authenticate again");
|
||||
}
|
||||
|
||||
try {
|
||||
// Parse the staffKeyShare which now contains: encapsulatedSecret || iv || encryptedTunnelKey
|
||||
const staffKeyShareBytes = this.hexToUint8Array(staffKeyShare);
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { writable } from "svelte/store";
|
||||
import { get, writable } from "svelte/store";
|
||||
import { UnifiedAppointmentCrypto } from "$lib/client/appointment-crypto";
|
||||
import { auth } from "./auth";
|
||||
|
||||
interface StaffCryptoState {
|
||||
crypto: UnifiedAppointmentCrypto | null;
|
||||
isAuthenticating: boolean;
|
||||
isAuthenticated: boolean;
|
||||
error: string | null;
|
||||
}
|
||||
@@ -11,6 +12,7 @@ interface StaffCryptoState {
|
||||
const createStaffCryptoStore = () => {
|
||||
const store = writable<StaffCryptoState>({
|
||||
crypto: null,
|
||||
isAuthenticating: false,
|
||||
isAuthenticated: false,
|
||||
error: null,
|
||||
});
|
||||
@@ -37,11 +39,18 @@ const createStaffCryptoStore = () => {
|
||||
*/
|
||||
async authenticate(staffId: string, tenantId: string): Promise<boolean> {
|
||||
try {
|
||||
store.set({
|
||||
...get(store),
|
||||
isAuthenticating: true,
|
||||
error: null,
|
||||
});
|
||||
|
||||
const crypto = new UnifiedAppointmentCrypto();
|
||||
await crypto.authenticateStaff(staffId, tenantId);
|
||||
|
||||
store.set({
|
||||
crypto,
|
||||
isAuthenticating: false,
|
||||
isAuthenticated: true,
|
||||
error: null,
|
||||
});
|
||||
@@ -51,6 +60,7 @@ const createStaffCryptoStore = () => {
|
||||
const errorMessage = error instanceof Error ? error.message : "Authentication failed";
|
||||
store.set({
|
||||
crypto: null,
|
||||
isAuthenticating: false,
|
||||
isAuthenticated: false,
|
||||
error: errorMessage,
|
||||
});
|
||||
@@ -59,6 +69,24 @@ const createStaffCryptoStore = () => {
|
||||
}
|
||||
},
|
||||
|
||||
async authenticateAndWait(staffId: string, tenantId: string): Promise<boolean> {
|
||||
const isAuthenticating = get(store).isAuthenticating;
|
||||
if (isAuthenticating) {
|
||||
// Wait for the ongoing authentication to complete
|
||||
return new Promise((resolve) => {
|
||||
const unsubscribe = store.subscribe((state) => {
|
||||
if (!state.isAuthenticating) {
|
||||
unsubscribe();
|
||||
resolve(state.isAuthenticated);
|
||||
}
|
||||
});
|
||||
});
|
||||
} else {
|
||||
// Start a new authentication process
|
||||
return await this.authenticate(staffId, tenantId);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Clear the staff crypto state and auth data
|
||||
*/
|
||||
@@ -66,6 +94,7 @@ const createStaffCryptoStore = () => {
|
||||
auth.clearPasskeyAuthData();
|
||||
store.set({
|
||||
crypto: null,
|
||||
isAuthenticating: false,
|
||||
isAuthenticated: false,
|
||||
error: null,
|
||||
});
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
import { staffCrypto } from "$lib/stores/staff-crypto";
|
||||
import { type TCalendarItem } from "$lib/types/calendar";
|
||||
import Loader2Icon from "@lucide/svelte/icons/loader-2";
|
||||
import { onMount } from "svelte";
|
||||
import { calendarStore } from "$lib/stores/calendar";
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
import { m } from "$i18n/messages";
|
||||
@@ -22,8 +21,14 @@
|
||||
let decrypted = $state<AppointmentData | undefined>();
|
||||
let error = $state<string | undefined>();
|
||||
|
||||
onMount(() => {
|
||||
decrypt();
|
||||
$effect(() => {
|
||||
if (!decrypted) {
|
||||
if ($staffCrypto.isAuthenticated) {
|
||||
decrypt();
|
||||
} else {
|
||||
error = "Keys missing";
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const errors = {
|
||||
@@ -31,6 +36,8 @@
|
||||
};
|
||||
|
||||
const decrypt = async (retry = true) => {
|
||||
error = undefined;
|
||||
|
||||
if (!item.appointment) {
|
||||
console.error("Unable to decrypt appointment data - no appointment data in item.", item.id);
|
||||
error = "Missing data";
|
||||
@@ -64,8 +71,7 @@
|
||||
const user = $auth.user;
|
||||
if (retry && user && user.tenantId) {
|
||||
console.warn("Staff crypto not initialized, retrying decryption...");
|
||||
// TODO: This should only be done once, not in every instance
|
||||
await staffCrypto.authenticate(user.id, user.tenantId);
|
||||
await staffCrypto.authenticateAndWait(user.id, user.tenantId);
|
||||
decrypt(false);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -74,7 +74,6 @@
|
||||
};
|
||||
|
||||
onMount(() => {
|
||||
console.log("item.start", item.start);
|
||||
if (mode.agentId && item.availableAgents) {
|
||||
const availableAgents = item.availableAgents.map((it) => it.id);
|
||||
if (availableAgents.includes(mode.agentId)) {
|
||||
|
||||
@@ -43,6 +43,10 @@
|
||||
onResult: async (event) => {
|
||||
if (event.result.type === "success") {
|
||||
auth.setUser(event.result.data?.user);
|
||||
|
||||
// Wait for cookies to be set before navigating to dashboard
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
|
||||
await goto(resolve(ROUTES.DASHBOARD.MAIN));
|
||||
} else {
|
||||
if ($formData.type === "passkey" && $formData.id === "") {
|
||||
|
||||
Reference in New Issue
Block a user