125 lines
3.4 KiB
TypeScript
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(),
|
|
};
|
|
},
|
|
});
|