diff --git a/messages/en-US.json b/messages/en-US.json index 43625ee73..b42aa4f10 100644 --- a/messages/en-US.json +++ b/messages/en-US.json @@ -1699,16 +1699,16 @@ "virtualApiKeysFilterUnassigned": "Unassigned", "myVirtualApiKeysTitle": "Your API Keys", "myVirtualApiKeysDescription": "View your identity key and any virtual API keys attributed to you in this organization", - "myVirtualApiKeysResourceTitle": "Your API Keys for This Resource", - "myVirtualApiKeysResourceDescription": "View your identity key and virtual API keys attributed to you that can access this resource", + "myVirtualApiKeysResourceTitle": "Your API Keys for {resourceName}", + "myVirtualApiKeysResourceDescription": "View your identity key and virtual API keys attributed to you that can access {resourceName}", "myVirtualApiKeysIdentityTitle": "Identity Key", "myVirtualApiKeysIdentityHeadline": "Your Personal API Key", "myVirtualApiKeysIdentityDescription": "Your personal key for this organization. It is unique to your account and used to identify you when calling AI Gateway resources.", - "myVirtualApiKeysIdentityResourceHeadline": "Your Personal API Key for This Resource", - "myVirtualApiKeysIdentityResourceDescription": "Your personal key for this organization. Use it to call this AI Gateway resource.", + "myVirtualApiKeysIdentityResourceHeadline": "Your Personal API Key for {resourceName}", + "myVirtualApiKeysIdentityResourceDescription": "Your personal key for this organization. Use it to call {resourceName}.", "myVirtualApiKeysManualTitle": "Attributed Keys", "myVirtualApiKeysManualDescription": "Manual virtual API keys an admin associated with your account", - "myVirtualApiKeysManualResourceDescription": "Manual virtual API keys associated with your account that can access this resource", + "myVirtualApiKeysManualResourceDescription": "Manual virtual API keys associated with your account that can access {resourceName}", "myVirtualApiKeysManualEmpty": "No attributed keys yet", "myVirtualApiKeysKindUser": "Identity", "myVirtualApiKeysKindManual": "Manual", diff --git a/server/routers/badger/verifySession.ts b/server/routers/badger/verifySession.ts index 525128fc8..1f479985b 100644 --- a/server/routers/badger/verifySession.ts +++ b/server/routers/badger/verifySession.ts @@ -263,28 +263,20 @@ export async function verifyResourceSession( ); if (action == "ACCEPT") { - // Public inference still requires a virtual API key; do not - // bypass that with an allow rule. - if (mode === "inference") { - logger.debug( - "Rule ACCEPT ignored for inference; continuing to virtual API key check" - ); - } else { - logger.debug("Resource allowed by rule"); + logger.debug("Resource allowed by rule"); - logRequestAudit( - { - action: true, - reason: 100, // allowed by rule - resourceId: resource.resourceId, - orgId: resource.orgId, - location: ipCC - }, - parsedBody.data - ); + logRequestAudit( + { + action: true, + reason: 100, // allowed by rule + resourceId: resource.resourceId, + orgId: resource.orgId, + location: ipCC + }, + parsedBody.data + ); - return allowed(res, undefined, dontStripSession); - } + return allowed(res, undefined, dontStripSession); } else if (action == "DROP") { logger.debug("Resource denied by rule"); @@ -425,7 +417,7 @@ export async function verifyResourceSession( parsedBody.data ); - return notAllowed(res, redirectPath); + return notAllowed(res, redirectPath, resource.orgId); } // check for access token in headers diff --git a/server/routers/resource/getResourceAuthInfo.ts b/server/routers/resource/getResourceAuthInfo.ts index f1328833d..667870959 100644 --- a/server/routers/resource/getResourceAuthInfo.ts +++ b/server/routers/resource/getResourceAuthInfo.ts @@ -42,6 +42,7 @@ export type GetResourceAuthInfoResponse = { skipToIdpId: number | null; orgId: string; postAuthPath: string | null; + mode: string; }; export async function getResourceAuthInfo( @@ -227,7 +228,8 @@ export async function getResourceAuthInfo( whitelist: effectivePolicy?.emailWhitelistEnabled ?? false, skipToIdpId: effectivePolicy?.idpId ?? resource.skipToIdpId, orgId: resource.orgId, - postAuthPath: resource.postAuthPath ?? null + postAuthPath: resource.postAuthPath ?? null, + mode: resource.mode }, success: true, error: false, diff --git a/server/routers/virtualApiKey/listMyVirtualApiKeys.ts b/server/routers/virtualApiKey/listMyVirtualApiKeys.ts index 244377ef1..5177a975a 100644 --- a/server/routers/virtualApiKey/listMyVirtualApiKeys.ts +++ b/server/routers/virtualApiKey/listMyVirtualApiKeys.ts @@ -123,10 +123,12 @@ export async function listMyVirtualApiKeys( } let resourceId: number | undefined; + let resourceName: string | undefined; if (resourceGuid) { const [resource] = await db .select({ - resourceId: resources.resourceId + resourceId: resources.resourceId, + name: resources.name }) .from(resources) .where( @@ -147,6 +149,7 @@ export async function listMyVirtualApiKeys( } resourceId = resource.resourceId; + resourceName = resource.name; } const { key: userKeyRow } = await getOrCreateUserVirtualApiKey({ @@ -203,7 +206,8 @@ export async function listMyVirtualApiKeys( userKey: toKeyWithResources(userKeyRow, resourceIdsByKey), manualKeys: manualRows.map((row) => toKeyWithResources(row, resourceIdsByKey) - ) + ), + ...(resourceName !== undefined ? { resourceName } : {}) }, success: true, error: false, diff --git a/server/routers/virtualApiKey/types.ts b/server/routers/virtualApiKey/types.ts index 73e475632..91bf17c0a 100644 --- a/server/routers/virtualApiKey/types.ts +++ b/server/routers/virtualApiKey/types.ts @@ -22,6 +22,7 @@ export type CreateOrEditVirtualApiKeyResponse = { export type ListMyVirtualApiKeysResponse = { userKey: VirtualApiKeyWithResources; manualKeys: VirtualApiKeyWithResources[]; + resourceName?: string | null; }; export type GetMyVirtualApiKeyResponse = { diff --git a/src/app/[orgId]/resource/[resourceGuid]/keys/page.tsx b/src/app/[orgId]/resource/[resourceGuid]/keys/page.tsx index 29e11eb92..dd673ac85 100644 --- a/src/app/[orgId]/resource/[resourceGuid]/keys/page.tsx +++ b/src/app/[orgId]/resource/[resourceGuid]/keys/page.tsx @@ -18,7 +18,7 @@ import { cache } from "react"; export async function generateMetadata(): Promise { const t = await getTranslations(); return { - title: t("myVirtualApiKeysResourceTitle") + title: t("myVirtualApiKeysTitle") }; } @@ -41,7 +41,9 @@ export default async function ResourceKeysPage(props: ResourceKeysPageProps) { const user = await getUser(); if (!user) { - redirect("/"); + redirect( + `/auth/resource/${encodeURIComponent(resourceGuid)}?redirect=${encodeURIComponent(`/${orgId}/resource/${resourceGuid}/keys`)}` + ); } const cookieHeader = await authCookieHeader(); @@ -111,11 +113,7 @@ export default async function ResourceKeysPage(props: ResourceKeysPageProps) { launcherMode showViewAsAdmin={isAdminOrOwner} > - + ); diff --git a/src/app/auth/resource/[resourceGuid]/page.tsx b/src/app/auth/resource/[resourceGuid]/page.tsx index dafc13f8f..3c4fd2691 100644 --- a/src/app/auth/resource/[resourceGuid]/page.tsx +++ b/src/app/auth/resource/[resourceGuid]/page.tsx @@ -71,6 +71,9 @@ export default async function ResourceAuthPage(props: { ); } + const isInference = authInfo.mode === "inference"; + const keysPath = `/${authInfo.orgId}/resource/${authInfo.resourceGuid}/keys`; + const hasLoginPageDomain = await isOrgSubscribed( authInfo.orgId, tierMatrix.loginPageDomain @@ -159,7 +162,9 @@ export default async function ResourceAuthPage(props: { if (user && !user.emailVerified && env.flags.emailVerificationRequired) { redirect( - `/auth/verify-email?redirect=/auth/resource/${authInfo.resourceGuid}` + `/auth/verify-email?redirect=${encodeURIComponent( + `/auth/resource/${authInfo.resourceGuid}` + )}` ); } @@ -193,6 +198,16 @@ export default async function ResourceAuthPage(props: { ); } + // Inference resources never establish a resource session on the inference + // host. Authenticated users retrieve their virtual API key on the dashboard. + if (isInference && user) { + redirect(keysPath); + } + + // After password/pincode/SSO, do not send the browser back to the + // inference host (session alone cannot pass Badger). Land on keys instead. + const postAuthRedirect = isInference ? keysPath : redirectUrl; + if (!hasAuth) { // no authentication so always go straight to the resource redirect(redirectUrl); @@ -277,7 +292,7 @@ export default async function ResourceAuthPage(props: { ); @@ -315,7 +330,7 @@ export default async function ResourceAuthPage(props: { name: authInfo.resourceName, id: authInfo.resourceId }} - redirect={redirectUrl} + redirect={postAuthRedirect} idps={loginIdps} orgId={build === "saas" ? authInfo.orgId : undefined} branding={ diff --git a/src/components/UserVirtualApiKeys.tsx b/src/components/UserVirtualApiKeys.tsx index d90241d0b..578f007b7 100644 --- a/src/components/UserVirtualApiKeys.tsx +++ b/src/components/UserVirtualApiKeys.tsx @@ -4,11 +4,9 @@ import { useState } from "react"; import { useTranslations } from "next-intl"; import { AxiosResponse } from "axios"; import moment from "moment"; -import { Badge } from "@app/components/ui/badge"; import { Button } from "@app/components/ui/button"; import CopyTextBox from "@app/components/CopyTextBox"; import CopyToClipboard from "@app/components/CopyToClipboard"; -import SettingsSectionTitle from "@app/components/SettingsSectionTitle"; import { SettingsContainer, SettingsFormCell, @@ -34,7 +32,6 @@ import { type UserVirtualApiKeysProps = { orgId: string; - resourceGuid?: string; initialData: ListMyVirtualApiKeysResponse; }; @@ -131,12 +128,12 @@ function IdentityKeyCenterpiece({ orgId, virtualApiKeyId, lastChars, - resourceGuid + resourceName }: { orgId: string; virtualApiKeyId: string; lastChars: string; - resourceGuid?: string; + resourceName?: string | null; }) { const t = useTranslations(); const preview = formatVirtualApiKeyPreview(virtualApiKeyId, lastChars); @@ -145,11 +142,11 @@ function IdentityKeyCenterpiece({ virtualApiKeyId ); const displayValue = credential ?? preview; - const headline = resourceGuid - ? t("myVirtualApiKeysIdentityResourceHeadline") + const headline = resourceName + ? t("myVirtualApiKeysIdentityResourceHeadline", { resourceName }) : t("myVirtualApiKeysIdentityHeadline"); - const description = resourceGuid - ? t("myVirtualApiKeysIdentityResourceDescription") + const description = resourceName + ? t("myVirtualApiKeysIdentityResourceDescription", { resourceName }) : t("myVirtualApiKeysIdentityDescription"); return ( @@ -220,17 +217,10 @@ function ManualKeyRow({ export default function UserVirtualApiKeys({ orgId, - resourceGuid, initialData }: UserVirtualApiKeysProps) { const t = useTranslations(); - - const title = resourceGuid - ? t("myVirtualApiKeysResourceTitle") - : t("myVirtualApiKeysTitle"); - const description = resourceGuid - ? t("myVirtualApiKeysResourceDescription") - : t("myVirtualApiKeysDescription"); + const resourceName = initialData.resourceName; return ( <> @@ -239,7 +229,7 @@ export default function UserVirtualApiKeys({ orgId={orgId} virtualApiKeyId={initialData.userKey.virtualApiKeyId} lastChars={initialData.userKey.lastChars} - resourceGuid={resourceGuid} + resourceName={resourceName} /> {initialData.manualKeys.length > 0 ? ( @@ -249,9 +239,10 @@ export default function UserVirtualApiKeys({ {t("myVirtualApiKeysManualTitle")} - {resourceGuid + {resourceName ? t( - "myVirtualApiKeysManualResourceDescription" + "myVirtualApiKeysManualResourceDescription", + { resourceName } ) : t("myVirtualApiKeysManualDescription")} diff --git a/src/components/resource-launcher/LauncherInferenceApiKeysSection.tsx b/src/components/resource-launcher/LauncherInferenceApiKeysSection.tsx index 2434ed575..9624a3a43 100644 --- a/src/components/resource-launcher/LauncherInferenceApiKeysSection.tsx +++ b/src/components/resource-launcher/LauncherInferenceApiKeysSection.tsx @@ -202,7 +202,12 @@ export function LauncherInferenceApiKeysSection({ {t( - "myVirtualApiKeysManualResourceDescription" + "myVirtualApiKeysManualResourceDescription", + { + resourceName: + data.resourceName ?? + t("resource") + } )}