diff --git a/README.md b/README.md index d014c4a1..2c6898cb 100644 --- a/README.md +++ b/README.md @@ -85,10 +85,17 @@ Learn more about the power of Turborepo: ## Execution policy -## Scope ExecutionPolicy - MachinePolicy Undefined UserPolicy Undefined Process Undefined CurrentUser RemoteSigned LocalMachine RemoteSigned + +## Moodle: + +1. Im docker volume gehe in lib -> Classes -> OAuth2 -> Endpoint.php +2. überspringe die https enforcement rule am Ende der Datei (true in if abfrage) +3. Moodle Admin -> General -> HTTP Security -> Curl einschränkungen löschen +4. http://localhost:8081/admin/category.php?category=authsettings -> Guest login button -> Hide +5. http://localhost:8081/admin/settings.php?section=sitepolicies -> emailchangeconfirmation -> False +6. Beim anlegen des Auth-Services Require Email verification deaktivieren diff --git a/apps/hub/.gitignore b/apps/hub/.gitignore index 4210409e..cc686105 100644 --- a/apps/hub/.gitignore +++ b/apps/hub/.gitignore @@ -38,3 +38,5 @@ yarn-error.log* # typescript *.tsbuildinfo next-env.d.ts + +certificates \ No newline at end of file diff --git a/apps/hub/app/(auth)/oauth/_components/Authorize.tsx b/apps/hub/app/(auth)/oauth/_components/Authorize.tsx index 7e25305a..15873a8e 100644 --- a/apps/hub/app/(auth)/oauth/_components/Authorize.tsx +++ b/apps/hub/app/(auth)/oauth/_components/Authorize.tsx @@ -10,7 +10,6 @@ export const Authorize = ({ service }: { service: Service }) => { searchParams.get("redirect_uri")?.startsWith(url), ); const { data: session } = useSession(); - console.log(session); if (!session) redirect("/login?redirect=" + encodeURIComponent(window.location.href)); if (!legitimeUrl) @@ -37,7 +36,14 @@ export const Authorize = ({ service }: { service: Service }) => { className="btn btn-primary" onClick={async () => { const code = await generateToken(service); - window.location.href = `${searchParams.get("redirect_uri")}?code=${code?.accessToken}`; + const url = new URL(searchParams.get("redirect_uri") as string); + url.searchParams.append("code", code?.accessToken as string); + url.searchParams.append( + "state", + searchParams.get("state") as string, + ); + + window.location.href = url.href; }} > Zulassen diff --git a/apps/hub/app/(auth)/oauth/page.tsx b/apps/hub/app/(auth)/oauth/page.tsx index 8da61cde..eb4ff001 100644 --- a/apps/hub/app/(auth)/oauth/page.tsx +++ b/apps/hub/app/(auth)/oauth/page.tsx @@ -18,7 +18,10 @@ export const services = [ secret: "d0f3e4e4", service: "moodle", name: "Moodle", - approvedUrls: ["https://moodle.virtualairrescue.com"], + approvedUrls: [ + "http://localhost:8081", + "https://moodle.virtualairrescue.com", + ], }, ]; export type Service = (typeof services)[number]; @@ -28,8 +31,11 @@ export default async ({ }: { searchParams: Promise<{ [key: string]: string | string[] | undefined }>; }) => { - const { service: serviceId } = await searchParams; - const service = services.find((service) => service.id === serviceId); + const { service: serviceId, client_id: clientId } = await searchParams; + + const service = services.find( + (service) => service.id === serviceId || service.id === clientId, + ); if (!service) { return
Service not found
; diff --git a/apps/hub/app/_components/ui/Header.tsx b/apps/hub/app/_components/ui/Header.tsx index e6f88a3f..4c80ca07 100644 --- a/apps/hub/app/_components/ui/Header.tsx +++ b/apps/hub/app/_components/ui/Header.tsx @@ -1,23 +1,22 @@ -'use client'; +"use client"; -import { useSession } from 'next-auth/react'; -import Link from 'next/link'; +import { useSession } from "next-auth/react"; +import Link from "next/link"; export const Header = () => { - const session = useSession(); - console.log(session); - return ( -
-

Hub

-
- {session.status === 'authenticated' ? ( -

{session.data?.user.firstname}

- ) : ( - - - - )} -
-
- ); + const session = useSession(); + return ( +
+

Hub

+
+ {session.status === "authenticated" ? ( +

{session.data?.user.firstname}

+ ) : ( + + + + )} +
+
+ ); }; diff --git a/apps/hub/app/api/auth/accessToken/route.ts b/apps/hub/app/api/auth/accessToken/route.ts index 0f3cf795..37d3161c 100644 --- a/apps/hub/app/api/auth/accessToken/route.ts +++ b/apps/hub/app/api/auth/accessToken/route.ts @@ -1,30 +1,40 @@ import { prisma, PrismaClient } from "@repo/db"; import { NextRequest, NextResponse } from "next/server"; import { sign } from "jsonwebtoken"; +import { services } from "../../../(auth)/oauth/page"; -export const GET = async (req: NextRequest) => { +export const POST = async (req: NextRequest) => { + const form = new URLSearchParams(await req.text()); + console.log("POST body:"); const client = new PrismaClient(); - const accessToken = - req.nextUrl.searchParams.get("token") || - req.nextUrl.searchParams.get("code"); - const client_id = req.nextUrl.searchParams.get("client_id"); - const client_secret = req.nextUrl.searchParams.get("client_secret"); + const accessToken = form.get("token") || form.get("code"); + const clientId = form.get("client_id"); + const clientSecret = form.get("client_secret"); + + console.log("Access token:", accessToken); + console.log("Client ID:", clientId); + console.log("Secret:", clientSecret); + const service = services.find((s) => s.id === clientId); if (!accessToken) return new Response("No access token provided", { status: 400 }); - if (!client_id) + if (!clientId) return new Response("No client ID token provided", { status: 400 }); const accessRequest = await client.oAuthToken.findFirst({ where: { accessToken: accessToken, - clientId: client_id, + clientId: clientId, }, include: { user: true, }, }); + + if (!service || service.secret !== clientSecret) + return new Response("Invalid client ID or secret", { status: 400 }); + if (!accessRequest) return new Response("Access token not found", { status: 404 }); @@ -37,12 +47,18 @@ export const GET = async (req: NextRequest) => { return new Response("Code expired", { status: 400 }); } - const jwt = sign(accessRequest.user, process.env.NEXTAUTH_SECRET as string, { - expiresIn: "30d", - }); + const jwt = sign( + { + ...accessRequest.user, + }, + process.env.NEXTAUTH_SECRET as string, + { + expiresIn: "30d", + }, + ); return Response.json({ - user: accessRequest.user, - jwt, + 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 bc7ff663..fe10d5b9 100644 --- a/apps/hub/app/api/user/route.ts +++ b/apps/hub/app/api/user/route.ts @@ -1,21 +1,47 @@ import { NextRequest, NextResponse } from "next/server"; import { getServerSession } from "../auth/[...nextauth]/auth"; import { prisma } from "@repo/db"; +import { generateToken } from "../../(auth)/oauth/_components/action"; +import { decode, verify } from "jsonwebtoken"; + +export async function middleware(req: NextRequest) { + const authHeader = req.headers.get("authorization"); + + if (authHeader?.startsWith("Bearer ")) { + const token = authHeader.split(" ")[1]; + + if (token) { + // Hier kannst du den Token validieren (optional) + + req.headers.set("x-next-auth-token", token); + } + + // Falls NextAuth keine Session hat, erstellen wir eine Fake-Session + } + + return NextResponse.next(); +} export const GET = async (req: NextRequest) => { - const session = await getServerSession(); - if (!session) { - return { - status: 401, - body: "Unauthorized", - }; + // This route is only used by Moodle, so NextAuth is not used here + const authHeader = req.headers.get("Authorization"); + const token = authHeader?.split(" ")[1]; + if (!authHeader || !token) { + return NextResponse.json({ error: "Not logged in" }, { status: 401 }); } + const decoded = await verify(token, process.env.NEXTAUTH_SECRET as string); + + if (typeof decoded === "string") + return NextResponse.json({ error: "Invalid token" }, { status: 401 }); const user = await prisma.user.findUnique({ where: { - id: session.user.id, + id: decoded.id, }, }); - return NextResponse.json(user); + return NextResponse.json({ + ...user, + moodleLastname: `${user?.lastname.split("")[0]}. - ${user?.publicId}`, + }); }; diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index d4726061..787eaedf 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -1,6 +1,3 @@ -# docker-compose.dev.yml - -version: "3.8" services: postgres: image: postgres:13 @@ -13,6 +10,7 @@ services: POSTGRES_DB: var volumes: - postgres-data:/var/lib/postgresql/data + pgadmin: image: dpage/pgadmin4:latest container_name: pgadmin @@ -23,5 +21,44 @@ services: - "8080:80" depends_on: - postgres + + moodle_database: + container_name: moodle_database + image: docker.io/bitnami/mariadb:latest + environment: + # ALLOW_EMPTY_PASSWORD is recommended only for development. + - ALLOW_EMPTY_PASSWORD=yes + - MARIADB_USER=bn_moodle + - MARIADB_DATABASE=bitnami_moodle + - MARIADB_CHARACTER_SET=utf8mb4 + - MARIADB_COLLATE=utf8mb4_unicode_ci + volumes: + - "moodle_database:/bitnami/mariadb" + moodle: + image: bitnami/moodle:latest + container_name: moodle + ports: + - "8081:8080" # Moodle läuft auf http://localhost:8081 + environment: + - MOODLE_DATABASE_HOST=moodle_database + - MOODLE_DATABASE_PORT_NUMBER=3306 + - MOODLE_DATABASE_USER=bn_moodle + - MOODLE_DATABASE_NAME=bitnami_moodle + + - MOODLE_USERNAME=admin + - MOODLE_PASSWORD=admin123 + - MOODLE_EMAIL=admin@example.com + - MOODLE_SITE_NAME="Mein Lokales Moodle" + - MOODLE_SSLPROXY=false + - ALLOW_EMPTY_PASSWORD=yes + depends_on: + - moodle_database + volumes: + - moodle_data:/bitnami/moodle + - moodle_moodledata:/bitnami/moodledata + # Für den Zugriff auf den Host volumes: postgres-data: + moodle_data: + moodle_database: + moodle_moodledata: