| 1 |
/** |
| 2 |
* Replacing the two secrets CryptX keeps. |
| 3 |
* |
| 4 |
* Sits on the Advanced tab for the same reason the preview sits on Appearance: |
| 5 |
* it is an action, not a setting, and it belongs where somebody would look for |
| 6 |
* it rather than in a row of switches. |
| 7 |
* |
| 8 |
* The text carries the one thing that decides whether pressing it is safe, and |
| 9 |
* it is not obvious: links already published go on working for ever, because |
| 10 |
* the key travels inside them. Pictures do not -- their token is opened on the |
| 11 |
* server -- so the replaced secret is kept for a while and the screen says |
| 12 |
* until when. |
| 13 |
*/ |
| 14 |
|
| 15 |
import { useState } from '@wordpress/element'; |
| 16 |
import apiFetch from '@wordpress/api-fetch'; |
| 17 |
import { |
| 18 |
Button, |
| 19 |
Card, |
| 20 |
CardBody, |
| 21 |
CardHeader, |
| 22 |
Notice, |
| 23 |
} from '@wordpress/components'; |
| 24 |
import { __, sprintf } from '@wordpress/i18n'; |
| 25 |
|
| 26 |
export default function Secrets( { rotatedAt } ) { |
| 27 |
const [ isBusy, setBusy ] = useState( false ); |
| 28 |
const [ result, setResult ] = useState( null ); |
| 29 |
// The date arrives ready-formatted from the server. Formatting it here |
| 30 |
// would use the reader's time zone and language instead of the site's, and |
| 31 |
// the success message right above it is formatted with wp_date() -- two |
| 32 |
// dates in the same card disagreeing by a day, on a card whose job is to |
| 33 |
// make an unexpected date stand out. |
| 34 |
const [ lastRotated, setLastRotated ] = useState( rotatedAt || '' ); |
| 35 |
const [ confirming, setConfirming ] = useState( false ); |
| 36 |
|
| 37 |
const rotate = () => { |
| 38 |
setBusy( true ); |
| 39 |
setConfirming( false ); |
| 40 |
|
| 41 |
apiFetch( { path: '/cryptx/v1/secrets/rotate', method: 'POST' } ) |
| 42 |
.then( ( response ) => { |
| 43 |
setResult( { status: 'success', text: response.message } ); |
| 44 |
setLastRotated( response.secretsRotatedAt || '' ); |
| 45 |
} ) |
| 46 |
.catch( ( error ) => |
| 47 |
setResult( { |
| 48 |
status: 'error', |
| 49 |
text: |
| 50 |
error?.message || |
| 51 |
__( 'The secrets could not be replaced.', 'cryptx' ), |
| 52 |
} ) |
| 53 |
) |
| 54 |
.finally( () => setBusy( false ) ); |
| 55 |
}; |
| 56 |
|
| 57 |
return ( |
| 58 |
<Card className="cryptx-section"> |
| 59 |
<CardHeader> |
| 60 |
<h2 className="cryptx-section__title"> |
| 61 |
{ __( 'Encryption secrets', 'cryptx' ) } |
| 62 |
</h2> |
| 63 |
</CardHeader> |
| 64 |
<CardBody> |
| 65 |
<p> |
| 66 |
{ __( |
| 67 |
"CryptX keeps two secrets. One is used to encrypt the links and travels inside each of them, so that a visitor's browser can open it. The other never leaves your server and stands in for the address when one is drawn as a picture.", |
| 68 |
'cryptx' |
| 69 |
) } |
| 70 |
</p> |
| 71 |
<p> |
| 72 |
{ __( |
| 73 |
'Replacing them is safe and rarely necessary — after restoring a backup that may have been seen by someone else, for instance. Links that are already published keep working, because each one carries the key it was made with. Pictures are opened on the server, so the old secret is kept for 30 days and the pictures in pages that are still cached go on working until then. Only one old secret is kept, so replacing them a second time inside those 30 days ends the grace period for the first — which is also how you cut it short deliberately, if the old secret needs to stop working now.', |
| 74 |
'cryptx' |
| 75 |
) } |
| 76 |
</p> |
| 77 |
|
| 78 |
{ lastRotated && ( |
| 79 |
<p className="cryptx-secrets__last"> |
| 80 |
{ sprintf( |
| 81 |
/* translators: %s: a date */ |
| 82 |
__( 'Last replaced on %s.', 'cryptx' ), |
| 83 |
lastRotated |
| 84 |
) } |
| 85 |
</p> |
| 86 |
) } |
| 87 |
|
| 88 |
{ result && ( |
| 89 |
<Notice |
| 90 |
status={ result.status } |
| 91 |
isDismissible={ false } |
| 92 |
className="cryptx-secrets__notice" |
| 93 |
> |
| 94 |
{ result.text } |
| 95 |
</Notice> |
| 96 |
) } |
| 97 |
|
| 98 |
{ confirming ? ( |
| 99 |
<div className="cryptx-secrets__confirm"> |
| 100 |
<p>{ __( 'Replace both secrets now?', 'cryptx' ) }</p> |
| 101 |
<Button |
| 102 |
variant="primary" |
| 103 |
isDestructive |
| 104 |
onClick={ rotate } |
| 105 |
isBusy={ isBusy } |
| 106 |
disabled={ isBusy } |
| 107 |
__next40pxDefaultSize |
| 108 |
> |
| 109 |
{ __( 'Yes, replace them', 'cryptx' ) } |
| 110 |
</Button> |
| 111 |
<Button |
| 112 |
variant="tertiary" |
| 113 |
onClick={ () => setConfirming( false ) } |
| 114 |
disabled={ isBusy } |
| 115 |
__next40pxDefaultSize |
| 116 |
> |
| 117 |
{ __( 'Cancel', 'cryptx' ) } |
| 118 |
</Button> |
| 119 |
</div> |
| 120 |
) : ( |
| 121 |
<Button |
| 122 |
variant="secondary" |
| 123 |
onClick={ () => setConfirming( true ) } |
| 124 |
disabled={ isBusy } |
| 125 |
__next40pxDefaultSize |
| 126 |
> |
| 127 |
{ __( 'Create new secrets', 'cryptx' ) } |
| 128 |
</Button> |
| 129 |
) } |
| 130 |
</CardBody> |
| 131 |
</Card> |
| 132 |
); |
| 133 |
} |
| 134 |
|