diff --git a/static/app/views/settings/organizationDeveloperSettings/sentryApplicationDetails.spec.tsx b/static/app/views/settings/organizationDeveloperSettings/sentryApplicationDetails.spec.tsx index a8362b5eedacff..d38cc459765995 100644 --- a/static/app/views/settings/organizationDeveloperSettings/sentryApplicationDetails.spec.tsx +++ b/static/app/views/settings/organizationDeveloperSettings/sentryApplicationDetails.spec.tsx @@ -4,7 +4,13 @@ import {RouterFixture} from 'sentry-fixture/routerFixture'; import {SentryAppFixture} from 'sentry-fixture/sentryApp'; import {SentryAppTokenFixture} from 'sentry-fixture/sentryAppToken'; -import {render, screen, userEvent, waitFor} from 'sentry-test/reactTestingLibrary'; +import { + render, + renderGlobalModal, + screen, + userEvent, + waitFor, +} from 'sentry-test/reactTestingLibrary'; import selectEvent from 'sentry-test/selectEvent'; import SentryApplicationDetails from 'sentry/views/settings/organizationDeveloperSettings/sentryApplicationDetails'; @@ -586,5 +592,49 @@ describe('Sentry Application Details', function () { ) ).toBeInTheDocument(); }); + + it('handles client secret rotation', async function () { + sentryApp = SentryAppFixture(); + sentryApp.clientSecret = null; + + MockApiClient.addMockResponse({ + url: `/sentry-apps/${sentryApp.slug}/`, + body: sentryApp, + }); + const rotateSecretApiCall = MockApiClient.addMockResponse({ + method: 'POST', + url: `/sentry-apps/${sentryApp.slug}/rotate-secret/`, + body: { + clientSecret: 'newSecret!', + }, + }); + + render( + + ); + renderGlobalModal(); + + expect(screen.getByText('hidden')).toBeInTheDocument(); + expect( + screen.getByRole('button', {name: 'Rotate client secret'}) + ).toBeInTheDocument(); + await userEvent.click(screen.getByRole('button', {name: 'Rotate client secret'})); + + expect( + screen.getByText('This will be the only time your client secret is visible!') + ).toBeInTheDocument(); + expect(screen.getByText('Rotated Client Secret')).toBeInTheDocument(); + expect(screen.getByText('Your client secret is:')).toBeInTheDocument(); + expect(screen.getByText('newSecret!')).toBeInTheDocument(); + + expect(rotateSecretApiCall).toHaveBeenCalledTimes(1); + }); }); }); diff --git a/static/app/views/settings/organizationDeveloperSettings/sentryApplicationDetails.tsx b/static/app/views/settings/organizationDeveloperSettings/sentryApplicationDetails.tsx index e65d1cb8dede55..6f4fef28f867b8 100644 --- a/static/app/views/settings/organizationDeveloperSettings/sentryApplicationDetails.tsx +++ b/static/app/views/settings/organizationDeveloperSettings/sentryApplicationDetails.tsx @@ -7,10 +7,12 @@ import {Observer} from 'mobx-react'; import scrollToElement from 'scroll-to-element'; import {addErrorMessage, addSuccessMessage} from 'sentry/actionCreators/indicator'; +import {openModal} from 'sentry/actionCreators/modal'; import { addSentryAppToken, removeSentryAppToken, } from 'sentry/actionCreators/sentryAppTokens'; +import {Alert} from 'sentry/components/alert'; import Avatar from 'sentry/components/avatar'; import type {Model} from 'sentry/components/avatarChooser'; import AvatarChooser from 'sentry/components/avatarChooser'; @@ -315,6 +317,33 @@ class SentryApplicationDetails extends DeprecatedAsyncView { return tokensToDisplay; }; + rotateClientSecret = async () => { + try { + const rotateResponse = await this.api.requestPromise( + `/sentry-apps/${this.props.params.appSlug}/rotate-secret/`, + { + method: 'POST', + } + ); + openModal(({Body, Header}) => ( + +
{t('Rotated Client Secret')}
+ + + {t('This will be the only time your client secret is visible!')} + +

+ {t('Your client secret is:')} + {rotateResponse.clientSecret} +

+ +
+ )); + } catch { + addErrorMessage(t('Error rotating secret')); + } + }; + onFieldChange = (name: string, value: FieldValue): void => { if (name === 'webhookUrl' && !value && this.isInternal) { // if no webhook, then set isAlertable to false @@ -488,7 +517,12 @@ class SentryApplicationDetails extends DeprecatedAsyncView { )} )} - + {({value, id}) => value ? ( { ) : ( - hidden + + {t('hidden')} + {this.hasTokenAccess ? ( + + ) : undefined} + ) } @@ -542,3 +583,15 @@ const AvatarPreviewText = styled('span')` grid-area: 2 / 2 / 3 / 3; padding-left: ${space(2)}; `; + +const HiddenSecret = styled('span')` + width: 100px; + font-style: italic; +`; + +const ClientSecret = styled('div')` + display: flex; + justify-content: right; + align-items: center; + margin-right: 0; +`;