From 0a04f36a9c092b1fa9c446044fda08208df22718 Mon Sep 17 00:00:00 2001 From: Niranjan Kumar Date: Thu, 25 Jun 2026 18:21:52 +0530 Subject: [PATCH] sync latest change for social provider --- .../tenant/communications/page.tsx | 152 ++- src/app/(dashboard)/tenant/social/page.tsx | 1180 ++++++++++------- 2 files changed, 845 insertions(+), 487 deletions(-) diff --git a/src/app/(dashboard)/tenant/communications/page.tsx b/src/app/(dashboard)/tenant/communications/page.tsx index d4e7ddf..260e681 100644 --- a/src/app/(dashboard)/tenant/communications/page.tsx +++ b/src/app/(dashboard)/tenant/communications/page.tsx @@ -1,7 +1,7 @@ "use client"; import type { Dispatch, ReactNode, SetStateAction } from "react"; -import { useMemo, useState } from "react"; +import { useState } from "react"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { Loader2, @@ -48,10 +48,111 @@ import { useAuthStore } from "@/stores/auth-store"; const PROVIDER_LABELS: Record = { sendgrid: "SendGrid", ses: "AWS SES", + smtp: "SMTP", + console: "Console", twilio: "Twilio", android_gateway: "Android Gateway", }; +const EMAIL_PROVIDER_META: Record< + string, + { + credentialLabel: string; + credentialPlaceholder: string; + credentialHelp: string; + fromLabel: string; + fromPlaceholder: string; + } +> = { + sendgrid: { + credentialLabel: "SendGrid API key", + credentialPlaceholder: "SG.xxxxx", + credentialHelp: + "Paste the SendGrid API key used to send transactional mail.", + fromLabel: "From email", + fromPlaceholder: "noreply@example.com", + }, + ses: { + credentialLabel: "AWS SES credentials", + credentialPlaceholder: "access_key_id:secret_access_key", + credentialHelp: + "Use `access_key_id:secret_access_key` when storing per-tenant SES credentials.", + fromLabel: "From email", + fromPlaceholder: "noreply@example.com", + }, + smtp: { + credentialLabel: "SMTP credentials", + credentialPlaceholder: "username:password@smtp.example.com:465", + credentialHelp: + "Format credentials as `username:password@host:port` to match the SMTP provider implementation.", + fromLabel: "From email", + fromPlaceholder: "noreply@example.com", + }, + console: { + credentialLabel: "Console provider token", + credentialPlaceholder: "debug-token", + credentialHelp: + "Console provider is mainly for debugging. If enabled by the backend, any non-empty placeholder value is sufficient.", + fromLabel: "From email", + fromPlaceholder: "noreply@example.com", + }, +}; + +const SMS_PROVIDER_META: Record< + string, + { + fromLabel: string; + fromPlaceholder: string; + fromHelp: string; + accountLabel: string; + accountPlaceholder: string; + accountHelp: string; + credentialLabel: string; + credentialPlaceholder: string; + credentialHelp: string; + } +> = { + twilio: { + fromLabel: "From number / messaging service SID", + fromPlaceholder: "+15551234567 or MGxxxxxxxx", + fromHelp: + "Use a Twilio phone number or a Messaging Service SID starting with `MG`.", + accountLabel: "Twilio account SID", + accountPlaceholder: "ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", + accountHelp: "The account SID used to authenticate Twilio API calls.", + credentialLabel: "Twilio auth token", + credentialPlaceholder: "twilio-auth-token", + credentialHelp: "Paste the Twilio auth token for this account.", + }, + android_gateway: { + fromLabel: "From number / sender label", + fromPlaceholder: "gateway", + fromHelp: + "Used only for display/context. The Android gateway sends from the SIM configured on the device.", + accountLabel: "Android gateway base URL", + accountPlaceholder: "https://api.sms-gate.app/3rdparty/v1", + accountHelp: + "Point this to the SMS Gateway for Android host or cloud endpoint expected by the backend provider.", + credentialLabel: "Gateway basic auth", + credentialPlaceholder: "username:password", + credentialHelp: + "If your gateway is protected, store credentials as `username:password`.", + }, + console: { + fromLabel: "From label", + fromPlaceholder: "console", + fromHelp: "Used only for debugging output.", + accountLabel: "Gateway / account reference", + accountPlaceholder: "debug-endpoint", + accountHelp: + "Optional reference shown in the UI when using the console provider.", + credentialLabel: "Console provider token", + credentialPlaceholder: "debug-token", + credentialHelp: + "Console provider is mainly for debugging. If enabled by the backend, any non-empty placeholder value is sufficient.", + }, +}; + function providerLabel(value: string) { return PROVIDER_LABELS[value] ?? value; } @@ -752,6 +853,9 @@ function EmailConfigDialog({ submitLabel: string; showSecretHint?: boolean; }) { + const providerMeta = + EMAIL_PROVIDER_META[form.provider] ?? EMAIL_PROVIDER_META.sendgrid; + return ( @@ -776,9 +880,10 @@ function EmailConfigDialog({
- + setForm((current) => ({ @@ -791,16 +896,20 @@ function EmailConfigDialog({
setForm((current) => ({ ...current, api_key: e.target.value })) } /> +

+ {providerMeta.credentialHelp} +

@@ -842,10 +951,8 @@ function SmsConfigDialog({ submitLabel: string; showSecretHint?: boolean; }) { - const providerRequiresAccountSid = useMemo( - () => form.provider !== "android_gateway", - [form.provider], - ); + const providerMeta = + SMS_PROVIDER_META[form.provider] ?? SMS_PROVIDER_META.android_gateway; return ( @@ -871,8 +978,9 @@ function SmsConfigDialog({
- + setForm((current) => ({ @@ -881,13 +989,12 @@ function SmsConfigDialog({ })) } /> +

+ {providerMeta.fromHelp} +

- + @@ -896,26 +1003,29 @@ function SmsConfigDialog({ account_sid: e.target.value, })) } - placeholder={ - providerRequiresAccountSid - ? "ACxxxxxxxx" - : "https://api.sms-gate.app/3rdparty/v1" - } + placeholder={providerMeta.accountPlaceholder} /> +

+ {providerMeta.accountHelp} +

setForm((current) => ({ ...current, api_key: e.target.value })) } /> +

+ {providerMeta.credentialHelp} +

diff --git a/src/app/(dashboard)/tenant/social/page.tsx b/src/app/(dashboard)/tenant/social/page.tsx index fb4d6e3..5234290 100644 --- a/src/app/(dashboard)/tenant/social/page.tsx +++ b/src/app/(dashboard)/tenant/social/page.tsx @@ -3,44 +3,44 @@ import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; import { apiClient } from "@/lib/api-client"; import { getApiErrorMessage } from "@/lib/errors"; -import { SocialProviderConfig } from "@/lib/types"; import { useActiveTenant } from "@/hooks/use-active-tenant"; import { - Globe, - Trash2, - ShieldCheck, - Loader2, - ExternalLink, - Github, - Chrome, - MoreVertical, - PlusCircle, - AlertTriangle, - Pencil + Globe, + Trash2, + ShieldCheck, + Loader2, + ExternalLink, + Github, + Chrome, + MoreVertical, + PlusCircle, + AlertTriangle, + Pencil, + Building2, } from "lucide-react"; import type { LucideIcon } from "lucide-react"; import { - Card, - CardContent, - CardDescription, - CardHeader, - CardTitle, + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, } from "@/components/ui/card"; import { - Dialog, - DialogContent, - DialogDescription, - DialogFooter, - DialogHeader, - DialogTitle, - DialogTrigger, + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, } from "@/components/ui/dialog"; import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuTrigger, + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; @@ -49,453 +49,701 @@ import { toast } from "sonner"; import { useState } from "react"; import { ConfirmDialog } from "@/components/confirm-dialog"; -interface TenantSocialProvider extends SocialProviderConfig { - id: string; - redirect_uri?: string; - oidc_discovery_url?: string; - client_secret_prefix?: string; - is_active?: boolean; +type SocialProviderName = "google" | "github" | "microsoft" | "authengine"; + +interface TenantSocialProvider { + id: string; + tenant_id: string; + provider: SocialProviderName; + client_id: string; // encrypted in backend responses; never display as raw client id + client_secret_prefix?: string; + redirect_uri?: string; + oidc_discovery_url?: string; + is_active: boolean; + created_at: string; + updated_at: string; } interface SocialProviderCreateForm { - provider: string; - client_id: string; - client_secret: string; - redirect_uri: string; - oidc_discovery_url: string; + provider: SocialProviderName; + client_id: string; + client_secret: string; + redirect_uri: string; + oidc_discovery_url: string; } interface SocialProviderUpdateForm { - client_id: string; - client_secret?: string; - redirect_uri: string; - oidc_discovery_url: string; + client_id: string; + client_secret: string; + redirect_uri: string; + oidc_discovery_url: string; } -const PROVIDER_ICONS: Record = { - google: Chrome, - github: Github, - oidc: Globe, +const SOCIAL_PROVIDER_OPTIONS: Array<{ + value: SocialProviderName; + label: string; + description: string; +}> = [ + { + value: "google", + label: "Google", + description: "Google OAuth credentials for tenant login", + }, + { + value: "github", + label: "GitHub", + description: "GitHub OAuth app credentials", + }, + { + value: "microsoft", + label: "Microsoft", + description: "Microsoft Entra / Azure AD OAuth credentials", + }, + { + value: "authengine", + label: "AuthEngine", + description: "Use another AuthEngine instance as the identity provider", + }, +]; + +const PROVIDER_ICONS: Record = { + google: Chrome, + github: Github, + microsoft: Building2, + authengine: Globe, }; +const PROVIDER_LABELS: Record = { + google: "Google", + github: "GitHub", + microsoft: "Microsoft", + authengine: "AuthEngine", +}; + +function defaultRedirectUri(provider: SocialProviderName) { + return `https://app.authengine.org/oauth/${provider}/callback`; +} + +function createEmptyProviderForm( + provider: SocialProviderName = "google", +): SocialProviderCreateForm { + return { + provider, + client_id: "", + client_secret: "", + redirect_uri: defaultRedirectUri(provider), + oidc_discovery_url: "", + }; +} + +function createEmptyEditForm( + provider: SocialProviderName, +): SocialProviderUpdateForm { + return { + client_id: "", + client_secret: "", + redirect_uri: defaultRedirectUri(provider), + oidc_discovery_url: "", + }; +} + export default function TenantSocialPage() { - const queryClient = useQueryClient(); - const { activeTenantId, activeTenant, isReady, isLoading: isLoadingTenant } = useActiveTenant(); - const [isAdding, setIsAdding] = useState(false); - const [isEditing, setIsEditing] = useState(false); - const [editingProvider, setEditingProvider] = useState(null); - const [deleteProviderTarget, setDeleteProviderTarget] = useState(null); - - const [newProvider, setNewProvider] = useState({ - provider: "google", - client_id: "", - client_secret: "", - redirect_uri: "", - oidc_discovery_url: "" - }); - - const [editForm, setEditForm] = useState({ - client_id: "", - client_secret: "", - redirect_uri: "", - oidc_discovery_url: "" - }); - - const { data: providers, isLoading } = useQuery({ - queryKey: ["tenantSocialProviders", activeTenantId], - queryFn: async () => { - const { data } = await apiClient.get(`/tenants/${activeTenantId}/social-providers`); - return data; - }, - enabled: isReady, - }); - - const createMutation = useMutation({ - mutationFn: async (payload: SocialProviderCreateForm) => { - await apiClient.post(`/tenants/${activeTenantId}/social-providers`, payload); - }, - onSuccess: () => { - toast.success("Social provider added"); - setIsAdding(false); - setNewProvider({ provider: "google", client_id: "", client_secret: "", redirect_uri: "", oidc_discovery_url: "" }); - queryClient.invalidateQueries({ queryKey: ["tenantSocialProviders", activeTenantId] }); - }, - onError: (error: unknown) => { - toast.error(getApiErrorMessage(error, "Failed to add social provider")); - } - }); - - // 3. Update Provider (PUT) - const updateMutation = useMutation({ - mutationFn: async ({ provider, payload }: { provider: string; payload: SocialProviderUpdateForm }) => { - await apiClient.put(`/tenants/${activeTenantId}/social-providers/${provider}`, payload); - }, - onSuccess: () => { - toast.success("Social provider updated"); - setIsEditing(false); - setEditingProvider(null); - queryClient.invalidateQueries({ queryKey: ["tenantSocialProviders", activeTenantId] }); - }, - onError: (error: unknown) => { - toast.error(getApiErrorMessage(error, "Failed to update social provider")); - } - }); - - // 4. Delete Provider - const deleteMutation = useMutation({ - mutationFn: async (provider: string) => { - await apiClient.delete(`/tenants/${activeTenantId}/social-providers/${provider}`); - }, - onSuccess: () => { - toast.success("Social provider removed"); - queryClient.invalidateQueries({ queryKey: ["tenantSocialProviders", activeTenantId] }); - }, - onError: (error: unknown) => { - toast.error(getApiErrorMessage(error, "Failed to remove social provider")); - } - }); - - // 5. Toggle Provider - const toggleMutation = useMutation({ - mutationFn: async ({ provider, is_active }: { provider: string, is_active: boolean }) => { - await apiClient.patch(`/tenants/${activeTenantId}/social-providers/${provider}/toggle`, { is_active }); - }, - onSuccess: () => { - queryClient.invalidateQueries({ queryKey: ["tenantSocialProviders", activeTenantId] }); - }, - onError: (error: unknown) => { - toast.error(getApiErrorMessage(error, "Failed to toggle provider")); - } - }); - - const openEditDialog = (p: TenantSocialProvider) => { - setEditingProvider(p); - setEditForm({ - client_id: p.client_id || "", - client_secret: "", - redirect_uri: p.redirect_uri || "", - oidc_discovery_url: p.oidc_discovery_url || "" - }); - setIsEditing(true); - }; - - if (isLoadingTenant) { - return ( -
- -
- ); - } - - if (!isReady) return
Select an organization.
; - - return ( -
-
-
-

Social Login (BYO-ID)

-

- {activeTenant?.type === "PLATFORM" - ? "Configure social login providers used on the public login page." - : "Configure custom OAuth/OIDC providers for your organization members."} -

-
- - {/* Add Provider Dialog */} - - - - - - - Add Social Identity Provider - - Configure own credentials for Google, GitHub, or any OIDC provider. - - -
-
- - -
- - {newProvider.provider === "authengine" && ( -
- - setNewProvider({ ...newProvider, oidc_discovery_url: e.target.value })} - /> -

- API root URL of the remote AuthEngine instance. A discovery URL ending in - /.well-known/openid-configuration is also accepted. -

-
- )} - -
- - setNewProvider({ ...newProvider, client_id: e.target.value })} - /> -
- -
- - setNewProvider({ ...newProvider, client_secret: e.target.value })} - /> -
- -
- - setNewProvider({ ...newProvider, redirect_uri: e.target.value })} - /> -

- This must match exactly the URI registered in your provider's console. -

-
-
- - - -
-
-
- - {/* Edit Provider Dialog */} - - - - - Edit {editingProvider?.provider ? editingProvider.provider.charAt(0).toUpperCase() + editingProvider.provider.slice(1) : ""} Provider - - - Update the credentials and configuration for this social provider. - - -
- {editingProvider?.provider === "authengine" && ( -
- - setEditForm({ ...editForm, oidc_discovery_url: e.target.value })} - /> -

- API root URL of the remote AuthEngine instance. A discovery URL ending in - /.well-known/openid-configuration is also accepted. -

-
- )} - -
- - setEditForm({ ...editForm, client_id: e.target.value })} - /> -
- -
- - setEditForm({ ...editForm, client_secret: e.target.value })} - /> -

- Leave blank to keep the existing secret unchanged. -

-
- -
- - setEditForm({ ...editForm, redirect_uri: e.target.value })} - /> -
-
- - - - -
-
- -
- {isLoading ? ( -
- -
- ) : providers?.length === 0 ? ( - - -
- -
-
-

No custom providers configured

-

- Users will use the platform's default social login credentials unless you configure your own here. -

-
- -
-
- ) : ( -
- {providers?.map((p) => { - const Icon = PROVIDER_ICONS[p.provider] || Globe; - return ( - - -
-
- -
-
- {p.provider} - - ID: {p.id.slice(0, 8)}... - -
-
-
-
- {p.is_active ? "Active" : "Disabled"} - toggleMutation.mutate({ provider: p.provider, is_active: val })} - /> -
- - - - - - openEditDialog(p)} - > - Edit Configuration - - setDeleteProviderTarget(p.provider)} - > - Delete - - - -
-
- -
-

Client ID

-

- {p.client_id} -

-
-
-

Client Secret

-

- {p.client_secret_prefix} -

-
-
-

Redirect URI

-

- {p.redirect_uri} - -

-
-
-
- ); - })} -
- )} -
- -
- -
-

Security Warning

-

- Ensure your redirect URIs are secured and match your provider's console exactly. - AuthEngine encrypts your Client IDs and Secrets at rest using platform-level keys. -

-
-
- - !open && setDeleteProviderTarget(null)} - title="Remove social provider?" - description={ - deleteProviderTarget - ? `The custom ${deleteProviderTarget} configuration will be deleted.` - : undefined - } - confirmLabel="Remove" - loading={deleteMutation.isPending} - onConfirm={() => { - if (deleteProviderTarget) { - deleteMutation.mutate(deleteProviderTarget); - setDeleteProviderTarget(null); - } - }} - /> -
- ); + const queryClient = useQueryClient(); + const { + activeTenantId, + activeTenant, + isReady, + isLoading: isLoadingTenant, + } = useActiveTenant(); + const [isAdding, setIsAdding] = useState(false); + const [isEditing, setIsEditing] = useState(false); + const [editingProvider, setEditingProvider] = + useState(null); + const [deleteProviderTarget, setDeleteProviderTarget] = useState< + string | null + >(null); + + const [newProvider, setNewProvider] = useState(() => + createEmptyProviderForm(), + ); + + const [editForm, setEditForm] = useState(() => + createEmptyEditForm("google"), + ); + + const { data: providers, isLoading } = useQuery({ + queryKey: ["tenantSocialProviders", activeTenantId], + queryFn: async () => { + const { data } = await apiClient.get( + `/tenants/${activeTenantId}/social-providers`, + ); + return data; + }, + enabled: isReady, + }); + + const createMutation = useMutation({ + mutationFn: async (payload: SocialProviderCreateForm) => { + await apiClient.post( + `/tenants/${activeTenantId}/social-providers`, + payload, + ); + }, + onSuccess: () => { + toast.success("Social provider added"); + setIsAdding(false); + setNewProvider(createEmptyProviderForm()); + queryClient.invalidateQueries({ + queryKey: ["tenantSocialProviders", activeTenantId], + }); + }, + onError: (error: unknown) => { + toast.error(getApiErrorMessage(error, "Failed to add social provider")); + }, + }); + + // 3. Update Provider (PUT) + const updateMutation = useMutation({ + mutationFn: async ({ + provider, + payload, + }: { + provider: string; + payload: SocialProviderUpdateForm; + }) => { + const updatePayload: Partial = { + redirect_uri: payload.redirect_uri, + }; + + if (payload.client_id.trim()) { + updatePayload.client_id = payload.client_id.trim(); + } + if (payload.client_secret.trim()) { + updatePayload.client_secret = payload.client_secret.trim(); + } + if (payload.oidc_discovery_url.trim()) { + updatePayload.oidc_discovery_url = payload.oidc_discovery_url.trim(); + } + + await apiClient.put( + `/tenants/${activeTenantId}/social-providers/${provider}`, + updatePayload, + ); + }, + onSuccess: () => { + toast.success("Social provider updated"); + setIsEditing(false); + setEditingProvider(null); + queryClient.invalidateQueries({ + queryKey: ["tenantSocialProviders", activeTenantId], + }); + }, + onError: (error: unknown) => { + toast.error( + getApiErrorMessage(error, "Failed to update social provider"), + ); + }, + }); + + // 4. Delete Provider + const deleteMutation = useMutation({ + mutationFn: async (provider: string) => { + await apiClient.delete( + `/tenants/${activeTenantId}/social-providers/${provider}`, + ); + }, + onSuccess: () => { + toast.success("Social provider removed"); + queryClient.invalidateQueries({ + queryKey: ["tenantSocialProviders", activeTenantId], + }); + }, + onError: (error: unknown) => { + toast.error( + getApiErrorMessage(error, "Failed to remove social provider"), + ); + }, + }); + + // 5. Toggle Provider + const toggleMutation = useMutation({ + mutationFn: async ({ + provider, + is_active, + }: { + provider: string; + is_active: boolean; + }) => { + await apiClient.patch( + `/tenants/${activeTenantId}/social-providers/${provider}/toggle`, + { is_active }, + ); + }, + onSuccess: () => { + queryClient.invalidateQueries({ + queryKey: ["tenantSocialProviders", activeTenantId], + }); + }, + onError: (error: unknown) => { + toast.error(getApiErrorMessage(error, "Failed to toggle provider")); + }, + }); + + const openEditDialog = (p: TenantSocialProvider) => { + setEditingProvider(p); + setEditForm({ + client_id: "", + client_secret: "", + redirect_uri: p.redirect_uri || defaultRedirectUri(p.provider), + oidc_discovery_url: p.oidc_discovery_url || "", + }); + setIsEditing(true); + }; + + if (isLoadingTenant) { + return ( +
+ +
+ ); + } + + if (!isReady) + return
Select an organization.
; + + return ( +
+
+
+

+ Social Login (BYO-ID) +

+

+ {activeTenant?.type === "PLATFORM" + ? "Configure social login providers used on the public login page." + : "Configure custom OAuth/OIDC providers for your organization members."} +

+
+ + {/* Add Provider Dialog */} + + + + + + + Add Social Identity Provider + + Configure own credentials for Google, GitHub, or any OIDC + provider. + + +
+
+ + +

+ { + SOCIAL_PROVIDER_OPTIONS.find( + (option) => option.value === newProvider.provider, + )?.description + } +

+
+ + {newProvider.provider === "authengine" && ( +
+ + + setNewProvider({ + ...newProvider, + oidc_discovery_url: e.target.value, + }) + } + /> +

+ Base URL of the remote AuthEngine instance, for example + `https://auth.authengine.org`. A discovery URL ending in + `/.well-known/openid-configuration` is also accepted. +

+
+ )} + +
+ + + setNewProvider({ + ...newProvider, + client_id: e.target.value, + }) + } + /> +

+ Use the raw client ID issued by the provider. +

+
+ +
+ + + setNewProvider({ + ...newProvider, + client_secret: e.target.value, + }) + } + /> +

+ Stored encrypted at rest after save. +

+
+ +
+ + + setNewProvider({ + ...newProvider, + redirect_uri: e.target.value, + }) + } + /> +

+ This must match exactly the callback URI registered in the + provider console. For dashboard sign-in flows, use the app + callback like `{defaultRedirectUri(newProvider.provider)}`. +

+
+
+ + + +
+
+
+ + {/* Edit Provider Dialog */} + + + + + Edit{" "} + {editingProvider?.provider + ? editingProvider.provider.charAt(0).toUpperCase() + + editingProvider.provider.slice(1) + : ""}{" "} + Provider + + + Update the credentials and configuration for this social provider. + + +
+ {editingProvider?.provider === "authengine" && ( +
+ + + setEditForm({ + ...editForm, + oidc_discovery_url: e.target.value, + }) + } + /> +

+ Base URL of the remote AuthEngine instance or its discovery + document. +

+
+ )} + +
+ + + setEditForm({ ...editForm, client_id: e.target.value }) + } + /> +

+ The backend does not return raw client IDs for existing provider + configs. Enter a new value only if you want to rotate it. +

+
+ +
+ + + setEditForm({ ...editForm, client_secret: e.target.value }) + } + /> +

+ Leave blank to keep the existing secret unchanged. +

+
+ +
+ + + setEditForm({ ...editForm, redirect_uri: e.target.value }) + } + /> +
+
+ + + + +
+
+ +
+ {isLoading ? ( +
+ +
+ ) : providers?.length === 0 ? ( + + +
+ +
+
+

+ No custom providers configured +

+

+ Users will use the platform's default social login + credentials unless you configure your own here. +

+
+ +
+
+ ) : ( +
+ {providers?.map((p) => { + const Icon = PROVIDER_ICONS[p.provider] || Globe; + return ( + + +
+
+ +
+
+ + {PROVIDER_LABELS[p.provider] ?? p.provider} + + + ID: {p.id.slice(0, 8)}... + +
+
+
+
+ + {p.is_active ? "Active" : "Disabled"} + + + toggleMutation.mutate({ + provider: p.provider, + is_active: val, + }) + } + /> +
+ + + + + + openEditDialog(p)} + > + Edit + Configuration + + setDeleteProviderTarget(p.provider)} + > + Delete + + + +
+
+ +
+

+ Client ID +

+

+ Stored securely in AuthEngine and not shown again. Enter + a new client ID in Edit if you need to rotate it. +

+
+
+

+ Client Secret +

+

+ {p.client_secret_prefix || "Stored securely"} +

+
+
+

+ Redirect URI +

+

+ + {p.redirect_uri || defaultRedirectUri(p.provider)} + + +

+
+ {p.provider === "authengine" && p.oidc_discovery_url ? ( +
+

+ AuthEngine Base URL +

+

+ {p.oidc_discovery_url} +

+
+ ) : null} +
+
+ ); + })} +
+ )} +
+ +
+ +
+

+ Security Warning +

+

+ Ensure your redirect URIs are secured and match your provider's + console exactly. AuthEngine encrypts your Client IDs and Secrets at + rest using platform-level keys. +

+
+
+ + !open && setDeleteProviderTarget(null)} + title="Remove social provider?" + description={ + deleteProviderTarget + ? `The custom ${deleteProviderTarget} configuration will be deleted.` + : undefined + } + confirmLabel="Remove" + loading={deleteMutation.isPending} + onConfirm={() => { + if (deleteProviderTarget) { + deleteMutation.mutate(deleteProviderTarget); + setDeleteProviderTarget(null); + } + }} + /> +
+ ); }