refactor(config): ♻️ Redo config structure from scratch, simplify validation code, improve checks, add support for loading sensitive data from paths

This commit is contained in:
Jesse Wierzbinski 2025-02-15 02:47:29 +01:00
parent d4afd84019
commit 54fd81f076
No known key found for this signature in database
118 changed files with 3892 additions and 5291 deletions

View file

@ -1,10 +1,10 @@
import { z } from "@hono/zod-openapi";
import { Hooks, Plugin } from "@versia/kit";
import { User } from "@versia/kit/db";
import chalk from "chalk";
import { getCookie } from "hono/cookie";
import { jwtVerify } from "jose";
import { JOSEError, JWTExpired } from "jose/errors";
import { keyPair, sensitiveString } from "~/classes/config/schema.ts";
import { ApiError } from "~/classes/errors/api-error.ts";
import { RolePermissions } from "~/drizzle/schema.ts";
import authorizeRoute from "./routes/authorize.ts";
@ -26,64 +26,12 @@ const configSchema = z.object({
id: z.string().min(1),
url: z.string().min(1),
client_id: z.string().min(1),
client_secret: z.string().min(1),
client_secret: sensitiveString,
icon: z.string().min(1).optional(),
}),
)
.default([]),
keys: z
.object({
public: z
.string()
.min(1)
.transform(async (v) => {
try {
return await crypto.subtle.importKey(
"spki",
Buffer.from(v, "base64"),
"Ed25519",
true,
["verify"],
);
} catch {
throw new Error(
"Public key at oidc.keys.public is invalid",
);
}
}),
private: z
.string()
.min(1)
.transform(async (v) => {
try {
return await crypto.subtle.importKey(
"pkcs8",
Buffer.from(v, "base64"),
"Ed25519",
true,
["sign"],
);
} catch {
throw new Error(
"Private key at oidc.keys.private is invalid",
);
}
}),
})
.optional()
.transform(async (v, ctx) => {
if (!(v?.private && v?.public)) {
const { public_key, private_key } = await User.generateKeys();
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: `Keys are missing, please add the following to your config:\n\nkeys.public: ${chalk.gray(public_key)}\nkeys.private: ${chalk.gray(private_key)}
`,
});
}
return v as Exclude<typeof v, undefined>;
}),
keys: keyPair,
});
const plugin = new Plugin(configSchema);

View file

@ -3,7 +3,7 @@ import { randomString } from "@/math";
import { Application } from "@versia/kit/db";
import { RolePermissions } from "@versia/kit/tables";
import { SignJWT } from "jose";
import { config } from "~/packages/config-manager";
import { config } from "~/config.ts";
import { fakeRequest, getTestUsers } from "~/tests/utils";
const { deleteUsers, tokens, users } = await getTestUsers(1);

View file

@ -223,7 +223,7 @@ export default (plugin: PluginType): void =>
...payload,
name: user.data.displayName,
preferred_username: user.data.username,
picture: user.getAvatarUrl(context.get("config")),
picture: user.getAvatarUrl(),
updated_at: new Date(
user.data.updatedAt,
).toISOString(),

View file

@ -6,6 +6,7 @@ import { OpenIdAccounts, RolePermissions, Users } from "@versia/kit/tables";
import { setCookie } from "hono/cookie";
import { SignJWT } from "jose";
import { ApiError } from "~/classes/errors/api-error.ts";
import { Account as AccountSchema } from "~/classes/schemas/account.ts";
import type { PluginType } from "../../index.ts";
import { automaticOidcFlow } from "../../utils.ts";
@ -199,30 +200,8 @@ export default (plugin: PluginType): void => {
email?.split("@")[0] ??
randomString(8, "hex");
const usernameValidator = z
.string()
.regex(/^[a-z0-9_]+$/)
.min(3)
.max(
context.get("config").validation
.max_username_size,
)
.refine(
(value) =>
!context
.get("config")
.validation.username_blacklist.includes(
value,
),
)
.refine((value) =>
context
.get("config")
.filters.username.some((filter) =>
value.match(filter),
),
)
.refine(
const usernameValidator =
AccountSchema.shape.username.refine(
async (value) =>
!(await User.fromSql(
and(