Skip to content

Commit c2ce21e

Browse files
[WEB-5657] feat: add synchronization configuration for multiple providers in authentication adapter (#8336)
* feat: add sync functionality for OAuth providers - Implemented `check_sync_enabled` method to verify if sync is enabled for Google, GitHub, GitLab, and Gitea. - Added `sync_user_data` method to update user details, including first name, last name, display name, and avatar. - Updated configuration variables to include sync options for each provider. - Integrated sync check into the login/signup process. * feat: add sync toggle for OAuth providers in configuration forms * fix: remove default value for sync options in OAuth configuration forms * chore: delete old avatar and upload a new one * chore: update class method * chore: add email nullable * refactor: streamline sync check for multiple providers and improve avatar deletion logic * fix: ensure ENABLE_SYNC configurations default to "0" for Gitea, Github, Gitlab, and Google forms * fix: simplify toggle switch value handling in ControllerSwitch component --------- Co-authored-by: b-saikrishnakanth <bsaikrishnakanth97@gmail.com>
1 parent 4908211 commit c2ce21e

File tree

10 files changed

+279
-74
lines changed

10 files changed

+279
-74
lines changed

apps/admin/app/(all)/(dashboard)/authentication/gitea/form.tsx

Lines changed: 26 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import { CodeBlock } from "@/components/common/code-block";
1212
import { ConfirmDiscardModal } from "@/components/common/confirm-discard-modal";
1313
import type { TControllerInputFormField } from "@/components/common/controller-input";
1414
import { ControllerInput } from "@/components/common/controller-input";
15+
import type { TControllerSwitchFormField } from "@/components/common/controller-switch";
16+
import { ControllerSwitch } from "@/components/common/controller-switch";
1517
import type { TCopyField } from "@/components/common/copy-field";
1618
import { CopyField } from "@/components/common/copy-field";
1719
// hooks
@@ -40,6 +42,7 @@ export function InstanceGiteaConfigForm(props: Props) {
4042
GITEA_HOST: config["GITEA_HOST"] || "https://gitea.com",
4143
GITEA_CLIENT_ID: config["GITEA_CLIENT_ID"],
4244
GITEA_CLIENT_SECRET: config["GITEA_CLIENT_SECRET"],
45+
ENABLE_GITEA_SYNC: config["ENABLE_GITEA_SYNC"] || "0",
4346
},
4447
});
4548

@@ -103,6 +106,11 @@ export function InstanceGiteaConfigForm(props: Props) {
103106
},
104107
];
105108

109+
const GITEA_FORM_SWITCH_FIELD: TControllerSwitchFormField<GiteaConfigFormValues> = {
110+
name: "ENABLE_GITEA_SYNC",
111+
label: "Gitea",
112+
};
113+
106114
const GITEA_SERVICE_FIELD: TCopyField[] = [
107115
{
108116
key: "Callback_URI",
@@ -129,20 +137,22 @@ export function InstanceGiteaConfigForm(props: Props) {
129137
const onSubmit = async (formData: GiteaConfigFormValues) => {
130138
const payload: Partial<GiteaConfigFormValues> = { ...formData };
131139

132-
await updateInstanceConfigurations(payload)
133-
.then((response = []) => {
134-
setToast({
135-
type: TOAST_TYPE.SUCCESS,
136-
title: "Done!",
137-
message: "Your Gitea authentication is configured. You should test it now.",
138-
});
139-
reset({
140-
GITEA_HOST: response.find((item) => item.key === "GITEA_HOST")?.value,
141-
GITEA_CLIENT_ID: response.find((item) => item.key === "GITEA_CLIENT_ID")?.value,
142-
GITEA_CLIENT_SECRET: response.find((item) => item.key === "GITEA_CLIENT_SECRET")?.value,
143-
});
144-
})
145-
.catch((err) => console.error(err));
140+
try {
141+
const response = await updateInstanceConfigurations(payload);
142+
setToast({
143+
type: TOAST_TYPE.SUCCESS,
144+
title: "Done!",
145+
message: "Your Gitea authentication is configured. You should test it now.",
146+
});
147+
reset({
148+
GITEA_HOST: response.find((item) => item.key === "GITEA_HOST")?.value,
149+
GITEA_CLIENT_ID: response.find((item) => item.key === "GITEA_CLIENT_ID")?.value,
150+
GITEA_CLIENT_SECRET: response.find((item) => item.key === "GITEA_CLIENT_SECRET")?.value,
151+
ENABLE_GITEA_SYNC: response.find((item) => item.key === "ENABLE_GITEA_SYNC")?.value,
152+
});
153+
} catch (err) {
154+
console.error(err);
155+
}
146156
};
147157

148158
const handleGoBack = (e: React.MouseEvent<HTMLAnchorElement, MouseEvent>) => {
@@ -176,12 +186,13 @@ export function InstanceGiteaConfigForm(props: Props) {
176186
required={field.required}
177187
/>
178188
))}
189+
<ControllerSwitch control={control} field={GITEA_FORM_SWITCH_FIELD} />
179190
<div className="flex flex-col gap-1 pt-4">
180191
<div className="flex items-center gap-4">
181192
<Button
182193
variant="primary"
183194
size="lg"
184-
onClick={handleSubmit(onSubmit)}
195+
onClick={(e) => void handleSubmit(onSubmit)(e)}
185196
loading={isSubmitting}
186197
disabled={!isDirty}
187198
>

apps/admin/app/(all)/(dashboard)/authentication/github/form.tsx

Lines changed: 26 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,12 @@ import { API_BASE_URL } from "@plane/constants";
88
import { Button, getButtonStyling } from "@plane/propel/button";
99
import { TOAST_TYPE, setToast } from "@plane/propel/toast";
1010
import type { IFormattedInstanceConfiguration, TInstanceGithubAuthenticationConfigurationKeys } from "@plane/types";
11-
12-
import { cn } from "@plane/utils";
1311
// components
1412
import { CodeBlock } from "@/components/common/code-block";
1513
import { ConfirmDiscardModal } from "@/components/common/confirm-discard-modal";
1614
import type { TControllerInputFormField } from "@/components/common/controller-input";
15+
import type { TControllerSwitchFormField } from "@/components/common/controller-switch";
16+
import { ControllerSwitch } from "@/components/common/controller-switch";
1717
import { ControllerInput } from "@/components/common/controller-input";
1818
import type { TCopyField } from "@/components/common/copy-field";
1919
import { CopyField } from "@/components/common/copy-field";
@@ -43,6 +43,7 @@ export function InstanceGithubConfigForm(props: Props) {
4343
GITHUB_CLIENT_ID: config["GITHUB_CLIENT_ID"],
4444
GITHUB_CLIENT_SECRET: config["GITHUB_CLIENT_SECRET"],
4545
GITHUB_ORGANIZATION_ID: config["GITHUB_ORGANIZATION_ID"],
46+
ENABLE_GITHUB_SYNC: config["ENABLE_GITHUB_SYNC"] || "0",
4647
},
4748
});
4849

@@ -104,6 +105,11 @@ export function InstanceGithubConfigForm(props: Props) {
104105
},
105106
];
106107

108+
const GITHUB_FORM_SWITCH_FIELD: TControllerSwitchFormField<GithubConfigFormValues> = {
109+
name: "ENABLE_GITHUB_SYNC",
110+
label: "GitHub",
111+
};
112+
107113
const GITHUB_COMMON_SERVICE_DETAILS: TCopyField[] = [
108114
{
109115
key: "Origin_URL",
@@ -152,20 +158,22 @@ export function InstanceGithubConfigForm(props: Props) {
152158
const onSubmit = async (formData: GithubConfigFormValues) => {
153159
const payload: Partial<GithubConfigFormValues> = { ...formData };
154160

155-
await updateInstanceConfigurations(payload)
156-
.then((response = []) => {
157-
setToast({
158-
type: TOAST_TYPE.SUCCESS,
159-
title: "Done!",
160-
message: "Your GitHub authentication is configured. You should test it now.",
161-
});
162-
reset({
163-
GITHUB_CLIENT_ID: response.find((item) => item.key === "GITHUB_CLIENT_ID")?.value,
164-
GITHUB_CLIENT_SECRET: response.find((item) => item.key === "GITHUB_CLIENT_SECRET")?.value,
165-
GITHUB_ORGANIZATION_ID: response.find((item) => item.key === "GITHUB_ORGANIZATION_ID")?.value,
166-
});
167-
})
168-
.catch((err) => console.error(err));
161+
try {
162+
const response = await updateInstanceConfigurations(payload);
163+
setToast({
164+
type: TOAST_TYPE.SUCCESS,
165+
title: "Done!",
166+
message: "Your GitHub authentication is configured. You should test it now.",
167+
});
168+
reset({
169+
GITHUB_CLIENT_ID: response.find((item) => item.key === "GITHUB_CLIENT_ID")?.value,
170+
GITHUB_CLIENT_SECRET: response.find((item) => item.key === "GITHUB_CLIENT_SECRET")?.value,
171+
GITHUB_ORGANIZATION_ID: response.find((item) => item.key === "GITHUB_ORGANIZATION_ID")?.value,
172+
ENABLE_GITHUB_SYNC: response.find((item) => item.key === "ENABLE_GITHUB_SYNC")?.value,
173+
});
174+
} catch (err) {
175+
console.error(err);
176+
}
169177
};
170178

171179
const handleGoBack = (e: React.MouseEvent<HTMLAnchorElement, MouseEvent>) => {
@@ -199,12 +207,13 @@ export function InstanceGithubConfigForm(props: Props) {
199207
required={field.required}
200208
/>
201209
))}
210+
<ControllerSwitch control={control} field={GITHUB_FORM_SWITCH_FIELD} />
202211
<div className="flex flex-col gap-1 pt-4">
203212
<div className="flex items-center gap-4">
204213
<Button
205214
variant="primary"
206215
size="lg"
207-
onClick={handleSubmit(onSubmit)}
216+
onClick={(e) => void handleSubmit(onSubmit)(e)}
208217
loading={isSubmitting}
209218
disabled={!isDirty}
210219
>

apps/admin/app/(all)/(dashboard)/authentication/gitlab/form.tsx

Lines changed: 26 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,12 @@ import { API_BASE_URL } from "@plane/constants";
77
import { Button, getButtonStyling } from "@plane/propel/button";
88
import { TOAST_TYPE, setToast } from "@plane/propel/toast";
99
import type { IFormattedInstanceConfiguration, TInstanceGitlabAuthenticationConfigurationKeys } from "@plane/types";
10-
import { cn } from "@plane/utils";
1110
// components
1211
import { CodeBlock } from "@/components/common/code-block";
1312
import { ConfirmDiscardModal } from "@/components/common/confirm-discard-modal";
1413
import type { TControllerInputFormField } from "@/components/common/controller-input";
14+
import type { TControllerSwitchFormField } from "@/components/common/controller-switch";
15+
import { ControllerSwitch } from "@/components/common/controller-switch";
1516
import { ControllerInput } from "@/components/common/controller-input";
1617
import type { TCopyField } from "@/components/common/copy-field";
1718
import { CopyField } from "@/components/common/copy-field";
@@ -41,6 +42,7 @@ export function InstanceGitlabConfigForm(props: Props) {
4142
GITLAB_HOST: config["GITLAB_HOST"],
4243
GITLAB_CLIENT_ID: config["GITLAB_CLIENT_ID"],
4344
GITLAB_CLIENT_SECRET: config["GITLAB_CLIENT_SECRET"],
45+
ENABLE_GITLAB_SYNC: config["ENABLE_GITLAB_SYNC"] || "0",
4446
},
4547
});
4648

@@ -108,6 +110,11 @@ export function InstanceGitlabConfigForm(props: Props) {
108110
},
109111
];
110112

113+
const GITLAB_FORM_SWITCH_FIELD: TControllerSwitchFormField<GitlabConfigFormValues> = {
114+
name: "ENABLE_GITLAB_SYNC",
115+
label: "GitLab",
116+
};
117+
111118
const GITLAB_SERVICE_FIELD: TCopyField[] = [
112119
{
113120
key: "Callback_URL",
@@ -134,20 +141,22 @@ export function InstanceGitlabConfigForm(props: Props) {
134141
const onSubmit = async (formData: GitlabConfigFormValues) => {
135142
const payload: Partial<GitlabConfigFormValues> = { ...formData };
136143

137-
await updateInstanceConfigurations(payload)
138-
.then((response = []) => {
139-
setToast({
140-
type: TOAST_TYPE.SUCCESS,
141-
title: "Done!",
142-
message: "Your GitLab authentication is configured. You should test it now.",
143-
});
144-
reset({
145-
GITLAB_HOST: response.find((item) => item.key === "GITLAB_HOST")?.value,
146-
GITLAB_CLIENT_ID: response.find((item) => item.key === "GITLAB_CLIENT_ID")?.value,
147-
GITLAB_CLIENT_SECRET: response.find((item) => item.key === "GITLAB_CLIENT_SECRET")?.value,
148-
});
149-
})
150-
.catch((err) => console.error(err));
144+
try {
145+
const response = await updateInstanceConfigurations(payload);
146+
setToast({
147+
type: TOAST_TYPE.SUCCESS,
148+
title: "Done!",
149+
message: "Your GitLab authentication is configured. You should test it now.",
150+
});
151+
reset({
152+
GITLAB_HOST: response.find((item) => item.key === "GITLAB_HOST")?.value,
153+
GITLAB_CLIENT_ID: response.find((item) => item.key === "GITLAB_CLIENT_ID")?.value,
154+
GITLAB_CLIENT_SECRET: response.find((item) => item.key === "GITLAB_CLIENT_SECRET")?.value,
155+
ENABLE_GITLAB_SYNC: response.find((item) => item.key === "ENABLE_GITLAB_SYNC")?.value,
156+
});
157+
} catch (err) {
158+
console.error(err);
159+
}
151160
};
152161

153162
const handleGoBack = (e: React.MouseEvent<HTMLAnchorElement, MouseEvent>) => {
@@ -181,12 +190,13 @@ export function InstanceGitlabConfigForm(props: Props) {
181190
required={field.required}
182191
/>
183192
))}
193+
<ControllerSwitch control={control} field={GITLAB_FORM_SWITCH_FIELD} />
184194
<div className="flex flex-col gap-1 pt-4">
185195
<div className="flex items-center gap-4">
186196
<Button
187197
variant="primary"
188198
size="lg"
189-
onClick={handleSubmit(onSubmit)}
199+
onClick={(e) => void handleSubmit(onSubmit)(e)}
190200
loading={isSubmitting}
191201
disabled={!isDirty}
192202
>

apps/admin/app/(all)/(dashboard)/authentication/google/form.tsx

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,12 @@ import { API_BASE_URL } from "@plane/constants";
88
import { Button, getButtonStyling } from "@plane/propel/button";
99
import { TOAST_TYPE, setToast } from "@plane/propel/toast";
1010
import type { IFormattedInstanceConfiguration, TInstanceGoogleAuthenticationConfigurationKeys } from "@plane/types";
11-
import { cn } from "@plane/utils";
1211
// components
1312
import { CodeBlock } from "@/components/common/code-block";
1413
import { ConfirmDiscardModal } from "@/components/common/confirm-discard-modal";
1514
import type { TControllerInputFormField } from "@/components/common/controller-input";
15+
import type { TControllerSwitchFormField } from "@/components/common/controller-switch";
16+
import { ControllerSwitch } from "@/components/common/controller-switch";
1617
import { ControllerInput } from "@/components/common/controller-input";
1718
import type { TCopyField } from "@/components/common/copy-field";
1819
import { CopyField } from "@/components/common/copy-field";
@@ -41,6 +42,7 @@ export function InstanceGoogleConfigForm(props: Props) {
4142
defaultValues: {
4243
GOOGLE_CLIENT_ID: config["GOOGLE_CLIENT_ID"],
4344
GOOGLE_CLIENT_SECRET: config["GOOGLE_CLIENT_SECRET"],
45+
ENABLE_GOOGLE_SYNC: config["ENABLE_GOOGLE_SYNC"] || "0",
4446
},
4547
});
4648

@@ -93,6 +95,11 @@ export function InstanceGoogleConfigForm(props: Props) {
9395
},
9496
];
9597

98+
const GOOGLE_FORM_SWITCH_FIELD: TControllerSwitchFormField<GoogleConfigFormValues> = {
99+
name: "ENABLE_GOOGLE_SYNC",
100+
label: "Google",
101+
};
102+
96103
const GOOGLE_COMMON_SERVICE_DETAILS: TCopyField[] = [
97104
{
98105
key: "Origin_URL",
@@ -140,19 +147,21 @@ export function InstanceGoogleConfigForm(props: Props) {
140147
const onSubmit = async (formData: GoogleConfigFormValues) => {
141148
const payload: Partial<GoogleConfigFormValues> = { ...formData };
142149

143-
await updateInstanceConfigurations(payload)
144-
.then((response = []) => {
145-
setToast({
146-
type: TOAST_TYPE.SUCCESS,
147-
title: "Done!",
148-
message: "Your Google authentication is configured. You should test it now.",
149-
});
150-
reset({
151-
GOOGLE_CLIENT_ID: response.find((item) => item.key === "GOOGLE_CLIENT_ID")?.value,
152-
GOOGLE_CLIENT_SECRET: response.find((item) => item.key === "GOOGLE_CLIENT_SECRET")?.value,
153-
});
154-
})
155-
.catch((err) => console.error(err));
150+
try {
151+
const response = await updateInstanceConfigurations(payload);
152+
setToast({
153+
type: TOAST_TYPE.SUCCESS,
154+
title: "Done!",
155+
message: "Your Google authentication is configured. You should test it now.",
156+
});
157+
reset({
158+
GOOGLE_CLIENT_ID: response.find((item) => item.key === "GOOGLE_CLIENT_ID")?.value,
159+
GOOGLE_CLIENT_SECRET: response.find((item) => item.key === "GOOGLE_CLIENT_SECRET")?.value,
160+
ENABLE_GOOGLE_SYNC: response.find((item) => item.key === "ENABLE_GOOGLE_SYNC")?.value,
161+
});
162+
} catch (err) {
163+
console.error(err);
164+
}
156165
};
157166

158167
const handleGoBack = (e: React.MouseEvent<HTMLAnchorElement, MouseEvent>) => {
@@ -186,12 +195,13 @@ export function InstanceGoogleConfigForm(props: Props) {
186195
required={field.required}
187196
/>
188197
))}
198+
<ControllerSwitch control={control} field={GOOGLE_FORM_SWITCH_FIELD} />
189199
<div className="flex flex-col gap-1 pt-4">
190200
<div className="flex items-center gap-4">
191201
<Button
192202
variant="primary"
193203
size="lg"
194-
onClick={handleSubmit(onSubmit)}
204+
onClick={(e) => void handleSubmit(onSubmit)(e)}
195205
loading={isSubmitting}
196206
disabled={!isDirty}
197207
>
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import type { Control, FieldPath, FieldValues } from "react-hook-form";
2+
import { Controller } from "react-hook-form";
3+
// plane internal packages
4+
import { ToggleSwitch } from "@plane/ui";
5+
6+
type Props<T extends FieldValues = FieldValues> = {
7+
control: Control<T>;
8+
field: TControllerSwitchFormField<T>;
9+
};
10+
11+
export type TControllerSwitchFormField<T extends FieldValues = FieldValues> = {
12+
name: FieldPath<T>;
13+
label: string;
14+
};
15+
16+
export function ControllerSwitch<T extends FieldValues>(props: Props<T>) {
17+
const {
18+
control,
19+
field: { name, label },
20+
} = props;
21+
22+
return (
23+
<div className="flex items-center justify-between gap-1">
24+
<h4 className="text-sm text-custom-text-300">Refresh user attributes from {label} during sign in</h4>
25+
<div className="relative">
26+
<Controller
27+
control={control}
28+
name={name as FieldPath<T>}
29+
render={({ field: { value, onChange } }) => (
30+
<ToggleSwitch
31+
value={Boolean(parseInt(value))}
32+
onChange={() => (parseInt(value) ? onChange("0") : onChange("1"))}
33+
size="sm"
34+
/>
35+
)}
36+
/>
37+
</div>
38+
</div>
39+
);
40+
}

0 commit comments

Comments
 (0)