feat: user page & $dropFetch util
This commit is contained in:
@@ -0,0 +1,14 @@
|
||||
import { AuthMec } from "@prisma/client";
|
||||
import aclManager from "~/server/internal/acls";
|
||||
import { applicationSettings } from "~/server/internal/config/application-configuration";
|
||||
|
||||
export default defineEventHandler(async (h3) => {
|
||||
const allowed = await aclManager.allowSystemACL(h3, ["auth:read"]);
|
||||
if (!allowed) throw createError({ statusCode: 403 });
|
||||
|
||||
const enabledMechanisms: AuthMec[] = await applicationSettings.get(
|
||||
"enabledAuthencationMechanisms"
|
||||
);
|
||||
|
||||
return enabledMechanisms;
|
||||
});
|
||||
@@ -0,0 +1,26 @@
|
||||
import aclManager from "~/server/internal/acls";
|
||||
import prisma from "~/server/internal/db/database";
|
||||
|
||||
export default defineEventHandler(async (h3) => {
|
||||
const allowed = await aclManager.allowSystemACL(h3, ["user:read"]);
|
||||
if (!allowed) throw createError({ statusCode: 403 });
|
||||
|
||||
const userId = getRouterParam(h3, "id");
|
||||
if (!userId)
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: "No userId in route.",
|
||||
});
|
||||
|
||||
if (userId == "system")
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: "Cannot delete system user.",
|
||||
});
|
||||
|
||||
const user = await prisma.user.findUnique({ where: { id: userId } });
|
||||
if (!user)
|
||||
throw createError({ statusCode: 404, statusMessage: "User not found." });
|
||||
|
||||
return user;
|
||||
});
|
||||
@@ -5,7 +5,18 @@ export default defineEventHandler(async (h3) => {
|
||||
const allowed = await aclManager.allowSystemACL(h3, ["user:read"]);
|
||||
if (!allowed) throw createError({ statusCode: 403 });
|
||||
|
||||
const users = await prisma.user.findMany({});
|
||||
const users = await prisma.user.findMany({
|
||||
where: {
|
||||
id: { not: "system" },
|
||||
},
|
||||
include: {
|
||||
authMecs: {
|
||||
select: {
|
||||
mec: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return users;
|
||||
});
|
||||
@@ -5,34 +5,57 @@ import { checkHash } from "~/server/internal/security/simple";
|
||||
import sessionHandler from "~/server/internal/session";
|
||||
|
||||
export default defineEventHandler(async (h3) => {
|
||||
const body = await readBody(h3);
|
||||
const body = await readBody(h3);
|
||||
|
||||
const username = body.username;
|
||||
const password = body.password;
|
||||
const rememberMe = body.rememberMe ?? false;
|
||||
if (username === undefined || password === undefined)
|
||||
throw createError({ statusCode: 403, statusMessage: "Username or password missing from request." });
|
||||
|
||||
const authMek = await prisma.linkedAuthMec.findFirst({
|
||||
where: {
|
||||
mec: AuthMec.Simple,
|
||||
credentials: {
|
||||
array_starts_with: username
|
||||
}
|
||||
}
|
||||
const username = body.username;
|
||||
const password = body.password;
|
||||
const rememberMe = body.rememberMe ?? false;
|
||||
if (username === undefined || password === undefined)
|
||||
throw createError({
|
||||
statusCode: 403,
|
||||
statusMessage: "Username or password missing from request.",
|
||||
});
|
||||
|
||||
if (!authMek) throw createError({ statusCode: 401, statusMessage: "Invalid username or password." });
|
||||
const authMek = await prisma.linkedAuthMec.findFirst({
|
||||
where: {
|
||||
mec: AuthMec.Simple,
|
||||
credentials: {
|
||||
array_starts_with: username,
|
||||
},
|
||||
enabled: true,
|
||||
},
|
||||
include: {
|
||||
user: {
|
||||
select: {
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const credentials = authMek.credentials as JsonArray;
|
||||
const hash = credentials.at(1);
|
||||
if (!authMek)
|
||||
throw createError({
|
||||
statusCode: 401,
|
||||
statusMessage: "Invalid username or password.",
|
||||
});
|
||||
|
||||
if (!hash) throw createError({ statusCode: 403, statusMessage: "Invalid or disabled account. Please contact the server administrator." });
|
||||
const credentials = authMek.credentials as JsonArray;
|
||||
const hash = credentials.at(1);
|
||||
|
||||
if (!await checkHash(password, hash.toString()))
|
||||
throw createError({ statusCode: 401, statusMessage: "Invalid username or password." });
|
||||
if (!hash || !authMek.user.enabled)
|
||||
throw createError({
|
||||
statusCode: 403,
|
||||
statusMessage:
|
||||
"Invalid or disabled account. Please contact the server administrator.",
|
||||
});
|
||||
|
||||
await sessionHandler.setUserId(h3, authMek.userId, rememberMe);
|
||||
if (!(await checkHash(password, hash.toString())))
|
||||
throw createError({
|
||||
statusCode: 401,
|
||||
statusMessage: "Invalid username or password.",
|
||||
});
|
||||
|
||||
return { result: true, userId: authMek.userId }
|
||||
});
|
||||
await sessionHandler.setUserId(h3, authMek.userId, rememberMe);
|
||||
|
||||
return { result: true, userId: authMek.userId };
|
||||
});
|
||||
|
||||
@@ -35,6 +35,7 @@ export const userACLDescriptions: ObjectFromList<typeof userACLs> = {
|
||||
};
|
||||
|
||||
export const systemACLDescriptions: ObjectFromList<typeof systemACLs> = {
|
||||
"auth:read": "Fetch the list of enabled authentication mechanisms configured.",
|
||||
"auth:simple:invitation:read": "Fetch simple auth invitations.",
|
||||
"auth:simple:invitation:new": "Create new simple auth invitations.",
|
||||
"auth:simple:invitation:delete": "Delete a simple auth invitation.",
|
||||
|
||||
@@ -33,6 +33,7 @@ const userACLPrefix = "user:";
|
||||
type UserACL = Array<(typeof userACLs)[number]>;
|
||||
|
||||
export const systemACLs = [
|
||||
"auth:read",
|
||||
"auth:simple:invitation:read",
|
||||
"auth:simple:invitation:new",
|
||||
"auth:simple:invitation:delete",
|
||||
|
||||
@@ -3,20 +3,12 @@ import prisma from "../db/database";
|
||||
|
||||
class ApplicationConfiguration {
|
||||
// Reference to the currently selected application configuration
|
||||
private currentApplicationSettings: ApplicationSettings = {
|
||||
timestamp: new Date(),
|
||||
enabledAuthencationMechanisms: [],
|
||||
metadataProviders: [],
|
||||
};
|
||||
private applicationStateProxy: object;
|
||||
private dirty: boolean = false;
|
||||
private dirtyPromise: Promise<any> | undefined = undefined;
|
||||
|
||||
constructor() {
|
||||
this.applicationStateProxy = {};
|
||||
}
|
||||
private currentApplicationSettings: ApplicationSettings | undefined =
|
||||
undefined;
|
||||
|
||||
private async save() {
|
||||
await this.init();
|
||||
|
||||
const deepAppConfigCopy: Omit<ApplicationSettings, "timestamp"> & {
|
||||
timestamp?: Date;
|
||||
} = JSON.parse(JSON.stringify(this.currentApplicationSettings));
|
||||
@@ -28,6 +20,19 @@ class ApplicationConfiguration {
|
||||
});
|
||||
}
|
||||
|
||||
private async init() {
|
||||
if (this.currentApplicationSettings === undefined) {
|
||||
const applicationSettingsCount = await prisma.applicationSettings.count(
|
||||
{}
|
||||
);
|
||||
if (applicationSettingsCount > 0) {
|
||||
await applicationSettings.pullConfiguration();
|
||||
} else {
|
||||
await applicationSettings.initialiseConfiguration();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Default application configuration
|
||||
async initialiseConfiguration() {
|
||||
const initialState = await prisma.applicationSettings.create({
|
||||
@@ -56,6 +61,10 @@ class ApplicationConfiguration {
|
||||
key: T,
|
||||
value: ApplicationSettings[T]
|
||||
) {
|
||||
await this.init();
|
||||
if (!this.currentApplicationSettings)
|
||||
throw new Error("Somehow, failed to initialise application settings");
|
||||
|
||||
if (this.currentApplicationSettings[key] !== value) {
|
||||
this.currentApplicationSettings[key] = value;
|
||||
|
||||
@@ -63,7 +72,11 @@ class ApplicationConfiguration {
|
||||
}
|
||||
}
|
||||
|
||||
get<T extends keyof ApplicationSettings>(key: T): ApplicationSettings[T] {
|
||||
async get<T extends keyof ApplicationSettings>(key: T): Promise<ApplicationSettings[T]> {
|
||||
await this.init();
|
||||
if (!this.currentApplicationSettings)
|
||||
throw new Error("Somehow, failed to initialise application settings");
|
||||
|
||||
return this.currentApplicationSettings[key];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,7 +125,7 @@ export abstract class ObjectBackend {
|
||||
// If we need to fetch a remote resource, it doesn't make sense
|
||||
// to immediately fetch the object, *then* check permissions.
|
||||
// Instead the caller can pass a simple anonymous funciton, like
|
||||
// () => $fetch('/my-image');
|
||||
// () => $dropFetch('/my-image');
|
||||
// And if we actually have permission to write, it fetches it then.
|
||||
async writeWithPermissions(
|
||||
id: ObjectReference,
|
||||
|
||||
@@ -2,14 +2,6 @@ import { applicationSettings } from "../internal/config/application-configuratio
|
||||
import prisma from "../internal/db/database";
|
||||
|
||||
export default defineNitroPlugin(async (nitro) => {
|
||||
const applicationSettingsCount = await prisma.applicationSettings.count({});
|
||||
if (applicationSettingsCount > 0) {
|
||||
await applicationSettings.pullConfiguration();
|
||||
} else {
|
||||
await applicationSettings.initialiseConfiguration();
|
||||
}
|
||||
console.log("initalised application config");
|
||||
|
||||
// Ensure system user exists
|
||||
// The system user owns any user-based code
|
||||
// that we want to re-use for the app
|
||||
|
||||
@@ -27,7 +27,9 @@ export default defineNitroPlugin(async (nitro) => {
|
||||
}
|
||||
|
||||
// Add providers based on their position in the application settings
|
||||
const configuredProviderList = applicationSettings.get("metadataProviders");
|
||||
const configuredProviderList = await applicationSettings.get(
|
||||
"metadataProviders"
|
||||
);
|
||||
const max = configuredProviderList.length;
|
||||
for (const [index, providerId] of configuredProviderList.entries()) {
|
||||
const priority = max * 2 - index; // Offset by the length --- (max - index) + max
|
||||
|
||||
Reference in New Issue
Block a user