@@ -7,10 +7,12 @@ import {Observer} from 'mobx-react';
7
7
import scrollToElement from 'scroll-to-element' ;
8
8
9
9
import { addErrorMessage , addSuccessMessage } from 'sentry/actionCreators/indicator' ;
10
+ import { openModal } from 'sentry/actionCreators/modal' ;
10
11
import {
11
12
addSentryAppToken ,
12
13
removeSentryAppToken ,
13
14
} from 'sentry/actionCreators/sentryAppTokens' ;
15
+ import { Alert } from 'sentry/components/alert' ;
14
16
import Avatar from 'sentry/components/avatar' ;
15
17
import type { Model } from 'sentry/components/avatarChooser' ;
16
18
import AvatarChooser from 'sentry/components/avatarChooser' ;
@@ -315,6 +317,33 @@ class SentryApplicationDetails extends DeprecatedAsyncView<Props, State> {
315
317
return tokensToDisplay ;
316
318
} ;
317
319
320
+ rotateClientSecret = async ( ) => {
321
+ try {
322
+ const rotateResponse = await this . api . requestPromise (
323
+ `/sentry-apps/${ this . props . params . appSlug } /rotate-secret/` ,
324
+ {
325
+ method : 'POST' ,
326
+ }
327
+ ) ;
328
+ openModal ( ( { Body, Header} ) => (
329
+ < Fragment >
330
+ < Header > { t ( 'Rotated Client Secret' ) } </ Header >
331
+ < Body >
332
+ < Alert type = "info" showIcon >
333
+ { t ( 'This will be the only time your client secret is visible!' ) }
334
+ </ Alert >
335
+ < p >
336
+ { t ( 'Your client secret is:' ) }
337
+ < code > { rotateResponse . clientSecret } </ code >
338
+ </ p >
339
+ </ Body >
340
+ </ Fragment >
341
+ ) ) ;
342
+ } catch {
343
+ addErrorMessage ( t ( 'Error rotating secret' ) ) ;
344
+ }
345
+ } ;
346
+
318
347
onFieldChange = ( name : string , value : FieldValue ) : void => {
319
348
if ( name === 'webhookUrl' && ! value && this . isInternal ) {
320
349
// if no webhook, then set isAlertable to false
@@ -488,7 +517,12 @@ class SentryApplicationDetails extends DeprecatedAsyncView<Props, State> {
488
517
) }
489
518
</ FormField >
490
519
) }
491
- < FormField name = "clientSecret" label = "Client Secret" >
520
+ < FormField
521
+ name = "clientSecret"
522
+ label = "Client Secret"
523
+ help = { t ( `Your secret is only available briefly after integration creation. Make
524
+ sure to save this value!` ) }
525
+ >
492
526
{ ( { value, id} ) =>
493
527
value ? (
494
528
< Tooltip
@@ -504,7 +538,14 @@ class SentryApplicationDetails extends DeprecatedAsyncView<Props, State> {
504
538
</ TextCopyInput >
505
539
</ Tooltip >
506
540
) : (
507
- < em > hidden</ em >
541
+ < ClientSecret >
542
+ < HiddenSecret > { t ( 'hidden' ) } </ HiddenSecret >
543
+ { this . hasTokenAccess ? (
544
+ < Button onClick = { this . rotateClientSecret } priority = "danger" >
545
+ Rotate client secret
546
+ </ Button >
547
+ ) : undefined }
548
+ </ ClientSecret >
508
549
)
509
550
}
510
551
</ FormField >
@@ -542,3 +583,15 @@ const AvatarPreviewText = styled('span')`
542
583
grid-area: 2 / 2 / 3 / 3;
543
584
padding-left: ${ space ( 2 ) } ;
544
585
` ;
586
+
587
+ const HiddenSecret = styled ( 'span' ) `
588
+ width: 100px;
589
+ font-style: italic;
590
+ ` ;
591
+
592
+ const ClientSecret = styled ( 'div' ) `
593
+ display: flex;
594
+ justify-content: right;
595
+ align-items: center;
596
+ margin-right: 0;
597
+ ` ;
0 commit comments