import { Divider, Option, Select, Typography } from "@mui/joy"; import { Button, Input } from "@usememos/mui"; import { XIcon } from "lucide-react"; import { useEffect, useState } from "react"; import { toast } from "react-hot-toast"; 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"; import { generateDialog } from "./Dialog"; 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 extends DialogProps { identityProvider?: IdentityProvider; confirmCallback?: () => void; } const CreateIdentityProviderDialog: React.FC = (props: Props) => { const t = useTranslate(); const identityProviderTypes = [...new Set(templateList.map((t) => t.type))]; const { confirmCallback, destroy, identityProvider } = props; 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; useEffect(() => { if (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(" ")); } } }, []); useEffect(() => { if (!isCreating) { 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]); const handleCloseBtnClick = () => { destroy(); }; 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); } if (confirmCallback) { confirmCallback(); } destroy(); }; 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, }) } fullWidth /> {t("setting.sso-section.identifier-filter")} setBasicInfo({ ...basicInfo, identifierFilter: e.target.value, }) } fullWidth /> {type === "OAUTH2" && ( <> {isCreating && (

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

)} {t("setting.sso-section.client-id")} * setPartialOAuth2Config({ clientId: e.target.value })} fullWidth /> {t("setting.sso-section.client-secret")} * setPartialOAuth2Config({ clientSecret: e.target.value })} fullWidth /> {t("setting.sso-section.authorization-endpoint")} * setPartialOAuth2Config({ authUrl: e.target.value })} fullWidth /> {t("setting.sso-section.token-endpoint")} * setPartialOAuth2Config({ tokenUrl: e.target.value })} fullWidth /> {t("setting.sso-section.user-endpoint")} * setPartialOAuth2Config({ userInfoUrl: e.target.value })} fullWidth /> {t("setting.sso-section.scopes")} * setOAuth2Scopes(e.target.value)} fullWidth /> {t("setting.sso-section.identifier")} * setPartialOAuth2Config({ fieldMapping: { ...oauth2Config.fieldMapping, identifier: e.target.value } as FieldMapping }) } fullWidth /> {t("setting.sso-section.display-name")} setPartialOAuth2Config({ fieldMapping: { ...oauth2Config.fieldMapping, displayName: e.target.value } as FieldMapping }) } fullWidth /> {t("common.email")} setPartialOAuth2Config({ fieldMapping: { ...oauth2Config.fieldMapping, email: e.target.value } as FieldMapping }) } fullWidth /> Avatar URL setPartialOAuth2Config({ fieldMapping: { ...oauth2Config.fieldMapping, avatarUrl: e.target.value } as FieldMapping }) } fullWidth /> )}
); }; function showCreateIdentityProviderDialog(identityProvider?: IdentityProvider, confirmCallback?: () => void) { generateDialog( { className: "create-identity-provider-dialog", dialogName: "create-identity-provider-dialog", }, CreateIdentityProviderDialog, { identityProvider, confirmCallback }, ); } export default showCreateIdentityProviderDialog;