diff --git a/apps/dispatch/app/pilot/_components/navbar/Audio.tsx b/apps/dispatch/app/pilot/_components/navbar/Audio.tsx index 480c7339..786a5053 100644 --- a/apps/dispatch/app/pilot/_components/navbar/Audio.tsx +++ b/apps/dispatch/app/pilot/_components/navbar/Audio.tsx @@ -1,7 +1,7 @@ "use client"; import { useEffect, useState } from "react"; -import { useDispatchConnectionStore } from "_store/pilot/connectionStore"; +import { usePilotConnectionStore } from "_store/pilot/connectionStore"; import { Disc, Mic, @@ -20,7 +20,7 @@ import { ConnectionQuality } from "livekit-client"; import { ROOMS } from "_data/livekitRooms"; export const Audio = () => { - const connection = useDispatchConnectionStore(); + const connection = usePilotConnectionStore(); const { isTalking, toggleTalking, diff --git a/apps/dispatch/app/pilot/_components/navbar/Connection.tsx b/apps/dispatch/app/pilot/_components/navbar/Connection.tsx index fa5aba77..745735fe 100644 --- a/apps/dispatch/app/pilot/_components/navbar/Connection.tsx +++ b/apps/dispatch/app/pilot/_components/navbar/Connection.tsx @@ -1,13 +1,13 @@ "use client"; import { useSession } from "next-auth/react"; -import { useDispatchConnectionStore } from "../../../_store/pilot/connectionStore"; +import { usePilotConnectionStore } from "../../../_store/pilot/connectionStore"; import { useEffect, useRef, useState } from "react"; import { useQuery } from "@tanstack/react-query"; import { getStationsAPI } from "querys/stations"; export const ConnectionBtn = () => { const modalRef = useRef(null); - const connection = useDispatchConnectionStore((state) => state); + const connection = usePilotConnectionStore((state) => state); const [form, setForm] = useState<{ logoffTime: string | null; selectedStationId: number | null; diff --git a/apps/hub/app/(auth)/oauth/_components/action.ts b/apps/hub/app/(auth)/oauth/_components/action.ts index 1385a754..333ad6a0 100644 --- a/apps/hub/app/(auth)/oauth/_components/action.ts +++ b/apps/hub/app/(auth)/oauth/_components/action.ts @@ -1,4 +1,5 @@ "use server"; +import { generateUUID } from "../../../../helper/uuid"; import { getServerSession } from "../../../api/auth/[...nextauth]/auth"; import { Service } from "../page"; import { PrismaClient } from "@repo/db"; @@ -9,15 +10,7 @@ export const generateToken = async (service: Service) => { const session = await getServerSession(); if (!session) return null; - const key = await crypto.subtle.generateKey( - { name: "HMAC", hash: "SHA-256" }, - true, - ["sign"], - ); - const exportedKey = await crypto.subtle.exportKey("raw", key); - const accessToken = Array.from(new Uint8Array(exportedKey)) - .map((byte) => byte.toString(16).padStart(2, "0")) - .join(""); + const accessToken = generateUUID(16); const code = await prisma.oAuthToken.create({ data: { diff --git a/apps/hub/app/(auth)/oauth/page.tsx b/apps/hub/app/(auth)/oauth/page.tsx index eb4ff001..d50134a4 100644 --- a/apps/hub/app/(auth)/oauth/page.tsx +++ b/apps/hub/app/(auth)/oauth/page.tsx @@ -9,9 +9,10 @@ export const services = [ }, { id: "2", + secret: "jp2k430fnv", service: "desktop", name: "Desktop client", - approvedUrls: ["var"], + approvedUrls: ["var://oAuth"], }, { id: "3", @@ -26,7 +27,7 @@ export const services = [ ]; export type Service = (typeof services)[number]; -export default async ({ +const Page = async ({ searchParams, }: { searchParams: Promise<{ [key: string]: string | string[] | undefined }>; @@ -43,3 +44,5 @@ export default async ({ return ; }; + +export default Page; diff --git a/apps/hub/app/api/auth/accessToken/route.ts b/apps/hub/app/api/auth/accessToken/route.ts index dbdf8afa..8872adf6 100644 --- a/apps/hub/app/api/auth/accessToken/route.ts +++ b/apps/hub/app/api/auth/accessToken/route.ts @@ -1,60 +1,83 @@ -import { prisma, PrismaClient } from "@repo/db"; -import { NextRequest, NextResponse } from "next/server"; +import { NextRequest } from "next/server"; import { sign } from "jsonwebtoken"; -import { services } from "../../../(auth)/oauth/page"; +import { services } from "(auth)/oauth/page"; +import { prisma } from "@repo/db"; export const POST = async (req: NextRequest) => { - const form = new URLSearchParams(await req.text()); - const client = new PrismaClient(); - const accessToken = form.get("token") || form.get("code"); - const clientId = form.get("client_id"); - const clientSecret = form.get("client_secret"); + try { + if ( + !req.headers + .get("content-type") + ?.includes("application/x-www-form-urlencoded") + ) { + return new Response("Unsupported Content-Type", { status: 415 }); + } - const service = services.find((s) => s.id === clientId); + const form = new URLSearchParams(await req.text()); + const accessToken = form.get("token") || form.get("code"); + const clientId = form.get("client_id"); + const clientSecret = form.get("client_secret"); - if (!accessToken) - return new Response("No access token provided", { status: 400 }); + if (!accessToken) { + console.log("No access token provided", accessToken); + return new Response("No access token provided", { status: 400 }); + } - if (!clientId) - return new Response("No client ID token provided", { status: 400 }); + if (!clientId) { + console.log("No client ID provided", clientId); + return new Response("No client ID provided", { status: 400 }); + } - const accessRequest = await client.oAuthToken.findFirst({ - where: { - accessToken: accessToken, - clientId: clientId, - }, - include: { - user: true, - }, - }); + const service = services.find((s) => s.id === clientId); - if (!service || service.secret !== clientSecret) - return new Response("Invalid client ID or secret", { status: 400 }); + if (!service || service.secret !== clientSecret) { + console.log("Invalid client ID or secret", clientId, clientSecret); + return new Response("Invalid client credentials", { status: 401 }); + } - if (!accessRequest) - return new Response("Access token not found", { status: 404 }); - - if (new Date().getTime() - accessRequest?.createdAt.getTime() > 60 * 1000) { - await prisma.oAuthToken.delete({ + const accessRequest = await prisma.oAuthToken.findFirst({ where: { - id: accessRequest.id, + accessToken: accessToken, + clientId: clientId, + }, + include: { + user: true, }, }); - return new Response("Code expired", { status: 400 }); + + if (!accessRequest) { + console.log("Access token not found", accessToken); + return new Response("Access token not found", { status: 404 }); + } + + if (new Date().getTime() - accessRequest.createdAt.getTime() > 60 * 1000) { + await prisma.oAuthToken.delete({ + where: { + id: accessRequest.id, + }, + }); + console.log("Code expired", accessRequest.id); + return new Response("Code expired", { status: 410 }); + } + + const jwt = sign( + { + ...accessRequest.user, + }, + process.env.NEXTAUTH_SECRET as string, + { + expiresIn: "30d", + }, + ); + + return Response.json({ + access_token: jwt, + token_type: "Bearer", + }); + } catch (error) { + console.error("Error in accessToken route:", error); + return new Response((error as Error).message || "Internal Server Error", { + status: 500, + }); } - - const jwt = sign( - { - ...accessRequest.user, - }, - process.env.NEXTAUTH_SECRET as string, - { - expiresIn: "30d", - }, - ); - - return Response.json({ - access_token: jwt, - token_type: "Bearer", - }); }; diff --git a/apps/hub/app/api/user/route.ts b/apps/hub/app/api/user/route.ts index 3ce48da3..cf101c40 100644 --- a/apps/hub/app/api/user/route.ts +++ b/apps/hub/app/api/user/route.ts @@ -5,7 +5,7 @@ import { getMoodleUserById } from "../../../helper/moodle"; import { inscribeToMoodleCourse } from "../../(app)/events/actions"; export const GET = async (req: NextRequest) => { - // This route is only used by Moodle, so NextAuth is not used here + // This route is only used by Moodle and DEsktop-client, so NextAuth is not used here const authHeader = req.headers.get("Authorization"); const token = authHeader?.split(" ")[1]; if (!authHeader || !token) { diff --git a/apps/hub/helper/uuid.ts b/apps/hub/helper/uuid.ts new file mode 100644 index 00000000..738a868a --- /dev/null +++ b/apps/hub/helper/uuid.ts @@ -0,0 +1,14 @@ +export const generateUUID = (length: number) => { + // Base62-Version (a-z, A-Z, 0-9) + const base62 = + "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; + let string = ""; + + for (let i = 0; i < length; i++) { + string += base62.charAt(Math.floor(Math.random() * base62.length)); + } + + console.log(string); + + return string; +}; diff --git a/grafana/grafana.db b/grafana/grafana.db index ee2e88a9..26692ff7 100644 Binary files a/grafana/grafana.db and b/grafana/grafana.db differ