import { z } from "zod"; import moment from "moment"; import { prisma } from "@ibcornelsen/database/server"; import { encodeToken } from "../../../lib/auth/token.js"; import { APIError, defineApiRoute } from "astro-typesafe-api/server"; import { TokenType } from "#lib/auth/types.js"; export const GET = defineApiRoute({ meta: { description: "Erstellt, basierend auf einem existierenden und gültigen Refresh Tokens, einen neuen Access Token, welcher zur Authentifizierung genutzt werden kann. Der resultierende Access Token ist nur 2 Tage gültig und muss danach neu generiert werden. Diese Funktion gibt ebenfalls einen neuen Refresh Token zurück, der alte wird dadurch invalidiert.", tags: ["Benutzer"], summary: "Access Token anfragen.", }, input: z.object({ refreshToken: z.string(), }), output: z.object({ accessToken: z.string(), accessTokenExpiry: z.number(), refreshToken: z.string(), refreshTokenExpiry: z.number(), }), async fetch(input, ctx) { /** * Wir benutzen rolling refresh tokens, also löschen wir den alten Token und stellen einen neuen aus, * damit dieser nicht geklaut werden kann. */ const response = await prisma.refreshTokens.findUnique({ where: { token: input.refreshToken, }, }); if (!response) { throw new APIError({ code: "BAD_REQUEST", message: "Der gegebene refresh token ist nicht gültig.", }); } if (response.expiry < new Date()) { // Falls der Token abgelaufen ist, müssen wir ihn löschen. await prisma.refreshTokens.delete({ where: { id: response.id, }, }); throw new APIError({ code: "BAD_REQUEST", message: "Der gegebene refresh token ist nicht gültig.", }); } // TODO: Das können wir später implementieren, falls wir das wirklich brauchen. // if (response.ip !== opts.ctx.ip) { // // Falls der Token nicht von der selben IP Adresse kommt, müssen wir ihn löschen. // await prisma.refreshTokens.delete({ // where: { // id: response.id // } // }) // throw new APIError({ code: "BAD_REQUEST", message: "Der gegebene refresh token wurde von einer anderen IP-Adresse ausgestellt, aus Sicherheitsgründen haben wir uns entschieden diesen zu invalidieren." }); // } const user = await prisma.benutzer.findUnique({ where: { id: response.benutzer_id, }, }); if (!user) { // Falls der Nutzer nicht mehr existiert müssen wir den Refresh Token invalidieren. await prisma.refreshTokens.delete({ where: { id: response.id, }, }); throw new APIError({ code: "BAD_REQUEST", message: "Der gegebene refresh token ist nicht mehr gültig.", }); } // Wir löschen den alten Token. await prisma.refreshTokens.delete({ where: { id: response.id, }, }); const refreshTokenExpiry = moment().add(30, "days"); const refreshToken = encodeToken({ uid: user.uid, typ: TokenType.Refresh, exp: refreshTokenExpiry.valueOf(), }); // Und erstellen einen neuen await prisma.refreshTokens.create({ data: { benutzer_id: user.id, expiry: refreshTokenExpiry.toDate(), ip: ctx.clientAddress ?? "", token: refreshToken, }, }); const accessTokenExpiry = moment().add(2, "days").valueOf(); const accessToken = encodeToken({ uid: user.uid, typ: TokenType.Access, exp: accessTokenExpiry, }); return { accessToken, accessTokenExpiry: accessTokenExpiry, refreshToken, refreshTokenExpiry: refreshTokenExpiry.valueOf(), }; }, });