import { useEffect, useState } from "react"; import { toast } from "react-hot-toast"; import { Button } from "@/components/ui/button"; import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog"; import { Input } from "@/components/ui/input"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Separator } from "@/components/ui/separator"; import { identityProviderServiceClient } from "@/grpcweb"; import { absolutifyLink } from "@/helpers/utils"; import { FieldMapping, IdentityProvider, IdentityProvider_Type, OAuth2Config } from "@/types/proto/api/v1/idp_service"; import { useTranslate } from "@/utils/i18n"; const templateList: IdentityProvider[] = [ { name: "", title: "GitHub", type: IdentityProvider_Type.OAUTH2, identifierFilter: "", config: { oauth2Config: { clientId: "", clientSecret: "", authUrl: "https://github.com/login/oauth/authorize", tokenUrl: "https://github.com/login/oauth/access_token", userInfoUrl: "https://api.github.com/user", scopes: ["read:user"], fieldMapping: FieldMapping.fromPartial({ identifier: "login", displayName: "name", email: "email", }), }, }, }, { name: "", title: "GitLab", type: IdentityProvider_Type.OAUTH2, identifierFilter: "", config: { oauth2Config: { clientId: "", clientSecret: "", authUrl: "https://gitlab.com/oauth/authorize", tokenUrl: "https://gitlab.com/oauth/token", userInfoUrl: "https://gitlab.com/oauth/userinfo", scopes: ["openid"], fieldMapping: FieldMapping.fromPartial({ identifier: "name", displayName: "name", email: "email", }), }, }, }, { name: "", title: "Google", type: IdentityProvider_Type.OAUTH2, identifierFilter: "", config: { oauth2Config: { clientId: "", clientSecret: "", authUrl: "https://accounts.google.com/o/oauth2/v2/auth", tokenUrl: "https://oauth2.googleapis.com/token", userInfoUrl: "https://www.googleapis.com/oauth2/v2/userinfo", scopes: ["https://www.googleapis.com/auth/userinfo.email", "https://www.googleapis.com/auth/userinfo.profile"], fieldMapping: FieldMapping.fromPartial({ identifier: "email", displayName: "name", email: "email", }), }, }, }, { name: "", title: "Custom", type: IdentityProvider_Type.OAUTH2, identifierFilter: "", config: { oauth2Config: { clientId: "", clientSecret: "", authUrl: "", tokenUrl: "", userInfoUrl: "", scopes: [], fieldMapping: FieldMapping.fromPartial({ identifier: "", displayName: "", email: "", }), }, }, }, ]; interface Props { open: boolean; onOpenChange: (open: boolean) => void; identityProvider?: IdentityProvider; onSuccess?: () => void; } function CreateIdentityProviderDialog({ open, onOpenChange, identityProvider, onSuccess }: Props) { const t = useTranslate(); const identityProviderTypes = [...new Set(templateList.map((t) => t.type))]; const [basicInfo, setBasicInfo] = useState({ title: "", identifierFilter: "", }); const [type, setType] = useState(IdentityProvider_Type.OAUTH2); const [oauth2Config, setOAuth2Config] = useState({ clientId: "", clientSecret: "", authUrl: "", tokenUrl: "", userInfoUrl: "", scopes: [], fieldMapping: FieldMapping.fromPartial({ identifier: "", displayName: "", email: "", }), }); const [oauth2Scopes, setOAuth2Scopes] = useState(""); const [selectedTemplate, setSelectedTemplate] = useState("GitHub"); const isCreating = identityProvider === undefined; // Reset state when dialog is closed useEffect(() => { if (!open) { // Reset to default state when dialog is closed setBasicInfo({ title: "", identifierFilter: "", }); setType(IdentityProvider_Type.OAUTH2); setOAuth2Config({ clientId: "", clientSecret: "", authUrl: "", tokenUrl: "", userInfoUrl: "", scopes: [], fieldMapping: FieldMapping.fromPartial({ identifier: "", displayName: "", email: "", }), }); setOAuth2Scopes(""); setSelectedTemplate("GitHub"); } }, [open]); // Load existing identity provider data when editing useEffect(() => { if (open && identityProvider) { setBasicInfo({ title: identityProvider.title, identifierFilter: identityProvider.identifierFilter, }); setType(identityProvider.type); if (identityProvider.type === IdentityProvider_Type.OAUTH2) { const oauth2Config = OAuth2Config.fromPartial(identityProvider.config?.oauth2Config || {}); setOAuth2Config(oauth2Config); setOAuth2Scopes(oauth2Config.scopes.join(" ")); } } }, [open, identityProvider]); // Load template data when creating new IDP useEffect(() => { if (!isCreating || !open) { return; } const template = templateList.find((t) => t.title === selectedTemplate); if (template) { setBasicInfo({ title: template.title, identifierFilter: template.identifierFilter, }); setType(template.type); if (template.type === IdentityProvider_Type.OAUTH2) { const oauth2Config = OAuth2Config.fromPartial(template.config?.oauth2Config || {}); setOAuth2Config(oauth2Config); setOAuth2Scopes(oauth2Config.scopes.join(" ")); } } }, [selectedTemplate, isCreating, open]); const handleCloseBtnClick = () => { onOpenChange(false); }; const allowConfirmAction = () => { if (basicInfo.title === "") { return false; } if (type === "OAUTH2") { if ( oauth2Config.clientId === "" || oauth2Config.authUrl === "" || oauth2Config.tokenUrl === "" || oauth2Config.userInfoUrl === "" || oauth2Scopes === "" || oauth2Config.fieldMapping?.identifier === "" ) { return false; } if (isCreating) { if (oauth2Config.clientSecret === "") { return false; } } } return true; }; const handleConfirmBtnClick = async () => { try { if (isCreating) { await identityProviderServiceClient.createIdentityProvider({ identityProvider: { ...basicInfo, type: type, config: { oauth2Config: { ...oauth2Config, scopes: oauth2Scopes.split(" "), }, }, }, }); toast.success(t("setting.sso-section.sso-created", { name: basicInfo.title })); } else { await identityProviderServiceClient.updateIdentityProvider({ identityProvider: { ...basicInfo, name: identityProvider!.name, type: type, config: { oauth2Config: { ...oauth2Config, scopes: oauth2Scopes.split(" "), }, }, }, updateMask: ["title", "identifier_filter", "config"], }); toast.success(t("setting.sso-section.sso-updated", { name: basicInfo.title })); } } catch (error: any) { toast.error(error.details); console.error(error); } onSuccess?.(); onOpenChange(false); }; const setPartialOAuth2Config = (state: Partial) => { setOAuth2Config({ ...oauth2Config, ...state, }); }; return ( {t(isCreating ? "setting.sso-section.create-sso" : "setting.sso-section.update-sso")}
{isCreating && ( <>

{t("common.type")}

{t("setting.sso-section.template")}

)}

{t("common.name")} *

setBasicInfo({ ...basicInfo, title: e.target.value, }) } />

{t("setting.sso-section.identifier-filter")}

setBasicInfo({ ...basicInfo, identifierFilter: e.target.value, }) } /> {type === "OAUTH2" && ( <> {isCreating && (

{t("setting.sso-section.redirect-url")}: {absolutifyLink("/auth/callback")}

)}

{t("setting.sso-section.client-id")} *

setPartialOAuth2Config({ clientId: e.target.value })} />

{t("setting.sso-section.client-secret")} *

setPartialOAuth2Config({ clientSecret: e.target.value })} />

{t("setting.sso-section.authorization-endpoint")} *

setPartialOAuth2Config({ authUrl: e.target.value })} />

{t("setting.sso-section.token-endpoint")} *

setPartialOAuth2Config({ tokenUrl: e.target.value })} />

{t("setting.sso-section.user-endpoint")} *

setPartialOAuth2Config({ userInfoUrl: e.target.value })} />

{t("setting.sso-section.scopes")} *

setOAuth2Scopes(e.target.value)} />

{t("setting.sso-section.identifier")} *

setPartialOAuth2Config({ fieldMapping: { ...oauth2Config.fieldMapping, identifier: e.target.value } as FieldMapping }) } />

{t("setting.sso-section.display-name")}

setPartialOAuth2Config({ fieldMapping: { ...oauth2Config.fieldMapping, displayName: e.target.value } as FieldMapping }) } />

{t("common.email")}

setPartialOAuth2Config({ fieldMapping: { ...oauth2Config.fieldMapping, email: e.target.value } as FieldMapping }) } />

Avatar URL

setPartialOAuth2Config({ fieldMapping: { ...oauth2Config.fieldMapping, avatarUrl: e.target.value } as FieldMapping }) } /> )}
); } export default CreateIdentityProviderDialog;