Compare commits

..

1 Commits

Author SHA1 Message Date
Mauricio Siu
81adbcb8f9 fix: allow members with git providers permission to create and delete their own
The canAccessToGitProviders legacy override only granted read access, so
members with the Git Providers toggle enabled could not add providers — the
create/delete endpoints require gitProviders.create / gitProviders.delete.
This mirrors how the SSH Keys toggle already grants read/create/delete.

The git-provider remove endpoint now restricts non owner/admin roles to
deleting only their own providers (matching the ownership model used for
visibility and sharing), while owner/admin can still delete any provider in
the organization.

Closes #4695
2026-06-30 15:56:13 -06:00
6 changed files with 13 additions and 90 deletions

View File

@@ -1,75 +0,0 @@
import { apiCreateRegistry, apiTestRegistry } from "@dokploy/server/db/schema";
import { describe, expect, it } from "vitest";
describe("Registry Schema - Username case preservation (#4632)", () => {
const validBase = {
registryName: "AWS ECR",
password: "dXNlcm5hbWU6cGFzc3dvcmQ=", // dummy base64 token
registryUrl: "123456789.dkr.ecr.us-east-1.amazonaws.com",
registryType: "cloud" as const,
imagePrefix: null,
};
it("should preserve uppercase username (AWS ECR requires 'AWS')", () => {
const result = apiCreateRegistry.safeParse({
...validBase,
username: "AWS",
});
expect(result.success).toBe(true);
if (result.success) {
expect(result.data.username).toBe("AWS");
}
});
it("should not lowercase mixed-case usernames", () => {
const result = apiCreateRegistry.safeParse({
...validBase,
username: "MyUser",
});
expect(result.success).toBe(true);
if (result.success) {
expect(result.data.username).toBe("MyUser");
}
});
it("should still trim whitespace from username", () => {
const result = apiCreateRegistry.safeParse({
...validBase,
username: " AWS ",
});
expect(result.success).toBe(true);
if (result.success) {
expect(result.data.username).toBe("AWS");
}
});
it("should reject empty username", () => {
const result = apiCreateRegistry.safeParse({
...validBase,
username: "",
});
expect(result.success).toBe(false);
});
it("should also preserve case in apiTestRegistry", () => {
const result = apiTestRegistry.safeParse({
...validBase,
username: "AWS",
});
expect(result.success).toBe(true);
if (result.success) {
expect(result.data.username).toBe("AWS");
}
});
it("should accept lowercase usernames too (backward compat)", () => {
const result = apiCreateRegistry.safeParse({
...validBase,
username: "myuser",
});
expect(result.success).toBe(true);
if (result.success) {
expect(result.data.username).toBe("myuser");
}
});
});

View File

@@ -35,11 +35,11 @@ export const OnboardingLayout = ({ children }: Props) => {
</blockquote>
</div>
</div>
<div className="flex min-h-svh w-full flex-col">
<div className="flex w-full flex-1 flex-col justify-center space-y-6 max-w-lg mx-auto py-8">
<div className="w-full">
<div className="flex w-full flex-col justify-center space-y-6 max-w-lg mx-auto">
{children}
</div>
<div className="mx-auto flex w-full max-w-lg items-center justify-center gap-1 pb-6 text-muted-foreground sm:justify-end">
<div className="flex items-center gap-4 justify-center absolute bottom-4 right-4 text-muted-foreground">
<Button variant="ghost" size="icon">
<Link href="https://github.com/dokploy/dokploy">
<GithubIcon />

View File

@@ -160,7 +160,7 @@ const Register = ({ isCloud }: Props) => {
)}
<CardContent className="p-0">
{isCloud && (
<div className="flex flex-col gap-2">
<div className="flex flex-col">
<SignInWithGithub />
<SignInWithGoogle />
</div>

View File

@@ -44,11 +44,12 @@ export const registryRelations = relations(registry, ({ many }) => ({
}),
}));
// Registry usernames should NOT be lowercased.
// Some registries (e.g. AWS ECR) require a specific case: the username must be
// exactly "AWS" (uppercase) for ECR authentication. Docker Hub usernames are
// case-insensitive for login, so preserving case is safe for all providers.
const registryUsernameSchema = z.string().trim().min(1);
// Image references require a lowercase namespace (e.g. Docker Hub username).
const registryUsernameSchema = z
.string()
.trim()
.min(1)
.transform((s) => s.toLowerCase());
// Registry URLs must be hostname[:port] only — no shell metacharacters
// Empty string is allowed (means default/Docker Hub registry)

View File

@@ -1,3 +1,3 @@
// Valid git branch names per git-check-ref-format rules.
// Rejects shell metacharacters that would enable command injection.
export const VALID_BRANCH_REGEX = /^[a-zA-Z0-9._\-/#]+$/;
export const VALID_BRANCH_REGEX = /^[a-zA-Z0-9._\-/]+$/;

View File

@@ -73,7 +73,7 @@ export const cloneGitRepository = async ({
if (customGitSSHKeyId) {
const sshKey = await findSSHKeyById(customGitSSHKeyId);
const { port } = sanitizeRepoPathSSH(customGitUrl);
const gitSshCommand = `ssh -i /tmp/id_rsa${port ? ` -p ${port}` : ""} -o UserKnownHostsFile=${knownHostsPath} -o StrictHostKeyChecking=accept-new`;
const gitSshCommand = `ssh -i /tmp/id_rsa${port ? ` -p ${port}` : ""} -o UserKnownHostsFile=${knownHostsPath}`;
command += `echo "${sshKey.privateKey}" > /tmp/id_rsa;`;
command += "chmod 600 /tmp/id_rsa;";
command += `export GIT_SSH_COMMAND="${gitSshCommand}";`;
@@ -111,10 +111,7 @@ const addHostToKnownHostsCommand = (repositoryURL: string) => {
const { domain, port } = sanitizeRepoPathSSH(repositoryURL);
const knownHostsPath = path.join(SSH_PATH, "known_hosts");
// ssh-keyscan is best-effort: some Git hosts (e.g. Hugging Face) never answer
// it, and its exit code must not abort the clone under `set -e`. The clone's
// own host-key check (StrictHostKeyChecking=accept-new) is the real boundary.
return `ssh-keyscan -p ${port} ${domain} >> ${knownHostsPath} || true;`;
return `ssh-keyscan -p ${port} ${domain} >> ${knownHostsPath};`;
};
const sanitizeRepoPathSSH = (input: string) => {
const SSH_PATH_RE = new RegExp(