mirror of
https://github.com/Dokploy/dokploy.git
synced 2026-07-16 19:35:25 +02:00
- Replaced `forwardAuthProviderId` with `forwardAuthEnabled` in the domain schema to simplify the configuration of forward authentication. - Updated related tests to reflect this change, ensuring consistency across the application. - Introduced a new SQL migration to create the `forward_auth_settings` table for managing authentication domains and their configurations. This refactor enhances the clarity and maintainability of the forward authentication logic within the application.
528 lines
12 KiB
TypeScript
528 lines
12 KiB
TypeScript
import type { ApplicationNested, Domain, Redirect } from "@dokploy/server";
|
|
import { createRouterConfig } from "@dokploy/server";
|
|
import { expect, test } from "vitest";
|
|
|
|
const baseApp: ApplicationNested = {
|
|
railpackVersion: "0.15.4",
|
|
rollbackActive: false,
|
|
applicationId: "",
|
|
previewLabels: [],
|
|
createEnvFile: true,
|
|
bitbucketRepositorySlug: "",
|
|
herokuVersion: "",
|
|
giteaRepository: "",
|
|
giteaOwner: "",
|
|
giteaBranch: "",
|
|
buildServerId: "",
|
|
buildRegistryId: "",
|
|
buildRegistry: null,
|
|
giteaBuildPath: "",
|
|
giteaId: "",
|
|
args: [],
|
|
rollbackRegistryId: "",
|
|
rollbackRegistry: null,
|
|
deployments: [],
|
|
cleanCache: false,
|
|
applicationStatus: "done",
|
|
endpointSpecSwarm: null,
|
|
appName: "",
|
|
autoDeploy: true,
|
|
enableSubmodules: false,
|
|
previewRequireCollaboratorPermissions: false,
|
|
serverId: "",
|
|
branch: null,
|
|
dockerBuildStage: "",
|
|
registryUrl: "",
|
|
watchPaths: [],
|
|
buildArgs: null,
|
|
buildSecrets: null,
|
|
isPreviewDeploymentsActive: false,
|
|
previewBuildArgs: null,
|
|
previewBuildSecrets: null,
|
|
triggerType: "push",
|
|
previewCertificateType: "none",
|
|
previewEnv: null,
|
|
previewHttps: false,
|
|
previewPath: "/",
|
|
previewPort: 3000,
|
|
previewLimit: 0,
|
|
previewCustomCertResolver: null,
|
|
previewWildcard: "",
|
|
environmentId: "",
|
|
environment: {
|
|
env: "",
|
|
isDefault: false,
|
|
environmentId: "",
|
|
name: "",
|
|
createdAt: "",
|
|
description: "",
|
|
projectId: "",
|
|
project: {
|
|
env: "",
|
|
organizationId: "",
|
|
name: "",
|
|
description: "",
|
|
createdAt: "",
|
|
projectId: "",
|
|
},
|
|
},
|
|
buildPath: "/",
|
|
gitlabPathNamespace: "",
|
|
buildType: "nixpacks",
|
|
bitbucketBranch: "",
|
|
bitbucketBuildPath: "",
|
|
bitbucketId: "",
|
|
bitbucketRepository: "",
|
|
bitbucketOwner: "",
|
|
githubId: "",
|
|
gitlabProjectId: 0,
|
|
gitlabBranch: "",
|
|
gitlabBuildPath: "",
|
|
gitlabId: "",
|
|
gitlabRepository: "",
|
|
gitlabOwner: "",
|
|
command: null,
|
|
cpuLimit: null,
|
|
cpuReservation: null,
|
|
createdAt: "",
|
|
customGitBranch: "",
|
|
customGitBuildPath: "",
|
|
customGitSSHKeyId: null,
|
|
customGitUrl: "",
|
|
description: "",
|
|
dockerfile: null,
|
|
dockerImage: null,
|
|
dropBuildPath: null,
|
|
enabled: null,
|
|
env: null,
|
|
icon: null,
|
|
healthCheckSwarm: null,
|
|
labelsSwarm: null,
|
|
memoryLimit: null,
|
|
memoryReservation: null,
|
|
modeSwarm: null,
|
|
mounts: [],
|
|
name: "",
|
|
networkSwarm: null,
|
|
owner: null,
|
|
password: null,
|
|
placementSwarm: null,
|
|
ports: [],
|
|
publishDirectory: null,
|
|
isStaticSpa: null,
|
|
redirects: [],
|
|
refreshToken: "",
|
|
registry: null,
|
|
registryId: null,
|
|
replicas: 1,
|
|
repository: null,
|
|
restartPolicySwarm: null,
|
|
rollbackConfigSwarm: null,
|
|
security: [],
|
|
sourceType: "git",
|
|
subtitle: null,
|
|
title: null,
|
|
updateConfigSwarm: null,
|
|
username: null,
|
|
dockerContextPath: null,
|
|
stopGracePeriodSwarm: null,
|
|
ulimitsSwarm: null,
|
|
};
|
|
|
|
const baseDomain: Domain = {
|
|
applicationId: "",
|
|
certificateType: "none",
|
|
createdAt: "",
|
|
domainId: "",
|
|
host: "",
|
|
https: false,
|
|
path: null,
|
|
port: null,
|
|
customEntrypoint: null,
|
|
serviceName: "",
|
|
composeId: "",
|
|
customCertResolver: null,
|
|
domainType: "application",
|
|
uniqueConfigKey: 1,
|
|
previewDeploymentId: "",
|
|
internalPath: "/",
|
|
stripPath: false,
|
|
middlewares: null,
|
|
forwardAuthEnabled: false,
|
|
};
|
|
|
|
const baseRedirect: Redirect = {
|
|
redirectId: "",
|
|
regex: "",
|
|
replacement: "",
|
|
permanent: false,
|
|
uniqueConfigKey: 1,
|
|
createdAt: "",
|
|
applicationId: "",
|
|
};
|
|
|
|
/** Middlewares */
|
|
|
|
test("Web entrypoint on http domain", async () => {
|
|
const router = await createRouterConfig(
|
|
baseApp,
|
|
{ ...baseDomain, https: false },
|
|
"web",
|
|
);
|
|
|
|
expect(router.middlewares).not.toContain("redirect-to-https");
|
|
expect(router.rule).not.toContain("PathPrefix");
|
|
});
|
|
|
|
test("Web entrypoint on http domain with custom path", async () => {
|
|
const router = await createRouterConfig(
|
|
baseApp,
|
|
{ ...baseDomain, path: "/foo", https: false },
|
|
"web",
|
|
);
|
|
|
|
expect(router.rule).toContain("PathPrefix(`/foo`)");
|
|
});
|
|
|
|
test("Web entrypoint on http domain with redirect", async () => {
|
|
const router = await createRouterConfig(
|
|
{
|
|
...baseApp,
|
|
appName: "test",
|
|
redirects: [{ ...baseRedirect, uniqueConfigKey: 1 }],
|
|
},
|
|
{ ...baseDomain, https: false },
|
|
"web",
|
|
);
|
|
|
|
expect(router.middlewares).not.toContain("redirect-to-https");
|
|
expect(router.middlewares).toContain("redirect-test-1");
|
|
});
|
|
|
|
test("Web entrypoint on http domain with multiple redirect", async () => {
|
|
const router = await createRouterConfig(
|
|
{
|
|
...baseApp,
|
|
appName: "test",
|
|
redirects: [
|
|
{ ...baseRedirect, uniqueConfigKey: 1 },
|
|
{ ...baseRedirect, uniqueConfigKey: 2 },
|
|
],
|
|
},
|
|
{ ...baseDomain, https: false },
|
|
"web",
|
|
);
|
|
|
|
expect(router.middlewares).not.toContain("redirect-to-https");
|
|
expect(router.middlewares).toContain("redirect-test-1");
|
|
expect(router.middlewares).toContain("redirect-test-2");
|
|
});
|
|
|
|
test("Web entrypoint on https domain", async () => {
|
|
const router = await createRouterConfig(
|
|
baseApp,
|
|
{ ...baseDomain, https: true },
|
|
"web",
|
|
);
|
|
|
|
expect(router.middlewares).toContain("redirect-to-https");
|
|
});
|
|
|
|
test("Web entrypoint on https domain with redirect", async () => {
|
|
const router = await createRouterConfig(
|
|
{
|
|
...baseApp,
|
|
appName: "test",
|
|
redirects: [{ ...baseRedirect, uniqueConfigKey: 1 }],
|
|
},
|
|
{ ...baseDomain, https: true },
|
|
"web",
|
|
);
|
|
|
|
expect(router.middlewares).toContain("redirect-to-https");
|
|
expect(router.middlewares).not.toContain("redirect-test-1");
|
|
});
|
|
|
|
test("Websecure entrypoint on https domain", async () => {
|
|
const router = await createRouterConfig(
|
|
baseApp,
|
|
{ ...baseDomain, https: true },
|
|
"websecure",
|
|
);
|
|
|
|
expect(router.middlewares).not.toContain("redirect-to-https");
|
|
});
|
|
|
|
test("Websecure entrypoint on https domain with redirect", async () => {
|
|
const router = await createRouterConfig(
|
|
{
|
|
...baseApp,
|
|
appName: "test",
|
|
redirects: [{ ...baseRedirect, uniqueConfigKey: 1 }],
|
|
},
|
|
{ ...baseDomain, https: true },
|
|
"websecure",
|
|
);
|
|
|
|
expect(router.middlewares).not.toContain("redirect-to-https");
|
|
expect(router.middlewares).toContain("redirect-test-1");
|
|
});
|
|
|
|
/** Custom Middlewares */
|
|
|
|
test("Web entrypoint with single custom middleware", async () => {
|
|
const router = await createRouterConfig(
|
|
baseApp,
|
|
{ ...baseDomain, middlewares: ["auth@file"] },
|
|
"web",
|
|
);
|
|
|
|
expect(router.middlewares).toContain("auth@file");
|
|
});
|
|
|
|
test("Web entrypoint with multiple custom middlewares", async () => {
|
|
const router = await createRouterConfig(
|
|
baseApp,
|
|
{ ...baseDomain, middlewares: ["auth@file", "rate-limit@file"] },
|
|
"web",
|
|
);
|
|
|
|
expect(router.middlewares).toContain("auth@file");
|
|
expect(router.middlewares).toContain("rate-limit@file");
|
|
});
|
|
|
|
test("Web entrypoint on https domain with custom middleware", async () => {
|
|
const router = await createRouterConfig(
|
|
baseApp,
|
|
{ ...baseDomain, https: true, middlewares: ["auth@file"] },
|
|
"web",
|
|
);
|
|
|
|
// Should only have HTTPS redirect - custom middleware applies on websecure
|
|
expect(router.middlewares).toContain("redirect-to-https");
|
|
expect(router.middlewares).not.toContain("auth@file");
|
|
});
|
|
|
|
test("Websecure entrypoint with custom middleware", async () => {
|
|
const router = await createRouterConfig(
|
|
baseApp,
|
|
{ ...baseDomain, https: true, middlewares: ["auth@file"] },
|
|
"websecure",
|
|
);
|
|
|
|
// Should have custom middleware but not HTTPS redirect
|
|
expect(router.middlewares).not.toContain("redirect-to-https");
|
|
expect(router.middlewares).toContain("auth@file");
|
|
});
|
|
|
|
test("Web entrypoint with redirect and custom middleware", async () => {
|
|
const router = await createRouterConfig(
|
|
{
|
|
...baseApp,
|
|
appName: "test",
|
|
redirects: [{ ...baseRedirect, uniqueConfigKey: 1 }],
|
|
},
|
|
{ ...baseDomain, middlewares: ["auth@file"] },
|
|
"web",
|
|
);
|
|
|
|
// Should have both redirect middleware and custom middleware
|
|
expect(router.middlewares).toContain("redirect-test-1");
|
|
expect(router.middlewares).toContain("auth@file");
|
|
});
|
|
|
|
test("Web entrypoint with empty middlewares array", async () => {
|
|
const router = await createRouterConfig(
|
|
baseApp,
|
|
{ ...baseDomain, https: false, middlewares: [] },
|
|
"web",
|
|
);
|
|
|
|
// Should behave same as no middlewares - no redirect for http
|
|
expect(router.middlewares).not.toContain("redirect-to-https");
|
|
});
|
|
|
|
/** Certificates */
|
|
|
|
test("CertificateType on websecure entrypoint", async () => {
|
|
const router = await createRouterConfig(
|
|
baseApp,
|
|
{ ...baseDomain, certificateType: "letsencrypt" },
|
|
"websecure",
|
|
);
|
|
|
|
expect(router.tls?.certResolver).toBe("letsencrypt");
|
|
});
|
|
|
|
test("Custom entrypoint on http domain", async () => {
|
|
const router = await createRouterConfig(
|
|
baseApp,
|
|
{ ...baseDomain, https: false, customEntrypoint: "custom" },
|
|
"custom",
|
|
);
|
|
|
|
expect(router.entryPoints).toEqual(["custom"]);
|
|
expect(router.middlewares).not.toContain("redirect-to-https");
|
|
expect(router.tls).toBeUndefined();
|
|
});
|
|
|
|
test("Custom entrypoint on https domain", async () => {
|
|
const router = await createRouterConfig(
|
|
baseApp,
|
|
{
|
|
...baseDomain,
|
|
https: true,
|
|
customEntrypoint: "custom",
|
|
certificateType: "letsencrypt",
|
|
},
|
|
"custom",
|
|
);
|
|
|
|
expect(router.entryPoints).toEqual(["custom"]);
|
|
expect(router.middlewares).not.toContain("redirect-to-https");
|
|
expect(router.tls?.certResolver).toBe("letsencrypt");
|
|
});
|
|
|
|
test("Custom entrypoint with path includes PathPrefix in rule", async () => {
|
|
const router = await createRouterConfig(
|
|
baseApp,
|
|
{ ...baseDomain, customEntrypoint: "custom", path: "/api" },
|
|
"custom",
|
|
);
|
|
|
|
expect(router.rule).toContain("PathPrefix(`/api`)");
|
|
expect(router.entryPoints).toEqual(["custom"]);
|
|
});
|
|
|
|
test("Custom entrypoint with stripPath adds stripprefix middleware", async () => {
|
|
const router = await createRouterConfig(
|
|
baseApp,
|
|
{
|
|
...baseDomain,
|
|
customEntrypoint: "custom",
|
|
path: "/api",
|
|
stripPath: true,
|
|
},
|
|
"custom",
|
|
);
|
|
|
|
expect(router.middlewares).toContain("stripprefix--1");
|
|
expect(router.entryPoints).toEqual(["custom"]);
|
|
});
|
|
|
|
test("Custom entrypoint with internalPath adds addprefix middleware", async () => {
|
|
const router = await createRouterConfig(
|
|
baseApp,
|
|
{
|
|
...baseDomain,
|
|
customEntrypoint: "custom",
|
|
internalPath: "/hello",
|
|
},
|
|
"custom",
|
|
);
|
|
|
|
expect(router.middlewares).toContain("addprefix--1");
|
|
expect(router.entryPoints).toEqual(["custom"]);
|
|
});
|
|
|
|
test("stripPath and internalPath together: stripprefix must come before addprefix", async () => {
|
|
const router = await createRouterConfig(
|
|
baseApp,
|
|
{
|
|
...baseDomain,
|
|
path: "/public",
|
|
stripPath: true,
|
|
internalPath: "/app/v2",
|
|
},
|
|
"web",
|
|
);
|
|
|
|
const stripIndex = router.middlewares?.indexOf("stripprefix--1") ?? -1;
|
|
const addIndex = router.middlewares?.indexOf("addprefix--1") ?? -1;
|
|
|
|
expect(stripIndex).toBeGreaterThanOrEqual(0);
|
|
expect(addIndex).toBeGreaterThanOrEqual(0);
|
|
expect(stripIndex).toBeLessThan(addIndex);
|
|
});
|
|
|
|
test("Custom entrypoint with https and custom cert resolver", async () => {
|
|
const router = await createRouterConfig(
|
|
baseApp,
|
|
{
|
|
...baseDomain,
|
|
https: true,
|
|
customEntrypoint: "custom",
|
|
certificateType: "custom",
|
|
customCertResolver: "myresolver",
|
|
},
|
|
"custom",
|
|
);
|
|
|
|
expect(router.entryPoints).toEqual(["custom"]);
|
|
expect(router.tls?.certResolver).toBe("myresolver");
|
|
});
|
|
|
|
test("Custom entrypoint without https should not have tls", async () => {
|
|
const router = await createRouterConfig(
|
|
baseApp,
|
|
{
|
|
...baseDomain,
|
|
https: false,
|
|
customEntrypoint: "custom",
|
|
certificateType: "letsencrypt",
|
|
},
|
|
"custom",
|
|
);
|
|
|
|
expect(router.entryPoints).toEqual(["custom"]);
|
|
expect(router.tls).toBeUndefined();
|
|
});
|
|
|
|
/** IDN/Punycode */
|
|
|
|
test("Internationalized domain name is converted to punycode", async () => {
|
|
const router = await createRouterConfig(
|
|
baseApp,
|
|
{ ...baseDomain, host: "тест.рф" },
|
|
"web",
|
|
);
|
|
|
|
// тест.рф in punycode is xn--e1aybc.xn--p1ai
|
|
expect(router.rule).toContain("Host(`xn--e1aybc.xn--p1ai`)");
|
|
expect(router.rule).not.toContain("тест.рф");
|
|
});
|
|
|
|
test("ASCII domain remains unchanged", async () => {
|
|
const router = await createRouterConfig(
|
|
baseApp,
|
|
{ ...baseDomain, host: "example.com" },
|
|
"web",
|
|
);
|
|
|
|
expect(router.rule).toContain("Host(`example.com`)");
|
|
});
|
|
|
|
test("Russian Cyrillic label with .ru TLD is converted to punycode", async () => {
|
|
const router = await createRouterConfig(
|
|
baseApp,
|
|
{ ...baseDomain, host: "сайт.ru" },
|
|
"web",
|
|
);
|
|
|
|
// сайт in punycode is xn--80aswg
|
|
expect(router.rule).toContain("Host(`xn--80aswg.ru`)");
|
|
expect(router.rule).not.toContain("сайт");
|
|
});
|
|
|
|
test("Subdomain with Russian IDN TLD converts non-ASCII part to punycode", async () => {
|
|
const router = await createRouterConfig(
|
|
baseApp,
|
|
{ ...baseDomain, host: "app.тест.рф" },
|
|
"web",
|
|
);
|
|
|
|
// app stays ASCII, тест.рф becomes xn--e1aybc.xn--p1ai
|
|
expect(router.rule).toContain("Host(`app.xn--e1aybc.xn--p1ai`)");
|
|
expect(router.rule).not.toContain("тест.рф");
|
|
});
|