mirror of
https://github.com/open-reception/appointment-booking-software.git
synced 2026-09-28 19:54:50 +02:00
Fix: Granting staff access to appointments
This commit is contained in:
@@ -1036,7 +1036,7 @@
|
||||
"reason": "Sie erhalten diese E-Mail, weil jemand für die PIN für Ihr Konto geändert hat."
|
||||
},
|
||||
"userInvite": {
|
||||
"subject": "E-Mail Adresse bestätigen für {tenant}",
|
||||
"subject": "E-Mail Adresse bestätigen",
|
||||
"introduction": "willkommen beim Terminbuchungsportal von {tenant}. Bitte bestätige Deine E-Mail Adresse.",
|
||||
"action": "E-Mail Adresse bestätigen",
|
||||
"hint": "Dieser Link ist nur {expirationMinutes} Minuten gültig und kann nur einmal verwendet werden.",
|
||||
|
||||
@@ -1247,6 +1247,26 @@ export class UnifiedAppointmentCrypto {
|
||||
return data.staffPublicKeys;
|
||||
}
|
||||
|
||||
async fetchStaffPublicKeysByStaff(tenantId: string): Promise<StaffPublicKey[]> {
|
||||
const response = await fetch(
|
||||
`/api/tenants/${tenantId}/appointments/staff-public-keys-by-staff`,
|
||||
{
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${this.bookingAccessToken}`,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to fetch staff public keys for staff: ${response.statusText}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return data.staffPublicKeys;
|
||||
}
|
||||
|
||||
private async bootstrapNewClientAccess(tenantId: string): Promise<void> {
|
||||
if (!this.tunnelId || !this.clientKeyPair) {
|
||||
throw new Error("Bootstrap requires generated client tunnel and key pair");
|
||||
|
||||
+1
-1
@@ -35,7 +35,7 @@
|
||||
step = "add-staff-key-shares";
|
||||
|
||||
// For each tunnel: decrypt currentStaffEncryptedTunnelKey with current user private key
|
||||
const allPublicKeys = await cryptoClient.fetchStaffPublicKeys(tenantId);
|
||||
const allPublicKeys = await cryptoClient.fetchStaffPublicKeysByStaff(tenantId);
|
||||
const newUserPublicKeys = allPublicKeys.filter((x) => x.userId === entity.id);
|
||||
|
||||
if (newUserPublicKeys.length === 0) {
|
||||
|
||||
@@ -5,6 +5,7 @@ import { BackendError, InternalError, logError, ValidationError } from "$lib/ser
|
||||
import type { RequestHandler } from "./$types";
|
||||
import { registerOpenAPIRoute } from "$lib/server/openapi";
|
||||
import logger from "$lib/logger";
|
||||
import { AppointmentService } from "$lib/server/services/appointment-service";
|
||||
|
||||
// Register OpenAPI documentation
|
||||
registerOpenAPIRoute("/auth/register", "POST", {
|
||||
@@ -154,6 +155,20 @@ export const POST: RequestHandler = async ({ params, cookies, request, url }) =>
|
||||
counter: verificationResult.counter,
|
||||
});
|
||||
|
||||
// Set user to ACCESS_GRANTED, if no appointments exists
|
||||
try {
|
||||
const user = await UserService.getUserByEmail(body.email);
|
||||
if (user.tenantId) {
|
||||
const appointmentService = await AppointmentService.forTenant(user.tenantId);
|
||||
const hasAppointments = await appointmentService.hasAppointments();
|
||||
if (!hasAppointments) {
|
||||
await UserService.updateUser(user.id, { confirmationState: "ACCESS_GRANTED" });
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// Silent fail for now
|
||||
}
|
||||
|
||||
return json(
|
||||
{
|
||||
message: "User account now has a passkey.",
|
||||
|
||||
@@ -228,8 +228,6 @@ export const GET: RequestHandler = async ({ params, locals }) => {
|
||||
throw new ValidationError("Tenant ID and appointment ID are required");
|
||||
}
|
||||
|
||||
// checkPermission(locals, tenantId, true);
|
||||
|
||||
log.debug("Getting appointment by ID", {
|
||||
tenantId,
|
||||
appointmentId,
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
import { logger } from "$lib/logger";
|
||||
import { registerOpenAPIRoute } from "$lib/server/openapi";
|
||||
import { StaffCryptoService } from "$lib/server/services/staff-crypto.service";
|
||||
import { BackendError, InternalError, logError, ValidationError } from "$lib/server/utils/errors";
|
||||
import { checkPermission } from "$lib/server/utils/permissions";
|
||||
import { json, type RequestHandler } from "@sveltejs/kit";
|
||||
|
||||
// Register OpenAPI documentation for GET
|
||||
registerOpenAPIRoute("/tenants/{id}/appointments/staff-public-keys-by-staff", "GET", {
|
||||
summary: "Get staff public keys",
|
||||
description:
|
||||
"Returns public encryption keys for all staff members. Requires a valid access token.",
|
||||
tags: ["GrantAccess"],
|
||||
parameters: [
|
||||
{
|
||||
name: "id",
|
||||
in: "path",
|
||||
required: true,
|
||||
schema: { type: "string", format: "uuid" },
|
||||
description: "Tenant ID",
|
||||
},
|
||||
],
|
||||
responses: {
|
||||
"200": {
|
||||
description: "Staff public keys retrieved successfully",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
staffPublicKeys: {
|
||||
type: "array",
|
||||
items: {
|
||||
type: "object",
|
||||
properties: {
|
||||
userId: {
|
||||
type: "string",
|
||||
format: "uuid",
|
||||
description: "Staff member's user ID",
|
||||
},
|
||||
publicKey: {
|
||||
type: "string",
|
||||
description: "ML-KEM-768 public key (hex encoded)",
|
||||
example: "deadbeef123456789abcdef...",
|
||||
},
|
||||
},
|
||||
required: ["userId", "publicKey"],
|
||||
},
|
||||
description: "List of staff public keys for encryption",
|
||||
},
|
||||
},
|
||||
required: ["staffPublicKeys"],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"400": {
|
||||
description: "Invalid tenant ID",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: { $ref: "#/components/schemas/Error" },
|
||||
},
|
||||
},
|
||||
},
|
||||
"401": {
|
||||
description: "Missing or invalid cookie access token",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: { $ref: "#/components/schemas/Error" },
|
||||
},
|
||||
},
|
||||
},
|
||||
"500": {
|
||||
description: "Internal server error or no staff keys found",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: { $ref: "#/components/schemas/Error" },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* GET /api/tenants/[id]/appointments/staff-public-keys-by-staff
|
||||
*
|
||||
* Returns the public keys of all staff members for encryption.
|
||||
* Requires booking access token from verify-challenge or bootstrap-verify endpoint.
|
||||
*/
|
||||
export const GET: RequestHandler = async ({ params, locals }) => {
|
||||
try {
|
||||
const tenantId = params.id;
|
||||
if (!tenantId) {
|
||||
throw new ValidationError("Tenant ID is required");
|
||||
}
|
||||
|
||||
checkPermission(locals, tenantId, false);
|
||||
|
||||
logger.info("Fetching staff public keys", { tenantId });
|
||||
|
||||
// Get staff public keys for encryption
|
||||
const staffCryptoService = new StaffCryptoService();
|
||||
const staffPublicKeys = await staffCryptoService.getStaffPublicKeys(tenantId);
|
||||
|
||||
if (staffPublicKeys.length === 0) {
|
||||
logger.warn("No staff public keys found for tenant", { tenantId });
|
||||
throw new InternalError("No staff members with encryption keys found");
|
||||
}
|
||||
|
||||
logger.info("Successfully retrieved staff public keys", {
|
||||
tenantId,
|
||||
staffCount: staffPublicKeys.length,
|
||||
});
|
||||
|
||||
return json({ staffPublicKeys });
|
||||
} catch (error) {
|
||||
logError(logger)("Failed to fetch staff public keys", error);
|
||||
if (error instanceof BackendError) {
|
||||
return error.toJson();
|
||||
}
|
||||
return new InternalError().toJson();
|
||||
}
|
||||
};
|
||||
+385
@@ -0,0 +1,385 @@
|
||||
import { logger } from "$lib/logger";
|
||||
import { StaffCryptoService } from "$lib/server/services/staff-crypto.service";
|
||||
import { AuthenticationError, AuthorizationError } from "$lib/server/utils/errors";
|
||||
import { checkPermission } from "$lib/server/utils/permissions";
|
||||
import type { RequestEvent } from "@sveltejs/kit";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { GET } from "../+server";
|
||||
|
||||
// Mock dependencies
|
||||
vi.mock("$lib/logger");
|
||||
vi.mock("$lib/server/services/staff-crypto.service");
|
||||
vi.mock("$lib/server/utils/permissions");
|
||||
vi.mock("$lib/server/openapi");
|
||||
|
||||
const mockCookies = {
|
||||
get: vi.fn(),
|
||||
set: vi.fn(),
|
||||
delete: vi.fn(),
|
||||
};
|
||||
|
||||
const createMockRequestEvent = (tenantId: string, locals: unknown = {}): RequestEvent =>
|
||||
({
|
||||
params: { id: tenantId },
|
||||
request: new Request(
|
||||
`http://localhost/api/tenants/${tenantId}/appointments/staff-public-keys-by-staff`,
|
||||
),
|
||||
url: new URL(
|
||||
`http://localhost/api/tenants/${tenantId}/appointments/staff-public-keys-by-staff`,
|
||||
),
|
||||
route: { id: "/api/tenants/[id]/appointments/staff-public-keys-by-staff" },
|
||||
locals,
|
||||
cookies: mockCookies,
|
||||
fetch: global.fetch,
|
||||
getClientAddress: () => "127.0.0.1",
|
||||
isDataRequest: false,
|
||||
platform: undefined,
|
||||
setHeaders: vi.fn(),
|
||||
depends: vi.fn(),
|
||||
parent: vi.fn(),
|
||||
}) as unknown as RequestEvent;
|
||||
|
||||
describe("GET /api/tenants/[id]/appointments/staff-public-keys-by-staff", () => {
|
||||
const mockTenantId = "550e8400-e29b-41d4-a716-446655440000";
|
||||
const mockStaffId1 = "f47ac10b-58cc-4372-a567-0e02b2c3d479";
|
||||
const mockStaffId2 = "f47ac10b-58cc-4372-a567-0e02b2c3d480";
|
||||
|
||||
const mockStaffPublicKeys = [
|
||||
{
|
||||
userId: mockStaffId1,
|
||||
publicKey: "deadbeef1234567890abcdef1234567890abcdef1234567890abcdef12345678",
|
||||
passkeyId: "key1",
|
||||
},
|
||||
{
|
||||
userId: mockStaffId2,
|
||||
publicKey: "cafebabe1234567890abcdef1234567890abcdef1234567890abcdef12345678",
|
||||
passkeyId: "key2",
|
||||
},
|
||||
];
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
vi.mocked(logger.info).mockImplementation(() => {});
|
||||
vi.mocked(logger.warn).mockImplementation(() => {});
|
||||
});
|
||||
|
||||
describe("Success Cases", () => {
|
||||
it("should return staff public keys for authenticated user with valid cookie", async () => {
|
||||
const locals = {
|
||||
user: {
|
||||
userId: "booking-user-id",
|
||||
role: "GUEST",
|
||||
tenantId: mockTenantId,
|
||||
},
|
||||
};
|
||||
|
||||
vi.mocked(checkPermission).mockImplementationOnce(() => {});
|
||||
vi.mocked(StaffCryptoService.prototype.getStaffPublicKeys).mockResolvedValueOnce(
|
||||
mockStaffPublicKeys,
|
||||
);
|
||||
|
||||
const event = createMockRequestEvent(mockTenantId, locals);
|
||||
const response = await GET(event);
|
||||
const data = await response.json();
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(data.staffPublicKeys).toEqual(mockStaffPublicKeys);
|
||||
expect(data.staffPublicKeys).toHaveLength(2);
|
||||
});
|
||||
|
||||
it("should return multiple staff public keys", async () => {
|
||||
const locals = {
|
||||
user: {
|
||||
userId: "booking-user-id",
|
||||
role: "GUEST",
|
||||
tenantId: mockTenantId,
|
||||
},
|
||||
};
|
||||
|
||||
vi.mocked(checkPermission).mockImplementationOnce(() => {});
|
||||
|
||||
const manyStaffKeys = Array.from({ length: 5 }, (_, i) => ({
|
||||
userId: `staff-${i}`,
|
||||
publicKey: `key-${i}${"a".repeat(60)}`,
|
||||
passkeyId: `passkey-${i}`,
|
||||
}));
|
||||
|
||||
vi.mocked(StaffCryptoService.prototype.getStaffPublicKeys).mockResolvedValueOnce(
|
||||
manyStaffKeys,
|
||||
);
|
||||
|
||||
const event = createMockRequestEvent(mockTenantId, locals);
|
||||
const response = await GET(event);
|
||||
const data = await response.json();
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(data.staffPublicKeys).toHaveLength(5);
|
||||
expect(data.staffPublicKeys[0]).toHaveProperty("userId");
|
||||
expect(data.staffPublicKeys[0]).toHaveProperty("publicKey");
|
||||
});
|
||||
|
||||
it("should call StaffCryptoService with correct tenant ID", async () => {
|
||||
const locals = {
|
||||
user: {
|
||||
userId: "booking-user-id",
|
||||
role: "GUEST",
|
||||
tenantId: mockTenantId,
|
||||
},
|
||||
};
|
||||
|
||||
vi.mocked(checkPermission).mockImplementationOnce(() => {});
|
||||
|
||||
const mockService = {
|
||||
getStaffPublicKeys: vi.fn().mockResolvedValueOnce(mockStaffPublicKeys),
|
||||
};
|
||||
|
||||
vi.mocked(StaffCryptoService).mockImplementationOnce(
|
||||
() => mockService as unknown as StaffCryptoService,
|
||||
);
|
||||
|
||||
const event = createMockRequestEvent(mockTenantId, locals);
|
||||
await GET(event);
|
||||
|
||||
expect(mockService.getStaffPublicKeys).toHaveBeenCalledWith(mockTenantId);
|
||||
expect(mockService.getStaffPublicKeys).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it("should call checkPermission with tenant ID and false flag", async () => {
|
||||
const locals = {
|
||||
user: {
|
||||
userId: "booking-user-id",
|
||||
role: "GUEST",
|
||||
tenantId: mockTenantId,
|
||||
},
|
||||
};
|
||||
|
||||
vi.mocked(checkPermission).mockImplementationOnce(() => {});
|
||||
vi.mocked(StaffCryptoService.prototype.getStaffPublicKeys).mockResolvedValueOnce(
|
||||
mockStaffPublicKeys,
|
||||
);
|
||||
|
||||
const event = createMockRequestEvent(mockTenantId, locals);
|
||||
await GET(event);
|
||||
|
||||
expect(vi.mocked(checkPermission)).toHaveBeenCalledWith(locals, mockTenantId, false);
|
||||
expect(vi.mocked(checkPermission)).toHaveBeenCalledOnce();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Validation & Error Cases", () => {
|
||||
it("should return 422 when tenant ID is missing (ValidationError from SvelteKit)", async () => {
|
||||
const locals = {
|
||||
user: {
|
||||
userId: "booking-user-id",
|
||||
role: "GUEST",
|
||||
tenantId: mockTenantId,
|
||||
},
|
||||
};
|
||||
|
||||
const event = createMockRequestEvent("", locals);
|
||||
const response = await GET(event);
|
||||
|
||||
// ValidationError returns 422 in SvelteKit
|
||||
expect(response.status).toBe(422);
|
||||
const data = await response.json();
|
||||
expect(data).toHaveProperty("error");
|
||||
});
|
||||
|
||||
it("should return 401 when user is not authenticated", async () => {
|
||||
vi.mocked(checkPermission).mockImplementationOnce(() => {
|
||||
throw new AuthenticationError("Authentication required");
|
||||
});
|
||||
|
||||
const locals = { user: null };
|
||||
const event = createMockRequestEvent(mockTenantId, locals);
|
||||
const response = await GET(event);
|
||||
|
||||
expect(response.status).toBe(401);
|
||||
const data = await response.json();
|
||||
expect(data).toHaveProperty("error");
|
||||
});
|
||||
|
||||
it("should return 401 when locals are missing user", async () => {
|
||||
vi.mocked(checkPermission).mockImplementationOnce(() => {
|
||||
throw new AuthenticationError("Authentication required");
|
||||
});
|
||||
|
||||
const event = createMockRequestEvent(mockTenantId, {});
|
||||
const response = await GET(event);
|
||||
|
||||
expect(response.status).toBe(401);
|
||||
const data = await response.json();
|
||||
expect(data).toHaveProperty("error");
|
||||
});
|
||||
|
||||
it("should return 403 when user lacks permission for tenant", async () => {
|
||||
vi.mocked(checkPermission).mockImplementationOnce(() => {
|
||||
throw new AuthorizationError("Insufficient permissions");
|
||||
});
|
||||
|
||||
const locals = {
|
||||
user: {
|
||||
userId: "booking-user-id",
|
||||
role: "GUEST",
|
||||
tenantId: "different-tenant-id",
|
||||
},
|
||||
};
|
||||
|
||||
const event = createMockRequestEvent(mockTenantId, locals);
|
||||
const response = await GET(event);
|
||||
|
||||
expect(response.status).toBe(403);
|
||||
const data = await response.json();
|
||||
expect(data).toHaveProperty("error");
|
||||
});
|
||||
|
||||
it("should return 500 when no staff keys are found", async () => {
|
||||
const locals = {
|
||||
user: {
|
||||
userId: "booking-user-id",
|
||||
role: "GUEST",
|
||||
tenantId: mockTenantId,
|
||||
},
|
||||
};
|
||||
|
||||
vi.mocked(checkPermission).mockImplementationOnce(() => {});
|
||||
vi.mocked(StaffCryptoService.prototype.getStaffPublicKeys).mockResolvedValueOnce([]);
|
||||
|
||||
const event = createMockRequestEvent(mockTenantId, locals);
|
||||
const response = await GET(event);
|
||||
|
||||
expect(response.status).toBe(500);
|
||||
const data = await response.json();
|
||||
expect(data).toHaveProperty("error");
|
||||
});
|
||||
|
||||
it("should return 500 when StaffCryptoService throws an error", async () => {
|
||||
const locals = {
|
||||
user: {
|
||||
userId: "booking-user-id",
|
||||
role: "GUEST",
|
||||
tenantId: mockTenantId,
|
||||
},
|
||||
};
|
||||
|
||||
vi.mocked(checkPermission).mockImplementationOnce(() => {});
|
||||
|
||||
const serviceError = new Error("Database connection failed");
|
||||
vi.mocked(StaffCryptoService.prototype.getStaffPublicKeys).mockRejectedValueOnce(
|
||||
serviceError,
|
||||
);
|
||||
|
||||
const event = createMockRequestEvent(mockTenantId, locals);
|
||||
const response = await GET(event);
|
||||
|
||||
expect(response.status).toBe(500);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Logging", () => {
|
||||
it("should log info when successfully fetching staff keys", async () => {
|
||||
const locals = {
|
||||
user: {
|
||||
userId: "booking-user-id",
|
||||
role: "GUEST",
|
||||
tenantId: mockTenantId,
|
||||
},
|
||||
};
|
||||
|
||||
vi.mocked(checkPermission).mockImplementationOnce(() => {});
|
||||
vi.mocked(StaffCryptoService.prototype.getStaffPublicKeys).mockResolvedValueOnce(
|
||||
mockStaffPublicKeys,
|
||||
);
|
||||
|
||||
const event = createMockRequestEvent(mockTenantId, locals);
|
||||
await GET(event);
|
||||
|
||||
expect(vi.mocked(logger.info)).toHaveBeenCalledWith("Fetching staff public keys", {
|
||||
tenantId: mockTenantId,
|
||||
});
|
||||
expect(vi.mocked(logger.info)).toHaveBeenCalledWith(
|
||||
"Successfully retrieved staff public keys",
|
||||
{
|
||||
tenantId: mockTenantId,
|
||||
staffCount: 2,
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it("should log warning when no staff keys found", async () => {
|
||||
const locals = {
|
||||
user: {
|
||||
userId: "booking-user-id",
|
||||
role: "GUEST",
|
||||
tenantId: mockTenantId,
|
||||
},
|
||||
};
|
||||
|
||||
vi.mocked(checkPermission).mockImplementationOnce(() => {});
|
||||
vi.mocked(StaffCryptoService.prototype.getStaffPublicKeys).mockResolvedValueOnce([]);
|
||||
|
||||
const event = createMockRequestEvent(mockTenantId, locals);
|
||||
await GET(event);
|
||||
|
||||
expect(vi.mocked(logger.warn)).toHaveBeenCalledWith("No staff public keys found for tenant", {
|
||||
tenantId: mockTenantId,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("Response Format", () => {
|
||||
it("should return properly formatted JSON response", async () => {
|
||||
const locals = {
|
||||
user: {
|
||||
userId: "booking-user-id",
|
||||
role: "GUEST",
|
||||
tenantId: mockTenantId,
|
||||
},
|
||||
};
|
||||
|
||||
vi.mocked(checkPermission).mockImplementationOnce(() => {});
|
||||
vi.mocked(StaffCryptoService.prototype.getStaffPublicKeys).mockResolvedValueOnce(
|
||||
mockStaffPublicKeys,
|
||||
);
|
||||
|
||||
const event = createMockRequestEvent(mockTenantId, locals);
|
||||
const response = await GET(event);
|
||||
|
||||
expect(response.headers.get("content-type")).toContain("application/json");
|
||||
expect(response.status).toBe(200);
|
||||
|
||||
const data = await response.json();
|
||||
expect(data).toHaveProperty("staffPublicKeys");
|
||||
expect(Array.isArray(data.staffPublicKeys)).toBe(true);
|
||||
});
|
||||
|
||||
it("should include complete staff key objects with userId and publicKey", async () => {
|
||||
const locals = {
|
||||
user: {
|
||||
userId: "booking-user-id",
|
||||
role: "GUEST",
|
||||
tenantId: mockTenantId,
|
||||
},
|
||||
};
|
||||
|
||||
vi.mocked(checkPermission).mockImplementationOnce(() => {});
|
||||
vi.mocked(StaffCryptoService.prototype.getStaffPublicKeys).mockResolvedValueOnce(
|
||||
mockStaffPublicKeys,
|
||||
);
|
||||
|
||||
const event = createMockRequestEvent(mockTenantId, locals);
|
||||
const response = await GET(event);
|
||||
const data = await response.json();
|
||||
|
||||
data.staffPublicKeys.forEach(
|
||||
(key: { userId: string; publicKey: string; passkeyId: string }) => {
|
||||
expect(key).toHaveProperty("userId");
|
||||
expect(key).toHaveProperty("publicKey");
|
||||
expect(key).toHaveProperty("passkeyId");
|
||||
expect(typeof key.userId).toBe("string");
|
||||
expect(typeof key.publicKey).toBe("string");
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -140,14 +140,12 @@ registerOpenAPIRoute("/tenants/{id}/appointments/tunnels/add-staff-key-shares",
|
||||
|
||||
const requestSchema = z.object({
|
||||
staffUserId: z.string().uuid("Invalid staff user ID format"),
|
||||
keyShares: z
|
||||
.array(
|
||||
z.object({
|
||||
tunnelId: z.string().uuid("Invalid tunnel ID format"),
|
||||
encryptedTunnelKey: z.string().min(1, "Encrypted tunnel key cannot be empty"),
|
||||
}),
|
||||
)
|
||||
.min(1, "At least one key share is required"),
|
||||
keyShares: z.array(
|
||||
z.object({
|
||||
tunnelId: z.string().uuid("Invalid tunnel ID format"),
|
||||
encryptedTunnelKey: z.string().min(1, "Encrypted tunnel key cannot be empty"),
|
||||
}),
|
||||
),
|
||||
});
|
||||
|
||||
export const POST: RequestHandler = async ({ params, locals, request }) => {
|
||||
|
||||
@@ -4,6 +4,7 @@ import { BackendError, InternalError, logError, ValidationError } from "$lib/ser
|
||||
import type { RequestHandler } from "@sveltejs/kit";
|
||||
import { registerOpenAPIRoute } from "$lib/server/openapi";
|
||||
import logger from "$lib/logger";
|
||||
import { checkPermission } from "$lib/server/utils/permissions";
|
||||
|
||||
// Register OpenAPI documentation for GET
|
||||
registerOpenAPIRoute("/tenants/{id}/calendar", "GET", {
|
||||
@@ -129,6 +130,8 @@ export const GET: RequestHandler = async ({ params, url, locals }) => {
|
||||
throw new ValidationError("Tenant ID is required");
|
||||
}
|
||||
|
||||
checkPermission(locals, tenantId, false);
|
||||
|
||||
// Parse query parameters for date range
|
||||
const startDateParam = url.searchParams.get("startDate");
|
||||
const endDateParam = url.searchParams.get("endDate");
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { describe, expect, it, vi, beforeEach } from "vitest";
|
||||
import { GET } from "../+server";
|
||||
import { ScheduleService } from "$lib/server/services/schedule-service";
|
||||
import { checkPermission } from "$lib/server/utils/permissions";
|
||||
|
||||
// Mock the ScheduleService
|
||||
vi.mock("$lib/server/services/schedule-service", () => ({
|
||||
@@ -9,6 +10,9 @@ vi.mock("$lib/server/services/schedule-service", () => ({
|
||||
},
|
||||
}));
|
||||
|
||||
// Mock checkPermission
|
||||
vi.mock("$lib/server/utils/permissions");
|
||||
|
||||
// Mock logger
|
||||
vi.mock("$lib/logger", () => ({
|
||||
default: {
|
||||
@@ -35,6 +39,8 @@ describe("Calendar API", () => {
|
||||
mockGetSchedule.mockReset();
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
vi.mocked(ScheduleService.forTenant).mockResolvedValue(mockScheduleService as any);
|
||||
// Mock checkPermission to succeed by default
|
||||
vi.mocked(checkPermission).mockImplementation(() => {});
|
||||
});
|
||||
|
||||
const createRequest = (tenantId: string, startDate?: string, endDate?: string) => {
|
||||
@@ -45,7 +51,13 @@ describe("Calendar API", () => {
|
||||
return {
|
||||
params: { id: tenantId },
|
||||
url,
|
||||
locals: {}, // Add locals object
|
||||
locals: {
|
||||
user: {
|
||||
id: "user-123",
|
||||
tenantId,
|
||||
role: "GUEST",
|
||||
},
|
||||
},
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
} as any;
|
||||
};
|
||||
@@ -143,7 +155,7 @@ describe("Calendar API", () => {
|
||||
startDate: "2024-01-01T00:00:00.000Z",
|
||||
endDate: "2024-01-02T00:00:00.000Z",
|
||||
timeZone: "UTC",
|
||||
staffUserId: undefined,
|
||||
staffUserId: "user-123",
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user