🚀 Backend komplett implementiert
Features: - Auth mit JWT + Argon2 (Login, Register, Refresh) - Rollen-System (Chef/Disponent/Mitarbeiter) - User Management mit Berechtigungen - Aufträge mit Zuweisungen - Verfügbarkeitsplanung - Stundenzettel mit Foto-Upload Support - Modulares System mit Config - Entwickler-Panel Endpoints Tech: - Deno + Oak - PostgreSQL - CORS enabled
This commit is contained in:
62
src/middleware/auth.ts
Normal file
62
src/middleware/auth.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
import { Context, Next } from "@oak/oak";
|
||||
import { verifyToken } from "../utils/auth.ts";
|
||||
import { AppError } from "./error.ts";
|
||||
import type { UserRole, AuthContext } from "../types/index.ts";
|
||||
|
||||
// Extend Oak context with auth
|
||||
declare module "@oak/oak" {
|
||||
interface State {
|
||||
auth: AuthContext;
|
||||
}
|
||||
}
|
||||
|
||||
// Auth middleware - requires valid JWT
|
||||
export async function authMiddleware(ctx: Context, next: Next): Promise<void> {
|
||||
const authHeader = ctx.request.headers.get("Authorization");
|
||||
|
||||
if (!authHeader || !authHeader.startsWith("Bearer ")) {
|
||||
throw new AppError("No token provided", 401);
|
||||
}
|
||||
|
||||
const token = authHeader.slice(7);
|
||||
const payload = await verifyToken(token);
|
||||
|
||||
if (!payload) {
|
||||
throw new AppError("Invalid or expired token", 401);
|
||||
}
|
||||
|
||||
ctx.state.auth = {
|
||||
user: {
|
||||
id: payload.sub,
|
||||
org_id: payload.org,
|
||||
role: payload.role,
|
||||
email: payload.email,
|
||||
},
|
||||
};
|
||||
|
||||
await next();
|
||||
}
|
||||
|
||||
// Role-based access control middleware
|
||||
export function requireRole(...allowedRoles: UserRole[]) {
|
||||
return async (ctx: Context, next: Next): Promise<void> => {
|
||||
await authMiddleware(ctx, async () => {
|
||||
const userRole = ctx.state.auth.user.role;
|
||||
|
||||
if (!allowedRoles.includes(userRole)) {
|
||||
throw new AppError("Insufficient permissions", 403);
|
||||
}
|
||||
|
||||
await next();
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
// Chef only
|
||||
export const requireChef = requireRole("chef");
|
||||
|
||||
// Chef or Disponent
|
||||
export const requireDisponentOrHigher = requireRole("chef", "disponent");
|
||||
|
||||
// Any authenticated user
|
||||
export const requireAuth = authMiddleware;
|
||||
28
src/middleware/error.ts
Normal file
28
src/middleware/error.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { Context, Next } from "@oak/oak";
|
||||
|
||||
export class AppError extends Error {
|
||||
status: number;
|
||||
|
||||
constructor(message: string, status = 500) {
|
||||
super(message);
|
||||
this.status = status;
|
||||
}
|
||||
}
|
||||
|
||||
export async function errorHandler(ctx: Context, next: Next): Promise<void> {
|
||||
try {
|
||||
await next();
|
||||
} catch (err) {
|
||||
if (err instanceof AppError) {
|
||||
ctx.response.status = err.status;
|
||||
ctx.response.body = { error: err.message };
|
||||
} else if (err instanceof Error) {
|
||||
console.error("Unhandled error:", err);
|
||||
ctx.response.status = 500;
|
||||
ctx.response.body = { error: "Internal server error" };
|
||||
} else {
|
||||
ctx.response.status = 500;
|
||||
ctx.response.body = { error: "Unknown error" };
|
||||
}
|
||||
}
|
||||
}
|
||||
17
src/middleware/logger.ts
Normal file
17
src/middleware/logger.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { Context, Next } from "@oak/oak";
|
||||
|
||||
export async function requestLogger(ctx: Context, next: Next): Promise<void> {
|
||||
const start = Date.now();
|
||||
|
||||
await next();
|
||||
|
||||
const ms = Date.now() - start;
|
||||
const status = ctx.response.status;
|
||||
const method = ctx.request.method;
|
||||
const url = ctx.request.url.pathname;
|
||||
|
||||
const color = status >= 500 ? "\x1b[31m" : status >= 400 ? "\x1b[33m" : "\x1b[32m";
|
||||
const reset = "\x1b[0m";
|
||||
|
||||
console.log(`${color}${status}${reset} ${method} ${url} - ${ms}ms`);
|
||||
}
|
||||
Reference in New Issue
Block a user