- Neue CalDAV-Route mit PROPFIND und GET-Endpoints - ICS-Format-Generator für Buchungsdaten - Token-basierte Authentifizierung für CalDAV-Zugriff - Admin-Interface mit CalDAV-Link-Generator - Schritt-für-Schritt-Anleitung für Kalender-Apps - 24h-Token-Ablaufzeit für Sicherheit - Unterstützung für Outlook, Google Calendar, Apple Calendar, Thunderbird Fixes: Admin kann jetzt Terminkalender in externen Apps abonnieren
149 lines
4.6 KiB
JavaScript
149 lines
4.6 KiB
JavaScript
import { os } from "@orpc/server";
|
|
import { z } from "zod";
|
|
import { randomUUID } from "crypto";
|
|
import { createKV } from "../lib/create-kv.js";
|
|
import { config } from "dotenv";
|
|
// Load environment variables from .env file
|
|
config();
|
|
const UserSchema = z.object({
|
|
id: z.string(),
|
|
username: z.string().min(3, "Benutzername muss mindestens 3 Zeichen lang sein"),
|
|
email: z.string().email("Ungültige E-Mail-Adresse"),
|
|
passwordHash: z.string(),
|
|
role: z.enum(["customer", "owner"]),
|
|
createdAt: z.string(),
|
|
});
|
|
const SessionSchema = z.object({
|
|
id: z.string(),
|
|
userId: z.string(),
|
|
expiresAt: z.string(),
|
|
createdAt: z.string(),
|
|
});
|
|
const usersKV = createKV("users");
|
|
const sessionsKV = createKV("sessions");
|
|
// Simple password hashing (in production, use bcrypt or similar)
|
|
const hashPassword = (password) => {
|
|
return Buffer.from(password).toString('base64');
|
|
};
|
|
const verifyPassword = (password, hash) => {
|
|
return hashPassword(password) === hash;
|
|
};
|
|
// Export hashPassword for external use (e.g., generating hashes for .env)
|
|
export const generatePasswordHash = hashPassword;
|
|
// Initialize default owner account
|
|
const initializeOwner = async () => {
|
|
const existingUsers = await usersKV.getAllItems();
|
|
if (existingUsers.length === 0) {
|
|
const ownerId = randomUUID();
|
|
// Get admin credentials from environment variables
|
|
const adminUsername = process.env.ADMIN_USERNAME || "owner";
|
|
const adminPasswordHash = process.env.ADMIN_PASSWORD_HASH || hashPassword("admin123");
|
|
const adminEmail = process.env.ADMIN_EMAIL || "owner@stargirlnails.de";
|
|
const owner = {
|
|
id: ownerId,
|
|
username: adminUsername,
|
|
email: adminEmail,
|
|
passwordHash: adminPasswordHash,
|
|
role: "owner",
|
|
createdAt: new Date().toISOString(),
|
|
};
|
|
await usersKV.setItem(ownerId, owner);
|
|
console.log(`✅ Admin account created: username="${adminUsername}", email="${adminEmail}"`);
|
|
}
|
|
};
|
|
// Initialize on module load
|
|
initializeOwner();
|
|
const login = os
|
|
.input(z.object({
|
|
username: z.string(),
|
|
password: z.string(),
|
|
}))
|
|
.handler(async ({ input }) => {
|
|
const users = await usersKV.getAllItems();
|
|
const user = users.find(u => u.username === input.username);
|
|
if (!user || !verifyPassword(input.password, user.passwordHash)) {
|
|
throw new Error("Invalid credentials");
|
|
}
|
|
// Create session
|
|
const sessionId = randomUUID();
|
|
const expiresAt = new Date();
|
|
expiresAt.setHours(expiresAt.getHours() + 24); // 24 hours
|
|
const session = {
|
|
id: sessionId,
|
|
userId: user.id,
|
|
expiresAt: expiresAt.toISOString(),
|
|
createdAt: new Date().toISOString(),
|
|
};
|
|
await sessionsKV.setItem(sessionId, session);
|
|
return {
|
|
sessionId,
|
|
user: {
|
|
id: user.id,
|
|
username: user.username,
|
|
email: user.email,
|
|
role: user.role,
|
|
},
|
|
};
|
|
});
|
|
const logout = os
|
|
.input(z.string()) // sessionId
|
|
.handler(async ({ input }) => {
|
|
await sessionsKV.removeItem(input);
|
|
return { success: true };
|
|
});
|
|
const verifySession = os
|
|
.input(z.string()) // sessionId
|
|
.handler(async ({ input }) => {
|
|
const session = await sessionsKV.getItem(input);
|
|
if (!session) {
|
|
throw new Error("Invalid session");
|
|
}
|
|
if (new Date(session.expiresAt) < new Date()) {
|
|
await sessionsKV.removeItem(input);
|
|
throw new Error("Session expired");
|
|
}
|
|
const user = await usersKV.getItem(session.userId);
|
|
if (!user) {
|
|
throw new Error("User not found");
|
|
}
|
|
return {
|
|
user: {
|
|
id: user.id,
|
|
username: user.username,
|
|
email: user.email,
|
|
role: user.role,
|
|
},
|
|
};
|
|
});
|
|
const changePassword = os
|
|
.input(z.object({
|
|
sessionId: z.string(),
|
|
currentPassword: z.string(),
|
|
newPassword: z.string(),
|
|
}))
|
|
.handler(async ({ input }) => {
|
|
const session = await sessionsKV.getItem(input.sessionId);
|
|
if (!session) {
|
|
throw new Error("Invalid session");
|
|
}
|
|
const user = await usersKV.getItem(session.userId);
|
|
if (!user) {
|
|
throw new Error("User not found");
|
|
}
|
|
if (!verifyPassword(input.currentPassword, user.passwordHash)) {
|
|
throw new Error("Current password is incorrect");
|
|
}
|
|
const updatedUser = {
|
|
...user,
|
|
passwordHash: hashPassword(input.newPassword),
|
|
};
|
|
await usersKV.setItem(user.id, updatedUser);
|
|
return { success: true };
|
|
});
|
|
export const router = {
|
|
login,
|
|
logout,
|
|
verifySession,
|
|
changePassword,
|
|
};
|