WIP on dev-moritz

This commit is contained in:
Moritz Utcke
2025-01-21 12:35:20 +07:00
parent 5a551c0f33
commit de8c859826
59 changed files with 1221 additions and 397 deletions

View File

@@ -0,0 +1,46 @@
import { authorizationMiddleware } from "#lib/middleware/authorization.js";
import { prisma } from "@ibcornelsen/database/server";
import { APIError, defineApiRoute } from "astro-typesafe-api/server";
export const PATCH = defineApiRoute({
fetch(input, context) {},
});
export const GET = defineApiRoute({
meta: {
description: "Gibt eine spezifische Aufnhame eines Objektes des Benutzers zurück.",
tags: ["Aufnahme"],
headers: {
"Authorization": {
description: "Ein gültiger Authentifizierungstoken",
required: true,
allowEmptyValue: false,
examples: {
Bearer: {
value: "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
}
}
}
},
middleware: authorizationMiddleware,
async fetch(input, context, user) {
const { uid } = context.params;
const aufnahme = await prisma.aufnahme.findUnique({
where: {
uid,
benutzer_id: user.id
},
});
if (!aufnahme) {
throw new APIError({
code: "NOT_FOUND",
message: "Aufnahme mit dieser UID existiert nicht oder gehört nicht dem aktuellen Benutzer."
})
}
return aufnahme
},
});

View File

View File

@@ -0,0 +1,125 @@
import { z } from "zod";
import moment from "moment";
import { prisma } from "@ibcornelsen/database/server";
import { TokenType, encodeToken } from "../../../lib/auth/token.js";
import { TRPCError } from "@trpc/server";
import { defineApiRoute } from "astro-typesafe-api/server";
export const tRPC_V1_BenutzerGetAccessTokenProcedure = 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 TRPCError({
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 TRPCError({
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 TRPCError({ 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 TRPCError({
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.unix(),
});
// 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").unix();
const accessToken = encodeToken({
uid: user.uid,
typ: TokenType.Access,
exp: accessTokenExpiry,
});
return {
accessToken,
accessTokenExpiry: accessTokenExpiry,
refreshToken,
refreshTokenExpiry: refreshTokenExpiry.unix(),
};
},
});

View File

@@ -0,0 +1,82 @@
import { z } from "zod";
import moment from "moment";
import { prisma } from "@ibcornelsen/database/server";
import { TokenType, encodeToken } from "../../../lib/auth/token.js";
import { hashPassword, validatePassword } from "../../../lib/password.js";
import { APIError, defineApiRoute } from "astro-typesafe-api/server";
export const GET = defineApiRoute({
meta: {
description:
"Erstellt sowohl einen neuen Refresh Token als auch einen Access Token für den gegebenen Benutzer. Der Refresh Token kann später für die Erstellung neuer Access Token genutzt werden.",
tags: ["Benutzer"],
summary: "Refresh Token anfragen.",
},
input: z.object({
email: z.string().email(),
passwort: z.string().min(8).max(100),
}),
output: z.object({
uid: z.string().uuid(),
accessToken: z.string(),
refreshToken: z.string(),
refreshTokenBase64: z.string(),
accessTokenBase64: z.string(),
exp: z.number(),
}),
async fetch(input, ctx) {
console.log(input);
// Falls der Nutzer nicht existiert, wird eine Fehlermeldung zurückgegeben.
const user = await prisma.benutzer.findUnique({
where: {
email: input.email,
},
});
if (!user) {
throw new APIError({
code: "BAD_REQUEST",
message: "Benutzer konnte nicht gefunden werden.",
});
}
// Falls das Passwort nicht stimmt, wird eine Fehlermeldung zurückgegeben.
if (!validatePassword(user.passwort, input.passwort)) {
throw new APIError({
code: "BAD_REQUEST",
message: "Benutzer konnte nicht gefunden werden.",
});
}
const refreshTokenExpiry = moment().add(30, "days");
const accessToken = encodeToken({
uid: user.uid,
typ: TokenType.Access,
exp: moment().add(30, "minutes").valueOf(),
});
const refreshToken = encodeToken({
uid: user.uid,
typ: TokenType.Refresh,
exp: refreshTokenExpiry.valueOf(),
});
const { id } = await prisma.refreshTokens.create({
data: {
token: refreshToken,
benutzer_id: user.id,
ip: ctx.clientAddress ?? "",
expiry: refreshTokenExpiry.toDate(),
},
});
return {
uid: user.uid,
accessToken,
refreshToken,
refreshTokenBase64: Buffer.from(refreshToken).toString("base64"),
accessTokenBase64: Buffer.from(accessToken).toString("base64"),
exp: refreshTokenExpiry.valueOf(),
};
},
});

View File

@@ -0,0 +1,46 @@
import { authorizationMiddleware } from "#lib/middleware/authorization.js";
import { prisma } from "@ibcornelsen/database/server";
import { APIError, defineApiRoute } from "astro-typesafe-api/server";
export const PATCH = defineApiRoute({
fetch(input, context) {},
});
export const GET = defineApiRoute({
meta: {
description: "Gibt ein spezifisches Gebäude des Benutzers zurück.",
tags: ["Gebäude"],
headers: {
"Authorization": {
description: "Ein gültiger Authentifizierungstoken",
required: true,
allowEmptyValue: false,
examples: {
Bearer: {
value: "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
}
}
}
},
middleware: authorizationMiddleware,
async fetch(input, context, user) {
const { uid } = context.params;
const objekt = await prisma.objekt.findUnique({
where: {
uid,
benutzer_id: user.id
},
});
if (!objekt) {
throw new APIError({
code: "NOT_FOUND",
message: "Objekt mit dieser UID existiert nicht oder gehört nicht dem aktuellen Benutzer."
})
}
return objekt
},
});

View File

@@ -0,0 +1,30 @@
import { authorizationMiddleware } from "#lib/middleware/authorization.js";
import { ObjektSchema, prisma } from "@ibcornelsen/database/server";
import { defineApiRoute } from "astro-typesafe-api/server";
import { z } from "zod";
export const POST = defineApiRoute({
fetch(input, context) {
},
})
export const GET = defineApiRoute({
input: z.object({
limit: z.number()
}),
output: z.array(ObjektSchema),
middleware: authorizationMiddleware,
async fetch(input, context, transfer) {
const objekte = await prisma.objekt.findMany({
take: input.limit,
where: {
benutzer: {
id: transfer.id
}
}
})
return objekte
},
})

View File

@@ -0,0 +1,23 @@
import { BenutzerSchema } from "@ibcornelsen/database/server";
import { z } from "zod";
import { defineApiRoute } from "astro-typesafe-api/server";
import { authorizationMiddleware } from "#lib/middleware/authorization.js";
export const GET = defineApiRoute({
meta: {
description:
"Gibt die Daten des momentan eingeloggten Benutzers zurück. Falls der Authorization Key invalid ist wird stattdessen null zurückgegeben.",
summary: "Gibt die Daten des eingeloggten Benutzers zurück.",
tags: ["Benutzer"],
},
input: z.void(),
output: BenutzerSchema.omit({
passwort: true,
id: true,
}).or(z.null()),
middleware: authorizationMiddleware,
async fetch(input, ctx, transfer) {
return transfer;
},
});

View File

@@ -0,0 +1,71 @@
import { authorizationMiddleware } from "#lib/middleware/authorization.js";
import { prisma } from "@ibcornelsen/database/server";
import { APIError, defineApiRoute } from "astro-typesafe-api/server";
export const PATCH = defineApiRoute({
fetch(input, context) {},
});
export const GET = defineApiRoute({
meta: {
description: "Gibt ein spezifisches Gebäude des Benutzers zurück.",
tags: ["Gebäude"],
headers: {
"Authorization": {
description: "Ein gültiger Authentifizierungstoken",
required: true,
allowEmptyValue: false,
examples: {
Bearer: {
value: "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
}
}
}
},
middleware: authorizationMiddleware,
async fetch(input, context, user) {
const { uid } = context.params;
const ausweis = await prisma.verbrauchsausweisWohnen.findUnique({
where: {
uid: input.uid,
},
include: {
benutzer: true,
aufnahme: {
include: {
objekt: {
include: {
gebaeude_bilder: true
},
},
rechnungen: true,
events: {
include: {
benutzer: {
select: {
uid: true
}
}
},
orderBy: {
date: "asc"
}
}
},
}
},
});
if (!ausweis || (ausweis.benutzer_id !== null && ausweis.benutzer_id !== user.id)) {
// Falls wir den Ausweis nicht finden können, werfen wir einen Fehler
throw new APIError({
code: "NOT_FOUND",
message: "Ausweis konnte nicht gefunden werden.",
});
}
return ausweis
},
});

View File

@@ -0,0 +1,71 @@
import { authorizationMiddleware } from "#lib/middleware/authorization.js";
import { prisma } from "@ibcornelsen/database/server";
import { APIError, defineApiRoute } from "astro-typesafe-api/server";
export const PATCH = defineApiRoute({
fetch(input, context) {},
});
export const GET = defineApiRoute({
meta: {
description: "Gibt ein spezifisches Gebäude des Benutzers zurück.",
tags: ["Gebäude"],
headers: {
"Authorization": {
description: "Ein gültiger Authentifizierungstoken",
required: true,
allowEmptyValue: false,
examples: {
Bearer: {
value: "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
}
}
}
},
middleware: authorizationMiddleware,
async fetch(input, context, user) {
const { uid } = context.params;
const ausweis = await prisma.verbrauchsausweisWohnen.findUnique({
where: {
uid,
},
include: {
benutzer: true,
aufnahme: {
include: {
objekt: {
include: {
gebaeude_bilder: true
},
},
rechnungen: true,
events: {
include: {
benutzer: {
select: {
uid: true
}
}
},
orderBy: {
date: "asc"
}
}
},
}
},
});
if (!ausweis || (ausweis.benutzer_id !== null && ausweis.benutzer_id !== user.id)) {
// Falls wir den Ausweis nicht finden können, werfen wir einen Fehler
throw new APIError({
code: "NOT_FOUND",
message: "Ausweis konnte nicht gefunden werden.",
});
}
return ausweis
},
});