Skip to content

Commit b4de452

Browse files
committed
feat(gdpr): show deletion token once, allow delete via recovery token
1 parent 548e500 commit b4de452

File tree

2 files changed

+61
-0
lines changed

2 files changed

+61
-0
lines changed

src/static/js/pad.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,38 @@ const getParams = () => {
176176

177177
const getUrlVars = () => new URL(window.location.href).searchParams;
178178

179+
// Surfaces the one-time pad deletion token when the server sends it in
180+
// clientVars (creator session, first CLIENT_READY). The token is cleared from
181+
// clientVars on acknowledgement so it is not re-exposed to later code paths.
182+
const showDeletionTokenModalIfPresent = () => {
183+
const token: string | null = (window as any).clientVars?.padDeletionToken;
184+
if (!token) return;
185+
const $modal = $('#deletiontoken-modal');
186+
const $input = $('#deletiontoken-value');
187+
const $copy = $('#deletiontoken-copy');
188+
const $ack = $('#deletiontoken-ack');
189+
if ($modal.length === 0) return;
190+
191+
$input.val(token);
192+
$modal.prop('hidden', false).addClass('popup-show');
193+
194+
$copy.off('click.gdpr').on('click.gdpr', async () => {
195+
try {
196+
await navigator.clipboard.writeText(token);
197+
} catch (_e) {
198+
($input[0] as HTMLInputElement).select();
199+
document.execCommand('copy');
200+
}
201+
$copy.text(html10n.get('pad.deletionToken.copied'));
202+
});
203+
204+
$ack.off('click.gdpr').on('click.gdpr', () => {
205+
$input.val('');
206+
$modal.prop('hidden', true).removeClass('popup-show');
207+
(window as any).clientVars.padDeletionToken = null;
208+
});
209+
};
210+
179211
const sendClientReady = (isReconnect) => {
180212
let padId = document.location.pathname.substring(document.location.pathname.lastIndexOf('/') + 1);
181213
// unescape necessary due to Safari and Opera interpretation of spaces
@@ -497,6 +529,8 @@ const pad = {
497529
skinVariants.updateSkinVariantsClasses(['super-dark-editor', 'dark-background', 'super-dark-toolbar']);
498530
}
499531

532+
showDeletionTokenModalIfPresent();
533+
500534
hooks.aCallAll('postAceInit', {ace: padeditor.ace, clientVars, pad});
501535
};
502536

src/static/js/pad_editor.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,33 @@ const padeditor = (() => {
8686
pad.changeViewOption('padFontFamily', $('#viewfontmenu').val());
8787
});
8888

89+
// delete pad using a recovery token (second device / no creator cookie)
90+
$('#delete-pad-token-submit').on('click', () => {
91+
const token = String($('#delete-pad-token-input').val() || '').trim();
92+
if (!token) return;
93+
if (!window.confirm(html10n.get('pad.delete.confirm'))) return;
94+
95+
let handled = false;
96+
pad.socket.on('message', (data: any) => {
97+
if (data && data.disconnect === 'deleted') {
98+
handled = true;
99+
window.location.href = '/';
100+
}
101+
});
102+
pad.socket.on('shout', (data: any) => {
103+
handled = true;
104+
const msg = data?.data?.payload?.message?.message;
105+
if (msg) window.alert(msg);
106+
});
107+
pad.collabClient.sendMessage({
108+
type: 'PAD_DELETE',
109+
data: {padId: pad.getPadId(), deletionToken: token},
110+
});
111+
setTimeout(() => {
112+
if (!handled) window.location.href = '/';
113+
}, 5000);
114+
});
115+
89116
// delete pad
90117
$('#delete-pad').on('click', () => {
91118
if (window.confirm(html10n.get('pad.delete.confirm'))) {

0 commit comments

Comments
 (0)