Merge pull request #3632 from Dokploy/1591-gitea-git-provider-fails-after-the-authorize-application-stage-self-hosted-on-private-network

feat(gitea): add optional internal URL for Gitea integration
This commit is contained in:
Mauricio Siu
2026-02-07 13:01:37 -06:00
committed by GitHub
10 changed files with 7328 additions and 2 deletions

View File

@@ -21,6 +21,7 @@ import {
FormControl,
FormField,
FormItem,
FormDescription,
FormLabel,
FormMessage,
} from "@/components/ui/form";
@@ -39,6 +40,10 @@ const Schema = z.object({
giteaUrl: z.string().min(1, {
message: "Gitea URL is required",
}),
giteaInternalUrl: z
.union([z.string().url(), z.literal("")])
.optional()
.transform((v) => (v === "" ? undefined : v)),
clientId: z.string().min(1, {
message: "Client ID is required",
}),
@@ -70,6 +75,7 @@ export const AddGiteaProvider = () => {
redirectUri: webhookUrl,
name: "",
giteaUrl: "https://gitea.com",
giteaInternalUrl: "",
},
resolver: zodResolver(Schema),
});
@@ -83,6 +89,7 @@ export const AddGiteaProvider = () => {
redirectUri: webhookUrl,
name: "",
giteaUrl: "https://gitea.com",
giteaInternalUrl: "",
});
}, [form, webhookUrl, isOpen]);
@@ -95,6 +102,7 @@ export const AddGiteaProvider = () => {
name: data.name,
redirectUri: data.redirectUri,
giteaUrl: data.giteaUrl,
giteaInternalUrl: data.giteaInternalUrl || undefined,
organizationName: data.organizationName,
})) as unknown as GiteaProviderResponse;
@@ -223,6 +231,29 @@ export const AddGiteaProvider = () => {
)}
/>
<FormField
control={form.control}
name="giteaInternalUrl"
render={({ field }) => (
<FormItem>
<FormLabel>Internal URL (Optional)</FormLabel>
<FormControl>
<Input
placeholder="http://gitea:3000"
{...field}
value={field.value ?? ""}
/>
</FormControl>
<FormDescription>
Use when Gitea runs on the same instance as Dokploy.
Used for OAuth token exchange to reach Gitea via
internal network (e.g. Docker service name).
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="redirectUri"

View File

@@ -19,6 +19,7 @@ import {
FormControl,
FormField,
FormItem,
FormDescription,
FormLabel,
FormMessage,
} from "@/components/ui/form";
@@ -30,6 +31,10 @@ import { useUrl } from "@/utils/hooks/use-url";
const formSchema = z.object({
name: z.string().min(1, "Name is required"),
giteaUrl: z.string().min(1, "Gitea URL is required"),
giteaInternalUrl: z
.union([z.string().url(), z.literal("")])
.optional()
.transform((v) => (v === "" ? undefined : v)),
clientId: z.string().min(1, "Client ID is required"),
clientSecret: z.string().min(1, "Client Secret is required"),
});
@@ -94,6 +99,7 @@ export const EditGiteaProvider = ({ giteaId }: Props) => {
defaultValues: {
name: "",
giteaUrl: "https://gitea.com",
giteaInternalUrl: "",
clientId: "",
clientSecret: "",
},
@@ -104,6 +110,7 @@ export const EditGiteaProvider = ({ giteaId }: Props) => {
form.reset({
name: gitea.gitProvider?.name || "",
giteaUrl: gitea.giteaUrl || "https://gitea.com",
giteaInternalUrl: gitea.giteaInternalUrl || "",
clientId: gitea.clientId || "",
clientSecret: gitea.clientSecret || "",
});
@@ -116,6 +123,7 @@ export const EditGiteaProvider = ({ giteaId }: Props) => {
gitProviderId: gitea?.gitProvider?.gitProviderId || "",
name: values.name,
giteaUrl: values.giteaUrl,
giteaInternalUrl: values.giteaInternalUrl ?? null,
clientId: values.clientId,
clientSecret: values.clientSecret,
})
@@ -224,6 +232,28 @@ export const EditGiteaProvider = ({ giteaId }: Props) => {
</FormItem>
)}
/>
<FormField
control={form.control}
name="giteaInternalUrl"
render={({ field }) => (
<FormItem>
<FormLabel>Internal URL (Optional)</FormLabel>
<FormControl>
<Input
placeholder="http://gitea:3000"
{...field}
value={field.value ?? ""}
/>
</FormControl>
<FormDescription>
Use when Gitea runs on the same instance as Dokploy. Used
for OAuth token exchange to reach Gitea via internal network
(e.g. Docker service name).
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="clientId"

View File

@@ -0,0 +1 @@
ALTER TABLE "gitea" ADD COLUMN "giteaInternalUrl" text;

File diff suppressed because it is too large Load Diff

View File

@@ -988,6 +988,13 @@
"when": 1770489900075,
"tag": "0140_lame_mattie_franklin",
"breakpoints": true
},
{
"idx": 141,
"version": "7",
"when": 1770490719123,
"tag": "0141_plain_earthquake",
"breakpoints": true
}
]
}

View File

@@ -15,7 +15,9 @@ const parseState = (state: string): string | null => {
// Helper to fetch access token from Gitea
const fetchAccessToken = async (gitea: Gitea, code: string) => {
const response = await fetch(`${gitea.giteaUrl}/login/oauth/access_token`, {
// Use internal URL for token exchange when Gitea is on same instance as Dokploy
const baseUrl = gitea.giteaInternalUrl || gitea.giteaUrl;
const response = await fetch(`${baseUrl}/login/oauth/access_token`, {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",

View File

@@ -9,6 +9,7 @@ export interface Gitea {
refreshToken: string | null;
expiresAt: number | null;
giteaUrl: string;
giteaInternalUrl: string | null;
clientId: string | null;
clientSecret: string | null;
organizationName?: string;

View File

@@ -11,6 +11,7 @@ export const gitea = pgTable("gitea", {
.primaryKey()
.$defaultFn(() => nanoid()),
giteaUrl: text("giteaUrl").default("https://gitea.com").notNull(),
giteaInternalUrl: text("giteaInternalUrl"),
redirectUri: text("redirect_uri"),
clientId: text("client_id"),
clientSecret: text("client_secret"),
@@ -40,6 +41,7 @@ export const apiCreateGitea = createSchema.extend({
redirectUri: z.string().optional(),
name: z.string().min(1),
giteaUrl: z.string().min(1),
giteaInternalUrl: z.string().optional().nullable(),
giteaUsername: z.string().optional(),
accessToken: z.string().optional(),
refreshToken: z.string().optional(),
@@ -76,6 +78,7 @@ export const apiUpdateGitea = createSchema.extend({
name: z.string().min(1),
giteaId: z.string().min(1),
giteaUrl: z.string().min(1),
giteaInternalUrl: z.string().optional().nullable(),
giteaUsername: z.string().optional(),
accessToken: z.string().optional(),
refreshToken: z.string().optional(),

View File

@@ -471,6 +471,7 @@ table git_provider {
table gitea {
giteaId text [pk, not null]
giteaUrl text [not null, default: 'https://gitea.com']
giteaInternalUrl text
redirect_uri text
client_id text
client_secret text

View File

@@ -49,7 +49,9 @@ export const refreshGiteaToken = async (giteaProviderId: string) => {
}
// Token is expired or about to expire, refresh it
const tokenEndpoint = `${giteaProvider.giteaUrl}/login/oauth/access_token`;
// Use internal URL when Gitea is on same instance as Dokploy
const baseUrl = giteaProvider.giteaInternalUrl || giteaProvider.giteaUrl;
const tokenEndpoint = `${baseUrl}/login/oauth/access_token`;
const params = new URLSearchParams({
grant_type: "refresh_token",
refresh_token: giteaProvider.refreshToken,