Füge eine Benutzerverwaltung hinzu, damit "Manage Treatments" und "Manage Bookings" nur für den Shop Inhaber zugänglich ist.
This commit is contained in:
165
src/server/rpc/auth.ts
Normal file
165
src/server/rpc/auth.ts
Normal file
@@ -0,0 +1,165 @@
|
||||
import { call, os } from "@orpc/server";
|
||||
import { z } from "zod";
|
||||
import { randomUUID } from "crypto";
|
||||
import { createKV } from "@/server/lib/create-kv";
|
||||
|
||||
const UserSchema = z.object({
|
||||
id: z.string(),
|
||||
username: z.string(),
|
||||
email: z.string(),
|
||||
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(),
|
||||
});
|
||||
|
||||
type User = z.output<typeof UserSchema>;
|
||||
type Session = z.output<typeof SessionSchema>;
|
||||
|
||||
const usersKV = createKV<User>("users");
|
||||
const sessionsKV = createKV<Session>("sessions");
|
||||
|
||||
// Simple password hashing (in production, use bcrypt or similar)
|
||||
const hashPassword = (password: string): string => {
|
||||
return Buffer.from(password).toString('base64');
|
||||
};
|
||||
|
||||
const verifyPassword = (password: string, hash: string): boolean => {
|
||||
return hashPassword(password) === hash;
|
||||
};
|
||||
|
||||
// Initialize default owner account
|
||||
const initializeOwner = async () => {
|
||||
const existingUsers = await usersKV.getAllItems();
|
||||
if (existingUsers.length === 0) {
|
||||
const ownerId = randomUUID();
|
||||
const owner: User = {
|
||||
id: ownerId,
|
||||
username: "owner",
|
||||
email: "owner@stargirlnails.de",
|
||||
passwordHash: hashPassword("admin123"), // Default password
|
||||
role: "owner",
|
||||
createdAt: new Date().toISOString(),
|
||||
};
|
||||
await usersKV.setItem(ownerId, owner);
|
||||
}
|
||||
};
|
||||
|
||||
// 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: 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,
|
||||
};
|
Reference in New Issue
Block a user