import {X} from 'lucide-react' import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query' import {useEffect, useState} from 'react' import {describeUserPoolClient, updateUserPoolClientOAuth} from '@/api/cloudProxyClient' import {useAccountId} from '@/lib/accountStore' import type {UserPoolClientSettings, UserPoolOAuthFlow} from '@/types/resource' type Props = { poolId: string clientId: string runtimeReachable: boolean onClose: () => void } const FLOWS: {flow: UserPoolOAuthFlow; label: string; hint: string}[] = [ {flow: 'code', label: 'Authorization code', hint: 'Recommended for web and mobile apps, with PKCE.'}, {flow: 'implicit', label: 'Implicit', hint: 'Legacy: tokens come back in the URL.'}, {flow: 'client_credentials', label: 'Client credentials', hint: 'Machine-to-machine. Needs a client secret.'}, ] const STANDARD_SCOPES = ['openid', 'email', 'phone', 'profile', 'aws.cognito.signin.user.admin'] const COGNITO = 'COGNITO' type Draft = {callbackUrls: string; logoutUrls: string; oauthFlows: UserPoolOAuthFlow[]; oauthScopes: string[]; identityProviders: string[]} export function CognitoAppClientSettings({poolId, clientId, runtimeReachable, onClose}: Props) { const accountId = useAccountId() const queryClient = useQueryClient() const clientKey = ['cognito-client', accountId, poolId, clientId] const [draft, setDraft] = useState(null) const [notice, setNotice] = useState('') const clientQuery = useQuery({ queryKey: clientKey, queryFn: ({signal}) => describeUserPoolClient('aws', poolId, clientId, signal), enabled: runtimeReachable, }) const client = clientQuery.data useEffect(() => { if (client) setDraft(draftFrom(client)) }, [client]) const saveMutation = useMutation({ mutationFn: (value: Draft) => updateUserPoolClientOAuth('aws', poolId, clientId, { callbackUrls: lines(value.callbackUrls), logoutUrls: lines(value.logoutUrls), oauthFlows: value.oauthFlows, oauthScopes: value.oauthScopes, identityProviders: value.identityProviders, }), onSuccess: (saved) => { queryClient.setQueryData(clientKey, saved) setNotice(saved.oauthFlows.includes('code') && saved.identityProviders.includes(COGNITO) ? 'Saved. Apps using this client can now send users to the hosted login page.' : 'Saved.') }, }) const disabled = !runtimeReachable || saveMutation.isPending const scopes = draft ? [...STANDARD_SCOPES, ...draft.oauthScopes.filter((scope) => !STANDARD_SCOPES.includes(scope))] : STANDARD_SCOPES const otherProviders = draft?.identityProviders.filter((provider) => provider !== COGNITO) ?? [] const changed = Boolean(client && draft && JSON.stringify(normalized(draft)) !== JSON.stringify(normalized(draftFrom(client)))) const update = (patch: Partial) => { setNotice(''); setDraft((current) => current && {...current, ...patch}) } const toggle = (list: T[], value: T, on: boolean) => on ? [...list.filter((entry) => entry !== value), value] : list.filter((entry) => entry !== value) const error = clientQuery.error ?? saveMutation.error return

App client

{client?.clientName || clientId}
{clientQuery.isLoading &&

Loading app client

} {client && draft &&
{ event.preventDefault(); saveMutation.mutate(draft) }}>
Client ID
{client.clientId}
Client secret
{client.hasSecret ? 'Yes' : 'No (public client)'}
Sign-in flows
{client.explicitAuthFlows.length ? client.explicitAuthFlows.map((flow) => {flow.replace(/^ALLOW_/, '')}) : '—'}

Hosted sign-in

{otherProviders.length > 0 &&

Also signs in with: {otherProviders.map((provider) => {provider})}

}

OAuth flows

{FLOWS.map(({flow, label, hint}) => ( ))}

Scopes

{scopes.map((scope) => ( ))}