🛂 Add key-based permission system

This commit is contained in:
2023-06-20 19:53:34 +02:00
parent 917783d114
commit 5d9317ac01
11 changed files with 432 additions and 17 deletions

View File

@ -101,6 +101,9 @@ async function checkLogin(req, res, next) {
res.sendStatus(401);
return;
}
req.locals = {
session: session.token,
};
renewSession(session);
next();
}

View File

@ -1,6 +1,60 @@
import Prisma from "@prisma/client";
const prisma = new Prisma.PrismaClient();
import {
applyKey,
hasPermission,
listPermissions,
revokeKey,
} from "./permission.js";
// Get info API endpoint (/api/info)
// Returns information about the requesting session
export async function getInfo(req, res) {
const session = await prisma.session.findUnique({
where: {
token: req.locals.session,
},
include: {
appliedKeys: {
select: {
key: true,
permissions: true,
validUntil: true,
},
},
},
});
res.send({
authenticated: true,
appliedKeys: session.appliedKeys,
permissions: await listPermissions(session.token),
});
}
// Put and Delete key API endpoints (/api/key)
// Applies or revokes a key from the requesting user's session
export async function putKey(req, res) {
if (await applyKey(req.locals.session, req.query.key)) {
res.status(200).send();
} else {
res.status(400).send({
success: false,
error: "invalid_key",
message: "This key does not exist",
});
}
}
export async function deleteKey(req, res) {
if (await revokeKey(req.locals.session, req.query.key)) {
res.status(200).send();
} else {
res.status(400).send();
}
}
// Get timetable API endpoint (/api/timetable)
// Returns timetable data for requested class if available
export async function getTimetable(req, res) {

95
server/api/permission.js Normal file
View File

@ -0,0 +1,95 @@
import Prisma from "@prisma/client";
import { log } from "../logs.js";
const prisma = new Prisma.PrismaClient();
export async function listPermissions(sessionToken) {
const session = await prisma.session.findUnique({
where: {
token: sessionToken,
},
include: {
appliedKeys: "true",
},
});
if (!session) return [];
const perms = [];
for (const key of session.appliedKeys) {
if (key.validUntil && new Date() > key.validUntil) continue;
for (const perm of key.permissions) {
perms.push(perm);
}
}
return perms;
}
export async function hasPermission(sessionToken, permission, forValue) {
let hasPermission = false;
for (const perm of await listPermissions(sessionToken)) {
if (perm == permission) hasPermission = true;
else if (perm == permission + ":" + forValue) hasPermission = true;
}
return hasPermission;
}
export async function applyKey(sessionToken, key) {
if (!key) return false;
const foundKey = await prisma.key.findUnique({
where: {
key,
},
});
if (!foundKey) return false;
await prisma.session.update({
where: {
token: sessionToken,
},
data: {
appliedKeys: {
connect: {
key: foundKey.key,
},
},
},
});
return true;
}
export async function revokeKey(sessionToken, key) {
if (!key) return false;
await prisma.session.update({
where: {
token: sessionToken,
},
data: {
appliedKeys: {
disconnect: {
key: key,
},
},
},
});
return true;
}
// Clean up expired keys every hour
setInterval(async () => {
const keys = await prisma.key.findMany();
for (const key of keys) {
if (key.validUntil && key.validUntil < new Date()) {
log(
"API / Permissions",
`Removed expired key: ${key.key}; Permissions: ${key.permissions.join(
", "
)}`
);
await prisma.key.delete({
where: {
key: key.key,
},
});
}
}
}, 1000 * 60 * 60);

View File

@ -8,6 +8,9 @@ import {
getSubstitutions,
getHistory,
getClasses,
getInfo,
putKey,
deleteKey,
} from "./api/index.js";
import auth from "./api/auth.js";
import { Parser } from "./parser/index.js";
@ -30,6 +33,7 @@ const port = process.env.PORT || 3000;
app.use(cors());
app.use(cookieParser());
app.use(express.urlencoded({ extended: true }));
app.use(express.json());
// Initialize the Parser and set it to update the
// substitution plan at the specified update interval
@ -55,6 +59,9 @@ app.get("/api/check", (_req, res) => {
});
// Register API endpoints
app.get("/api/info", getInfo);
app.put("/api/key", putKey);
app.delete("/api/key", deleteKey);
app.get("/api/timetable", getTimetable);
app.get("/api/substitutions", getSubstitutions);
app.get("/api/history", getHistory);

View File

@ -73,7 +73,18 @@ model Time {
}
model Session {
token String @id @unique @default(uuid())
createdAt DateTime @default(now())
validUntil DateTime
token String @id @unique @default(uuid())
createdAt DateTime @default(now())
validUntil DateTime
appliedKeys Key[]
}
model Key {
key String @id @unique @default(uuid())
createdAt DateTime? @default(now())
validUntil DateTime?
permissions String[]
notes String?
Session Session? @relation(fields: [sessionToken], references: [token])
sessionToken String?
}