refactor: env validation typescript and common package (#1912)

This commit is contained in:
Meier Lukas
2025-01-14 19:03:38 +01:00
committed by GitHub
parent a03a01b964
commit 1fd3fd8dfb
36 changed files with 98 additions and 83 deletions

View File

@@ -8,7 +8,7 @@ import type { SupportedAuthProvider } from "@homarr/definitions";
import { createAdapter } from "./adapter";
import { createSessionCallback } from "./callbacks";
import { env } from "./env.mjs";
import { env } from "./env";
import { createSignInEventHandler } from "./events";
import { createCredentialsConfiguration, createLdapConfiguration } from "./providers/credentials/credentials-provider";
import { EmptyNextAuthProvider } from "./providers/empty/empty-provider";

View File

@@ -1,10 +1,9 @@
import { createEnv } from "@t3-oss/env-nextjs";
import { z } from "zod";
const trueStrings = ["1", "yes", "t", "true"];
const falseStrings = ["0", "no", "f", "false"];
import { createBooleanSchema, createDurationSchema, shouldSkipEnvValidation } from "@homarr/common/env-validation";
import { supportedAuthProviders } from "@homarr/definitions";
const supportedAuthProviders = ["credentials", "oidc", "ldap"];
const authProvidersSchema = z
.string()
.min(1)
@@ -14,7 +13,7 @@ const authProvidersSchema = z
.toLowerCase()
.split(",")
.filter((provider) => {
if (supportedAuthProviders.includes(provider)) return true;
if (supportedAuthProviders.some((supportedProvider) => supportedProvider === provider)) return true;
else if (!provider)
console.log("One or more of the entries for AUTH_PROVIDER could not be parsed and/or returned null.");
else console.log(`The value entered for AUTH_PROVIDER "${provider}" is incorrect.`);
@@ -23,41 +22,7 @@ const authProvidersSchema = z
)
.default("credentials");
const createDurationSchema = (defaultValue) =>
z
.string()
.regex(/^\d+[smhd]?$/)
.default(defaultValue)
.transform((duration) => {
const lastChar = duration[duration.length - 1];
if (!isNaN(Number(lastChar))) {
return Number(defaultValue);
}
const multipliers = {
s: 1,
m: 60,
h: 60 * 60,
d: 60 * 60 * 24,
};
const numberDuration = Number(duration.slice(0, -1));
const multiplier = multipliers[lastChar];
return numberDuration * multiplier;
});
const booleanSchema = z
.string()
.default("false")
.transform((value, ctx) => {
const normalized = value.trim().toLowerCase();
if (trueStrings.includes(normalized)) return true;
if (falseStrings.includes(normalized)) return false;
throw new Error(`Invalid boolean value for ${ctx.path.join(".")}`);
});
const skipValidation = Boolean(process.env.CI) || Boolean(process.env.SKIP_ENV_VALIDATION);
const skipValidation = shouldSkipEnvValidation();
const authProviders = skipValidation ? [] : authProvidersSchema.parse(process.env.AUTH_PROVIDERS);
export const env = createEnv({
@@ -71,7 +36,7 @@ export const env = createEnv({
AUTH_OIDC_CLIENT_ID: z.string().min(1),
AUTH_OIDC_CLIENT_SECRET: z.string().min(1),
AUTH_OIDC_CLIENT_NAME: z.string().min(1).default("OIDC"),
AUTH_OIDC_AUTO_LOGIN: booleanSchema,
AUTH_OIDC_AUTO_LOGIN: createBooleanSchema(false),
AUTH_OIDC_SCOPE_OVERWRITE: z.string().min(1).default("openid email profile groups"),
AUTH_OIDC_GROUPS_ATTRIBUTE: z.string().default("groups"), // Is used in the signIn event to assign the correct groups, key is from object of decoded id_token
AUTH_OIDC_NAME_ATTRIBUTE_OVERWRITE: z.string().optional(),

View File

@@ -8,7 +8,7 @@ import { groupMembers, groups, users } from "@homarr/db/schema";
import { colorSchemeCookieKey, everyoneGroup } from "@homarr/definitions";
import { logger } from "@homarr/log";
import { env } from "./env.mjs";
import { env } from "./env";
import { extractProfileName } from "./providers/oidc/oidc-provider";
export const createSignInEventHandler = (db: Database): Exclude<NextAuthConfig["events"], undefined>["signIn"] => {

View File

@@ -11,7 +11,7 @@
"./client": "./client.ts",
"./server": "./server.ts",
"./shared": "./shared.ts",
"./env.mjs": "./env.mjs"
"./env": "./env.ts"
},
"main": "./index.ts",
"types": "./index.ts",

View File

@@ -1,6 +1,6 @@
import type { SupportedAuthProvider } from "@homarr/definitions";
import { env } from "../env.mjs";
import { env } from "../env";
export const isProviderEnabled = (provider: SupportedAuthProvider) => {
// The question mark is placed there because isProviderEnabled is called during static build of about page

View File

@@ -7,7 +7,7 @@ import { logger } from "@homarr/log";
import type { validation } from "@homarr/validation";
import { z } from "@homarr/validation";
import { env } from "../../../env.mjs";
import { env } from "../../../env";
import { LdapClient } from "../ldap-client";
export const authorizeWithLdapCredentialsAsync = async (

View File

@@ -3,7 +3,7 @@ import { Client } from "ldapts";
import { objectEntries } from "@homarr/common";
import { env } from "../../env.mjs";
import { env } from "../../env";
export interface BindOptions {
distinguishedName: string;

View File

@@ -1,6 +1,6 @@
import type { Provider } from "next-auth/providers";
import { env } from "../env.mjs";
import { env } from "../env";
export const filterProviders = (providers: Exclude<Provider, () => unknown>[]) => {
// During build this will be undefined, so we default to an empty array

View File

@@ -2,7 +2,7 @@ import type { ReadonlyHeaders } from "next/dist/server/web/spec-extension/adapte
import type { OIDCConfig } from "@auth/core/providers";
import type { Profile } from "@auth/core/types";
import { env } from "../../env.mjs";
import { env } from "../../env";
import { createRedirectUri } from "../../redirect";
export const OidcProvider = (headers: ReadonlyHeaders | null): OIDCConfig<Profile> => ({

View File

@@ -9,7 +9,7 @@ import { createDb } from "@homarr/db/test";
import { authorizeWithLdapCredentialsAsync } from "../credentials/authorization/ldap-authorization";
import * as ldapClient from "../credentials/ldap-client";
vi.mock("../../env.mjs", () => ({
vi.mock("../../env", () => ({
env: {
AUTH_LDAP_BIND_DN: "bind_dn",
AUTH_LDAP_BIND_PASSWORD: "bind_password",

View File

@@ -11,7 +11,7 @@ import { colorSchemeCookieKey, everyoneGroup } from "@homarr/definitions";
import { createSignInEventHandler } from "../events";
vi.mock("../env.mjs", () => {
vi.mock("../env", () => {
return {
env: {
AUTH_OIDC_GROUPS_ATTRIBUTE: "someRandomGroupsKey",