Skip to content

Commit

Permalink
feat: refactor plugin types and config parse
Browse files Browse the repository at this point in the history
  • Loading branch information
kuoruan committed Oct 12, 2024
1 parent c8be84a commit f185829
Show file tree
Hide file tree
Showing 4 changed files with 79 additions and 84 deletions.
7 changes: 7 additions & 0 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,13 @@ export default tseslint.config(
},
],
"@typescript-eslint/no-import-type-side-effects": "error",
"@typescript-eslint/no-unused-vars": [
"error",
{
argsIgnorePattern: "^_",
varsIgnorePattern: "^_",
},
],
},
settings: {
"import-x/resolver": {
Expand Down
6 changes: 3 additions & 3 deletions src/server/plugin/AuthCore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,9 +88,9 @@ export class AuthCore {
*/
private initConfiguredGroups(packages: Record<string, PackageAccess> = {}): string[] {
for (const packageConfig of Object.values(packages)) {
const groups = ["access", "publish", "unpublish"]
.flatMap((key) => packageConfig[key] as string[])
.filter(Boolean);
const groups = (["access", "publish", "unpublish"] as const)
.flatMap((key) => packageConfig[key])
.filter(Boolean) as string[];

return [...new Set(groups)];
}
Expand Down
114 changes: 45 additions & 69 deletions src/server/plugin/Config.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,21 @@
import process from "node:process";

import { defaultSecurity } from "@verdaccio/config";
import type {
Config as IncorrectVerdaccioConfig,
PackageAccess as IncorrectVerdaccioPackageAccess,
Security,
} from "@verdaccio/types";
import type { Config, PackageAccess as IncorrectPackageAccess, PackageList, Security } from "@verdaccio/types";
import merge from "deepmerge";
import { mixed, object, Schema, string } from "yup";

import { plugin, pluginKey } from "@/constants";
import { CONFIG_ENV_NAME_REGEX } from "@/server/constants";
import logger from "@/server/logger";

// Verdaccio incorrectly types some of these as string arrays
// although they are all strings.
export type PackageAccess = IncorrectVerdaccioPackageAccess & {
unpublish?: string[];
};

export type VerdaccioConfig = IncorrectVerdaccioConfig & {
packages?: Record<string, PackageAccess>;
security?: Partial<Security>;
};

type ProviderType = "gitlab";

export interface PluginConfig {
export interface PackageAccess extends IncorrectPackageAccess {
unpublish?: string[];
}

export interface OpenIDConfig {
"provider-host": string;
"provider-type"?: ProviderType;
"configuration-uri"?: string;
Expand All @@ -44,13 +33,6 @@ export interface PluginConfig {
"group-users"?: string | Record<string, string[]>;
}

export interface OpenIdConfig {
middlewares: Record<"openid", PluginConfig>;
auth: Record<"openid", PluginConfig>;
}

export type Config = OpenIdConfig & VerdaccioConfig;

export interface ConfigHolder {
providerHost: string;
providerType?: string;
Expand Down Expand Up @@ -107,61 +89,55 @@ function handleValidationError(error: any, key: string) {
process.exit(1);
}

function getOpenIdConfigValue<T>(config: OpenIdConfig, key: keyof PluginConfig, schema: Schema): T {
const valueOrEnvironmentName = config.auth?.[pluginKey]?.[key] ?? config.middlewares?.[pluginKey]?.[key];

/**
* If the value is not defined in the config, use the plugin name and key as the environment variable name.
*
* eg. client-id -> `VERDACCIO_OPENID_CLIENT_ID`
*/
const environmentName: string =
typeof valueOrEnvironmentName === "string" && CONFIG_ENV_NAME_REGEX.test(valueOrEnvironmentName)
? valueOrEnvironmentName
: `${plugin.name}-${key}`.toUpperCase().replaceAll("-", "_");

/**
* Allow environment variables to be used as values.
*/
const value = getEnvironmentValue(environmentName) ?? valueOrEnvironmentName;

try {
schema.validateSync(value);
} catch (error: any) {
handleValidationError(error, key);
}

return value as T;
}

export class ParsedPluginConfig implements ConfigHolder {
constructor(public readonly config: Config) {
for (const node of ["middlewares", "auth"] satisfies (keyof OpenIdConfig)[]) {
const object_ = config[node]?.[pluginKey];
constructor(
private readonly config: OpenIDConfig,
private readonly verdaccioConfig: Config,
) {}

if (!object_) {
throw new Error(`"${node}.${pluginKey}" must be defined in the verdaccio config.`);
}
}
}

public get secret() {
return this.config.secret;
public get secret(): string {
return this.verdaccioConfig.secret;
}

public get security(): Security {
return merge(defaultSecurity, this.config.security || {});
return merge(defaultSecurity, this.verdaccioConfig.security ?? {});
}

public get packages() {
return this.config.packages ?? {};
public get packages(): PackageList {
return this.verdaccioConfig.packages ?? {};
}
public get urlPrefix(): string {
return this.config.url_prefix ?? "";
}
return this.verdaccioConfig.url_prefix ?? "";
}

private getConfigValue<T>(key: keyof OpenIDConfig, schema: Schema): T {
const valueOrEnvironmentName =
this.config[key] ??
this.verdaccioConfig.auth?.[pluginKey]?.[key] ??
this.verdaccioConfig.middlewares?.[pluginKey]?.[key];

/**
* If the value is not defined in the config, use the plugin name and key as the environment variable name.
*
* eg. client-id -> `VERDACCIO_OPENID_CLIENT_ID`
*/
const environmentName: string =
typeof valueOrEnvironmentName === "string" && CONFIG_ENV_NAME_REGEX.test(valueOrEnvironmentName)
? valueOrEnvironmentName
: `${plugin.name}-${key}`.toUpperCase().replaceAll("-", "_");

/**
* Allow environment variables to be used as values.
*/
const value = getEnvironmentValue(environmentName) ?? valueOrEnvironmentName;

try {
schema.validateSync(value);
} catch (error: any) {
handleValidationError(error, key);
}

private getConfigValue<T>(key: keyof PluginConfig, schema: Schema): T {
return getOpenIdConfigValue<T>(this.config, key, schema);
return value as T;
}

public get providerHost() {
Expand Down
36 changes: 24 additions & 12 deletions src/server/plugin/Plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@ import type {
AllowAccess,
AuthAccessCallback,
AuthCallback,
IBasicAuth,
IPluginAuth,
IPluginMiddleware,
Logger,
PluginOptions,
RemoteUser,
} from "@verdaccio/types";
import type { Application } from "express";

import { plugin } from "@/constants";
import { debug } from "@/server/debugger";
import { CliFlow, WebFlow } from "@/server/flows";
import logger, { setLogger } from "@/server/logger";
Expand All @@ -19,7 +21,7 @@ import { registerGlobalProxy } from "@/server/proxy-agent";

import { AuthCore, type User } from "./AuthCore";
import type { AuthProvider } from "./AuthProvider";
import { type Config, type PackageAccess, ParsedPluginConfig } from "./Config";
import { type OpenIDConfig, type PackageAccess, ParsedPluginConfig } from "./Config";
import { PatchHtml } from "./PatchHtml";
import { ServeStatic } from "./ServeStatic";

Expand All @@ -31,24 +33,34 @@ export class Plugin implements IPluginMiddleware<any>, IPluginAuth<any> {
private readonly provider: AuthProvider;
private readonly core: AuthCore;

constructor(config: Config, params: { logger: Logger }) {
setLogger(params.logger);
constructor(config: OpenIDConfig, options: PluginOptions<OpenIDConfig>) {
setLogger(options.logger);

const verdaccioConfig = options.config;

registerGlobalProxy({
http_proxy: config.http_proxy,
https_proxy: config.https_proxy,
no_proxy: config.no_proxy,
http_proxy: verdaccioConfig.http_proxy,
https_proxy: verdaccioConfig.https_proxy,
no_proxy: verdaccioConfig.no_proxy,
});

this.config = new ParsedPluginConfig(config);
this.provider = new OpenIDConnectAuthProvider(this.config);
this.core = new AuthCore(this.config, this.provider);
const parsedConfig = new ParsedPluginConfig(config, verdaccioConfig);
const provider = new OpenIDConnectAuthProvider(parsedConfig);
const core = new AuthCore(parsedConfig, provider);

this.config = parsedConfig;
this.provider = provider;
this.core = core;
}

public get version(): string {
return plugin.version;
}

/**
* IPluginMiddleware
*/
register_middlewares(app: Application, auth) {
register_middlewares(app: Application, auth: IBasicAuth<any>, _storage) {
this.core.setAuth(auth as Auth);

const children = [
Expand Down Expand Up @@ -119,7 +131,7 @@ export class Plugin implements IPluginMiddleware<any>, IPluginAuth<any> {

const grant = this.checkPackageAccess(user, config.access);
if (!grant) {
logger.info(
logger.debug(
{ username: user.name, package: config.name },
`user "@{username}" is not allowed to access "@{package}"`,
);
Expand Down

0 comments on commit f185829

Please sign in to comment.