Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions src/components/environment/git-sync-section.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -83,21 +83,21 @@ export function GitSyncSection({
}

function handleTest() {
const testToken = token || undefined;
if (!repoUrl) {
toast.error("Enter a repository URL first");
return;
}
if (!testToken && !hasGitToken) {
if (!token && !hasGitToken) {
toast.error("Enter an access token first");
return;
}
if (testToken) {
setIsTesting(true);
testMutation.mutate({ environmentId, repoUrl, branch, token: testToken });
} else {
toast.warning("Enter a new token to test the connection");
}
setIsTesting(true);
testMutation.mutate({
environmentId,
repoUrl,
branch,
...(token ? { token } : {}),
});
}

function handleDisconnect() {
Expand Down
19 changes: 16 additions & 3 deletions src/server/routers/environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { router, protectedProcedure, withTeamAccess, requireSuperAdmin } from "@
import { prisma } from "@/lib/prisma";
import { withAudit } from "@/server/middleware/audit";
import { generateEnrollmentToken } from "@/server/services/agent-token";
import { encrypt } from "@/server/services/crypto";
import { encrypt, decrypt } from "@/server/services/crypto";

export const environmentRouter = router({
list: protectedProcedure
Expand Down Expand Up @@ -145,11 +145,24 @@ export const environmentRouter = router({
environmentId: z.string(),
repoUrl: z.string().url(),
branch: z.string().min(1).max(100).regex(/^[a-zA-Z0-9._\/-]+$/),
token: z.string().min(1),
token: z.string().min(1).optional(),
}))
.use(withTeamAccess("EDITOR"))
.use(withAudit("environment.gitConnection.tested", "Environment"))
.mutation(async ({ input }) => {
// Resolve token: use provided token, or fall back to stored encrypted token
let resolvedToken = input.token;
if (!resolvedToken) {
const env = await prisma.environment.findUnique({
where: { id: input.environmentId },
select: { gitToken: true },
});
if (!env?.gitToken) {
return { success: false, error: "No access token configured" };
}
resolvedToken = decrypt(env.gitToken);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

decrypt() call is outside the try/catch block

If the stored gitToken value is corrupted, truncated, or was encrypted with a different NEXTAUTH_SECRET, decrypt() will throw an Error. Since this call sits outside the try/catch block that wraps the git clone, the exception propagates as a raw tRPC INTERNAL_SERVER_ERROR rather than the { success: false, error: "..." } shape that the rest of this procedure returns. The client's onSuccess handler is never reached, so the user sees a generic "Connection test failed" toast with no useful description.

Wrap the decryption in a try/catch to keep the error path consistent:

Suggested change
resolvedToken = decrypt(env.gitToken);
resolvedToken = (() => {
try {
return decrypt(env.gitToken);
} catch {
return null;
}
})();
if (!resolvedToken) {
return { success: false, error: "Stored token could not be decrypted. Please re-save your token." };
}
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/server/routers/environment.ts
Line: 163

Comment:
**`decrypt()` call is outside the try/catch block**

If the stored `gitToken` value is corrupted, truncated, or was encrypted with a different `NEXTAUTH_SECRET`, `decrypt()` will throw an `Error`. Since this call sits outside the `try/catch` block that wraps the git clone, the exception propagates as a raw tRPC `INTERNAL_SERVER_ERROR` rather than the `{ success: false, error: "..." }` shape that the rest of this procedure returns. The client's `onSuccess` handler is never reached, so the user sees a generic "Connection test failed" toast with no useful description.

Wrap the decryption in a try/catch to keep the error path consistent:

```suggestion
        resolvedToken = (() => {
          try {
            return decrypt(env.gitToken);
          } catch {
            return null;
          }
        })();
        if (!resolvedToken) {
          return { success: false, error: "Stored token could not be decrypted. Please re-save your token." };
        }
```

How can I resolve this? If you propose a fix, please make it concise.

}

const parsedUrl = new URL(input.repoUrl);
if (parsedUrl.protocol !== "https:") {
return { success: false, error: "Only HTTPS repository URLs are supported" };
Expand All @@ -165,7 +178,7 @@ export const environmentRouter = router({
workdir = await mkdtemp(join(tmpdir(), "vf-git-test-"));
const repoDir = join(workdir, "repo");
const git = simpleGit(workdir);
parsedUrl.username = input.token;
parsedUrl.username = resolvedToken;
parsedUrl.password = "";
await git.clone(parsedUrl.toString(), repoDir, [
"--branch", input.branch,
Expand Down
Loading