Various bug fixes (#102)

* feat: set lang in html head

* fix: add # in front of git ref

* fix: remove unused vars from example env

* fix: package name and license field

* fix: enable sourcemap for client and server

* fix: emojis not showing in prod

this is extremely cursed, but it works

* chore: refactor auth manager

* feat: disable invitations if simple auth disabled

* feat: add drop version to footer

* feat: translate auth endpoints

* chore: move oidc module

* feat: add weekly tasks

enabled object cleanup as weekly task

* feat: add timestamp to task log msgs

* feat: add guard to prevent invalid progress %

* fix: add missing global scope to i18n components

* feat: set base url for i18n

* feat: switch task log to json format

* ci: run ci on develop branch only

* fix: UserWidget text not updating #109

* fix: EXTERNAL_URL being computed at build

* feat: add basic language outlines for translation

* feat: add more english dialects
This commit is contained in:
Husky
2025-06-07 23:49:43 -04:00
committed by GitHub
parent 9f5a3b3976
commit 72ae7a2884
43 changed files with 577 additions and 229 deletions

View File

@ -1,11 +1,13 @@
import { AuthMec } from "~/prisma/client";
import aclManager from "~/server/internal/acls";
import { enabledAuthManagers } from "~/server/plugins/04.auth-init";
import authManager from "~/server/internal/auth";
export default defineEventHandler(async (h3) => {
const allowed = await aclManager.allowSystemACL(h3, ["auth:read"]);
if (!allowed) throw createError({ statusCode: 403 });
const enabledAuthManagers = authManager.getAuthProviders();
const authData = {
[AuthMec.Simple]: enabledAuthManagers.Simple,
[AuthMec.OpenID]:

View File

@ -30,6 +30,7 @@ export default defineEventHandler(async (h3) => {
take: 10,
});
const dailyTasks = await taskHandler.dailyTasks();
const weeklyTasks = await taskHandler.weeklyTasks();
return { runningTasks, historicalTasks, dailyTasks };
return { runningTasks, historicalTasks, dailyTasks, weeklyTasks };
});

View File

@ -1,9 +1,5 @@
import { enabledAuthManagers } from "~/server/plugins/04.auth-init";
import authManager from "~/server/internal/auth";
export default defineEventHandler(() => {
const authManagers = Object.entries(enabledAuthManagers)
.filter((e) => !!e[1])
.map((e) => e[0]);
return authManagers;
return authManager.getEnabledAuthProviders();
});

View File

@ -2,12 +2,11 @@ import { AuthMec } from "~/prisma/client";
import type { JsonArray } from "@prisma/client/runtime/library";
import { type } from "arktype";
import prisma from "~/server/internal/db/database";
import {
import sessionHandler from "~/server/internal/session";
import authManager, {
checkHashArgon2,
checkHashBcrypt,
} from "~/server/internal/security/simple";
import sessionHandler from "~/server/internal/session";
import { enabledAuthManagers } from "~/server/plugins/04.auth-init";
} from "~/server/internal/auth";
const signinValidator = type({
username: "string",
@ -18,10 +17,12 @@ const signinValidator = type({
export default defineEventHandler<{
body: typeof signinValidator.infer;
}>(async (h3) => {
if (!enabledAuthManagers.Simple)
const t = await useTranslation(h3);
if (!authManager.getAuthProviders().Simple)
throw createError({
statusCode: 403,
statusMessage: "Sign in method not enabled",
statusMessage: t("errors.auth.method.signinDisabled"),
});
const body = signinValidator(await readBody(h3));
@ -55,14 +56,13 @@ export default defineEventHandler<{
if (!authMek)
throw createError({
statusCode: 401,
statusMessage: "Invalid username or password.",
statusMessage: t("errors.auth.invalidUserOrPass"),
});
if (!authMek.user.enabled)
throw createError({
statusCode: 403,
statusMessage:
"Invalid or disabled account. Please contact the server administrator.",
statusMessage: t("errors.auth.disabled"),
});
// LEGACY bcrypt
@ -72,15 +72,14 @@ export default defineEventHandler<{
if (!hash)
throw createError({
statusCode: 403,
statusMessage:
"Invalid password state. Please contact the server administrator.",
statusCode: 500,
statusMessage: t("errors.auth.invalidPassState"),
});
if (!(await checkHashBcrypt(body.password, hash)))
throw createError({
statusCode: 401,
statusMessage: "Invalid username or password.",
statusMessage: t("errors.auth.invalidUserOrPass"),
});
// TODO: send user to forgot password screen or something to force them to change their password to new system
@ -93,14 +92,13 @@ export default defineEventHandler<{
if (!hash || typeof hash !== "string")
throw createError({
statusCode: 500,
statusMessage:
"Invalid password state. Please contact the server administrator.",
statusMessage: t("errors.auth.invalidPassState"),
});
if (!(await checkHashArgon2(body.password, hash)))
throw createError({
statusCode: 401,
statusMessage: "Invalid username or password.",
statusMessage: t("errors.auth.invalidUserOrPass"),
});
await sessionHandler.signin(h3, authMek.userId, body.rememberMe);

View File

@ -1,13 +1,22 @@
import prisma from "~/server/internal/db/database";
import taskHandler from "~/server/internal/tasks";
import authManager from "~/server/internal/auth";
export default defineEventHandler(async (h3) => {
const t = await useTranslation(h3);
if (!authManager.getAuthProviders().Simple)
throw createError({
statusCode: 403,
statusMessage: t("errors.auth.method.signinDisabled"),
});
const query = getQuery(h3);
const id = query.id?.toString();
if (!id)
throw createError({
statusCode: 400,
statusMessage: "id required in fetching invitation",
statusMessage: t("errors.auth.inviteIdRequired"),
});
taskHandler.runTaskGroupByName("cleanup:invitations");
@ -15,7 +24,7 @@ export default defineEventHandler(async (h3) => {
if (!invitation)
throw createError({
statusCode: 404,
statusMessage: "Invalid or expired invitation",
statusMessage: t("errors.auth.invalidInvite"),
});
return invitation;

View File

@ -1,6 +1,6 @@
import { AuthMec } from "~/prisma/client";
import prisma from "~/server/internal/db/database";
import { createHashArgon2 } from "~/server/internal/security/simple";
import authManager, { createHashArgon2 } from "~/server/internal/auth";
import * as jdenticon from "jdenticon";
import objectHandler from "~/server/internal/objects";
import { type } from "arktype";
@ -18,13 +18,21 @@ export const CreateUserValidator = type({
export default defineEventHandler<{
body: typeof CreateUserValidator.infer;
}>(async (h3) => {
const t = await useTranslation(h3);
if (!authManager.getAuthProviders().Simple)
throw createError({
statusCode: 403,
statusMessage: t("errors.auth.method.signinDisabled"),
});
const user = await readValidatedBody(h3, CreateUserValidator);
const invitationId = user.invitation;
if (!invitationId)
throw createError({
statusCode: 401,
statusMessage: "Invalid or expired invitation.",
statusMessage: t("errors.auth.invalidInvite"),
});
const invitation = await prisma.invitation.findUnique({
@ -33,7 +41,7 @@ export default defineEventHandler<{
if (!invitation)
throw createError({
statusCode: 401,
statusMessage: "Invalid or expired invitation.",
statusMessage: t("errors.auth.invalidInvite"),
});
// reuse items from invite
@ -46,7 +54,7 @@ export default defineEventHandler<{
if (existing > 0)
throw createError({
statusCode: 400,
statusMessage: "Username already taken.",
statusMessage: t("errors.auth.usernameTaken"),
});
const userId = randomUUID();

View File

@ -1,45 +0,0 @@
import path from "path";
import module from "module";
import fs from "fs/promises";
import sanitize from "sanitize-filename";
import aclManager from "~/server/internal/acls";
const twemojiJson = module.findPackageJSON(
"@discordapp/twemoji",
import.meta.url,
);
export default defineEventHandler(async (h3) => {
const userId = await aclManager.getUserIdACL(h3, ["object:read"]);
if (!userId)
throw createError({
statusCode: 403,
});
if (!twemojiJson)
throw createError({
statusCode: 500,
statusMessage: "Failed to resolve emoji package",
});
const unsafeId = getRouterParam(h3, "id");
if (!unsafeId)
throw createError({ statusCode: 400, statusMessage: "Invalid ID" });
const svgPath = path.join(
path.dirname(twemojiJson),
"dist",
"svg",
sanitize(unsafeId),
);
setHeader(
h3,
"Cache-Control",
// 7 days
"public, max-age=604800, s-maxage=604800",
);
setHeader(h3, "Content-Type", "image/svg+xml");
return await fs.readFile(svgPath);
});

View File

@ -4,6 +4,6 @@ export default defineEventHandler((_h3) => {
return {
appName: "Drop",
version: systemConfig.getDropVersion(),
ref: systemConfig.getGitRef(),
gitRef: `#${systemConfig.getGitRef()}`,
};
});