mirror of
https://github.com/dograh-hq/dograh.git
synced 2026-07-25 12:01:04 +02:00
feat: add warning with missing signature secret
This commit is contained in:
parent
f644933e28
commit
e1e9782c48
10 changed files with 122 additions and 20 deletions
|
|
@ -103,6 +103,30 @@ class TelephonyConfigurationClient(BaseDBClient):
|
|||
)
|
||||
return int(result.scalar() or 0)
|
||||
|
||||
async def count_vonage_configs_missing_signature_secret(
|
||||
self, organization_id: int
|
||||
) -> int:
|
||||
"""Count Vonage configs in this org with no signature_secret."""
|
||||
async with self.async_session() as session:
|
||||
result = await session.execute(
|
||||
select(func.count(TelephonyConfigurationModel.id)).where(
|
||||
TelephonyConfigurationModel.organization_id == organization_id,
|
||||
TelephonyConfigurationModel.provider == "vonage",
|
||||
(
|
||||
TelephonyConfigurationModel.credentials.op("->>")(
|
||||
"signature_secret"
|
||||
).is_(None)
|
||||
)
|
||||
| (
|
||||
TelephonyConfigurationModel.credentials.op("->>")(
|
||||
"signature_secret"
|
||||
)
|
||||
== ""
|
||||
),
|
||||
)
|
||||
)
|
||||
return int(result.scalar() or 0)
|
||||
|
||||
async def list_all_telephony_configurations_by_provider(
|
||||
self, provider: str
|
||||
) -> List[TelephonyConfigurationModel]:
|
||||
|
|
|
|||
|
|
@ -145,6 +145,7 @@ class TelephonyConfigWarningsResponse(BaseModel):
|
|||
"""
|
||||
|
||||
telnyx_missing_webhook_public_key_count: int
|
||||
vonage_missing_signature_secret_count: int
|
||||
|
||||
|
||||
@router.get("/context", response_model=OrganizationContextResponse)
|
||||
|
|
@ -200,8 +201,7 @@ async def get_telephony_providers_metadata(user: UserModel = Depends(get_user)):
|
|||
async def get_telephony_config_warnings(user: UserModel = Depends(get_user)):
|
||||
"""Return aggregated warning counts for the current org's telephony configs.
|
||||
|
||||
Today this surfaces only Telnyx configs missing ``webhook_public_key``;
|
||||
additional warning types should be added as new fields on the response.
|
||||
Surfaces provider configs missing webhook-verification credentials.
|
||||
"""
|
||||
if not user.selected_organization_id:
|
||||
raise HTTPException(status_code=400, detail="No organization selected")
|
||||
|
|
@ -209,8 +209,12 @@ async def get_telephony_config_warnings(user: UserModel = Depends(get_user)):
|
|||
telnyx_missing = await db_client.count_telnyx_configs_missing_webhook_public_key(
|
||||
user.selected_organization_id
|
||||
)
|
||||
vonage_missing = await db_client.count_vonage_configs_missing_signature_secret(
|
||||
user.selected_organization_id
|
||||
)
|
||||
return TelephonyConfigWarningsResponse(
|
||||
telnyx_missing_webhook_public_key_count=telnyx_missing,
|
||||
vonage_missing_signature_secret_count=vonage_missing,
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -13,8 +13,8 @@ class VonageConfigurationRequest(BaseModel):
|
|||
api_secret: str = Field(..., description="Vonage API Secret")
|
||||
application_id: str = Field(..., description="Vonage Application ID")
|
||||
private_key: str = Field(..., description="Private key for JWT generation")
|
||||
signature_secret: str = Field(
|
||||
...,
|
||||
signature_secret: Optional[str] = Field(
|
||||
None,
|
||||
description="Vonage signature secret used to verify signed webhooks",
|
||||
)
|
||||
from_numbers: List[str] = Field(
|
||||
|
|
|
|||
|
|
@ -442,7 +442,7 @@ class VonageProvider(TelephonyProvider):
|
|||
account_id=claims.get("api_key") or webhook_data.get("account_id"),
|
||||
from_country=None,
|
||||
to_country=None,
|
||||
raw_data={**webhook_data, "signed_claims": claims},
|
||||
raw_data=webhook_data,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
|
|
@ -630,13 +630,14 @@ class VonageProvider(TelephonyProvider):
|
|||
capabilities = app_data.get("capabilities") or {}
|
||||
voice = capabilities.get("voice") or {}
|
||||
webhooks = voice.get("webhooks") or {}
|
||||
backend_endpoint, _ = await get_backend_endpoints()
|
||||
|
||||
webhooks["answer_url"] = {
|
||||
"address": webhook_url,
|
||||
"http_method": "POST",
|
||||
}
|
||||
webhooks["event_url"] = {
|
||||
"address": f"{webhook_url.rsplit('/inbound/run', 1)[0]}/vonage/events",
|
||||
"address": f"{backend_endpoint}/api/v1/telephony/vonage/events",
|
||||
"http_method": "POST",
|
||||
}
|
||||
voice["webhooks"] = webhooks
|
||||
|
|
|
|||
|
|
@ -53,6 +53,7 @@ export default function TelephonyConfigurationsPage() {
|
|||
const { user, getAccessToken, loading: authLoading } = useAuth();
|
||||
const {
|
||||
telnyxMissingWebhookPublicKeyCount,
|
||||
vonageMissingSignatureSecretCount,
|
||||
refresh: refreshWarnings,
|
||||
} = useTelephonyConfigWarnings();
|
||||
const [items, setItems] = useState<TelephonyConfigurationListItem[]>([]);
|
||||
|
|
@ -82,9 +83,9 @@ export default function TelephonyConfigurationsPage() {
|
|||
}
|
||||
}, [authLoading, user, getAccessToken]);
|
||||
|
||||
// After a save (create/update), the backing config may have flipped between
|
||||
// missing/present webhook_public_key — refresh the cached warning state so
|
||||
// the page banner and nav badge update without a manual reload.
|
||||
// After a save (create/update), webhook-verification warning state may have
|
||||
// changed — refresh the cached warning state so the page banner and nav badge
|
||||
// update without a manual reload.
|
||||
const onSaved = useCallback(async () => {
|
||||
await fetchItems();
|
||||
await refreshWarnings();
|
||||
|
|
@ -194,6 +195,26 @@ export default function TelephonyConfigurationsPage() {
|
|||
</div>
|
||||
)}
|
||||
|
||||
{vonageMissingSignatureSecretCount > 0 && (
|
||||
<div className="mb-6 rounded-md border border-amber-300 bg-amber-50 p-4 text-amber-900 dark:border-amber-800 dark:bg-amber-950 dark:text-amber-200">
|
||||
<div className="flex items-start gap-3">
|
||||
<AlertTriangle className="h-5 w-5 shrink-0 mt-0.5" />
|
||||
<div className="space-y-1 text-sm">
|
||||
<p className="font-medium">Signature secret not configured</p>
|
||||
<p>
|
||||
{vonageMissingSignatureSecretCount === 1
|
||||
? "1 Vonage configuration is"
|
||||
: `${vonageMissingSignatureSecretCount} Vonage configurations are`}{" "}
|
||||
missing a signature secret. Without it, Vonage signed webhooks
|
||||
are rejected, so inbound calls and call status updates will not
|
||||
work. Copy the signature secret from your Vonage account and
|
||||
paste it into the affected Vonage configuration below.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{loading ? (
|
||||
<div className="grid gap-3">
|
||||
<Skeleton className="h-24 w-full" />
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -5323,6 +5323,10 @@ export type TelephonyConfigWarningsResponse = {
|
|||
* Telnyx Missing Webhook Public Key Count
|
||||
*/
|
||||
telnyx_missing_webhook_public_key_count: number;
|
||||
/**
|
||||
* Vonage Missing Signature Secret Count
|
||||
*/
|
||||
vonage_missing_signature_secret_count: number;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -6427,6 +6431,12 @@ export type VonageConfigurationRequest = {
|
|||
* Private key for JWT generation
|
||||
*/
|
||||
private_key: string;
|
||||
/**
|
||||
* Signature Secret
|
||||
*
|
||||
* Vonage signature secret used to verify signed webhooks
|
||||
*/
|
||||
signature_secret?: string | null;
|
||||
/**
|
||||
* From Numbers
|
||||
*
|
||||
|
|
@ -6461,6 +6471,10 @@ export type VonageConfigurationResponse = {
|
|||
* Private Key
|
||||
*/
|
||||
private_key: string;
|
||||
/**
|
||||
* Signature Secret
|
||||
*/
|
||||
signature_secret?: string | null;
|
||||
/**
|
||||
* From Numbers
|
||||
*/
|
||||
|
|
@ -7553,6 +7567,27 @@ export type HandleVonageEventsApiV1TelephonyVonageEventsWorkflowRunIdPostRespons
|
|||
200: unknown;
|
||||
};
|
||||
|
||||
export type HandleVonageEventsWithoutRunApiV1TelephonyVonageEventsPostData = {
|
||||
body?: never;
|
||||
path?: never;
|
||||
query?: never;
|
||||
url: '/api/v1/telephony/vonage/events';
|
||||
};
|
||||
|
||||
export type HandleVonageEventsWithoutRunApiV1TelephonyVonageEventsPostErrors = {
|
||||
/**
|
||||
* Not found
|
||||
*/
|
||||
404: unknown;
|
||||
};
|
||||
|
||||
export type HandleVonageEventsWithoutRunApiV1TelephonyVonageEventsPostResponses = {
|
||||
/**
|
||||
* Successful Response
|
||||
*/
|
||||
200: unknown;
|
||||
};
|
||||
|
||||
export type ImpersonateApiV1SuperuserImpersonatePostData = {
|
||||
body: ImpersonateRequest;
|
||||
headers?: {
|
||||
|
|
|
|||
|
|
@ -167,8 +167,13 @@ export function AppSidebar() {
|
|||
const { provider, getSelectedTeam, logout, user } = useAuth();
|
||||
const { config } = useAppConfig();
|
||||
const { openHireExpert } = useLeadForms();
|
||||
const { telnyxMissingWebhookPublicKeyCount } = useTelephonyConfigWarnings();
|
||||
const hasTelephonyWarning = telnyxMissingWebhookPublicKeyCount > 0;
|
||||
const {
|
||||
telnyxMissingWebhookPublicKeyCount,
|
||||
vonageMissingSignatureSecretCount,
|
||||
} = useTelephonyConfigWarnings();
|
||||
const hasTelephonyWarning =
|
||||
telnyxMissingWebhookPublicKeyCount > 0 ||
|
||||
vonageMissingSignatureSecretCount > 0;
|
||||
const isCollapsed = !isMobile && state === "collapsed";
|
||||
|
||||
// Get selected team for Stack auth (cast to Team type from Stack)
|
||||
|
|
|
|||
|
|
@ -7,12 +7,14 @@ import { useAuth } from '@/lib/auth';
|
|||
|
||||
interface TelephonyConfigWarningsContextType {
|
||||
telnyxMissingWebhookPublicKeyCount: number;
|
||||
vonageMissingSignatureSecretCount: number;
|
||||
refresh: () => Promise<void>;
|
||||
loading: boolean;
|
||||
}
|
||||
|
||||
const TelephonyConfigWarningsContext = createContext<TelephonyConfigWarningsContextType>({
|
||||
telnyxMissingWebhookPublicKeyCount: 0,
|
||||
vonageMissingSignatureSecretCount: 0,
|
||||
refresh: async () => { },
|
||||
loading: false,
|
||||
});
|
||||
|
|
@ -23,7 +25,8 @@ const TelephonyConfigWarningsContext = createContext<TelephonyConfigWarningsCont
|
|||
// change. Page-level callers invalidate via refresh() after a save.
|
||||
export function TelephonyConfigWarningsProvider({ children }: { children: ReactNode }) {
|
||||
const auth = useAuth();
|
||||
const [count, setCount] = useState(0);
|
||||
const [telnyxCount, setTelnyxCount] = useState(0);
|
||||
const [vonageCount, setVonageCount] = useState(0);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const hasFetched = useRef(false);
|
||||
|
||||
|
|
@ -31,9 +34,11 @@ export function TelephonyConfigWarningsProvider({ children }: { children: ReactN
|
|||
setLoading(true);
|
||||
try {
|
||||
const res = await getTelephonyConfigWarningsApiV1OrganizationsTelephonyConfigWarningsGet();
|
||||
setCount(res.data?.telnyx_missing_webhook_public_key_count ?? 0);
|
||||
setTelnyxCount(res.data?.telnyx_missing_webhook_public_key_count ?? 0);
|
||||
setVonageCount(res.data?.vonage_missing_signature_secret_count ?? 0);
|
||||
} catch {
|
||||
setCount(0);
|
||||
setTelnyxCount(0);
|
||||
setVonageCount(0);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
|
|
@ -53,7 +58,8 @@ export function TelephonyConfigWarningsProvider({ children }: { children: ReactN
|
|||
return (
|
||||
<TelephonyConfigWarningsContext.Provider
|
||||
value={{
|
||||
telnyxMissingWebhookPublicKeyCount: count,
|
||||
telnyxMissingWebhookPublicKeyCount: telnyxCount,
|
||||
vonageMissingSignatureSecretCount: vonageCount,
|
||||
refresh,
|
||||
loading,
|
||||
}}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue