Files
online-energieausweis/src/pages/api/auth/access-token.ts
2025-02-20 21:25:38 +11:00

125 lines
3.4 KiB
TypeScript

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(),
};
},
});