Merge pull request #1496 from dannycoates/dear-nice-things-too
Begin implementing a reporting mechanism
This commit is contained in:
commit
0a8663aa51
17
app/api.js
17
app/api.js
|
@ -127,10 +127,10 @@ export async function metadata(id, keychain) {
|
||||||
return {
|
return {
|
||||||
size: meta.size,
|
size: meta.size,
|
||||||
ttl: data.ttl,
|
ttl: data.ttl,
|
||||||
iv: meta.iv,
|
|
||||||
name: meta.name,
|
name: meta.name,
|
||||||
type: meta.type,
|
type: meta.type,
|
||||||
manifest: meta.manifest
|
manifest: meta.manifest,
|
||||||
|
flagged: data.flagged
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
throw new Error(result.response.status);
|
throw new Error(result.response.status);
|
||||||
|
@ -438,3 +438,16 @@ export async function getConstants() {
|
||||||
|
|
||||||
throw new Error(response.status);
|
throw new Error(response.status);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function reportLink(id, key, reason) {
|
||||||
|
const response = await fetch(
|
||||||
|
getApiUrl(`/api/report/${id}`),
|
||||||
|
post({ key, reason })
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(response.status);
|
||||||
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import FileSender from './fileSender';
|
import FileSender from './fileSender';
|
||||||
import FileReceiver from './fileReceiver';
|
import FileReceiver from './fileReceiver';
|
||||||
|
import { reportLink } from './api';
|
||||||
import { copyToClipboard, delay, openLinksInNewTab, percent } from './utils';
|
import { copyToClipboard, delay, openLinksInNewTab, percent } from './utils';
|
||||||
import * as metrics from './metrics';
|
import * as metrics from './metrics';
|
||||||
import { bytes, locale } from './utils';
|
import { bytes, locale } from './utils';
|
||||||
|
@ -306,6 +307,26 @@ export default function(state, emitter) {
|
||||||
render();
|
render();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
emitter.on('report', async ({ reason }) => {
|
||||||
|
try {
|
||||||
|
const file = state.fileInfo;
|
||||||
|
if (!file) {
|
||||||
|
// TODO
|
||||||
|
emitter.emit('pushState', '/error');
|
||||||
|
return render();
|
||||||
|
}
|
||||||
|
await reportLink(file.id, file.secretKey, reason);
|
||||||
|
render();
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
if (err.message === '404') {
|
||||||
|
state.fileInfo = { reported: true };
|
||||||
|
return render();
|
||||||
|
}
|
||||||
|
emitter.emit('pushState', '/error');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
setInterval(() => {
|
setInterval(() => {
|
||||||
// poll for updates of the upload list
|
// poll for updates of the upload list
|
||||||
if (!state.modal && state.route === '/') {
|
if (!state.modal && state.route === '/') {
|
||||||
|
|
|
@ -47,9 +47,9 @@ export default class FileReceiver extends Nanobus {
|
||||||
const meta = await metadata(this.fileInfo.id, this.keychain);
|
const meta = await metadata(this.fileInfo.id, this.keychain);
|
||||||
this.fileInfo.name = meta.name;
|
this.fileInfo.name = meta.name;
|
||||||
this.fileInfo.type = meta.type;
|
this.fileInfo.type = meta.type;
|
||||||
this.fileInfo.iv = meta.iv;
|
|
||||||
this.fileInfo.size = +meta.size;
|
this.fileInfo.size = +meta.size;
|
||||||
this.fileInfo.manifest = meta.manifest;
|
this.fileInfo.manifest = meta.manifest;
|
||||||
|
this.fileInfo.flagged = meta.flagged;
|
||||||
this.state = 'ready';
|
this.state = 'ready';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
74
app/main.css
74
app/main.css
|
@ -55,6 +55,12 @@ body {
|
||||||
@apply bg-blue-70;
|
@apply bg-blue-70;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.btn:disabled {
|
||||||
|
@apply bg-grey-transparent;
|
||||||
|
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
.checkbox {
|
.checkbox {
|
||||||
@apply leading-normal;
|
@apply leading-normal;
|
||||||
@apply select-none;
|
@apply select-none;
|
||||||
|
@ -138,21 +144,6 @@ footer li:hover {
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
|
|
||||||
.feedback-link {
|
|
||||||
background-color: #000;
|
|
||||||
background-image: url('../assets/feedback.svg');
|
|
||||||
background-position: 0.125rem 0.25rem;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-size: 1.125rem;
|
|
||||||
color: #fff;
|
|
||||||
display: block;
|
|
||||||
font-size: 0.75rem;
|
|
||||||
line-height: 0.75rem;
|
|
||||||
padding: 0.375rem 0.375rem 0.375rem 1.25rem;
|
|
||||||
text-indent: 0.125rem;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.link-blue {
|
.link-blue {
|
||||||
@apply text-blue-60;
|
@apply text-blue-60;
|
||||||
}
|
}
|
||||||
|
@ -175,6 +166,10 @@ footer li:hover {
|
||||||
height: unset;
|
height: unset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dl-bg {
|
||||||
|
filter: grayscale(1) opacity(0.15);
|
||||||
|
}
|
||||||
|
|
||||||
.main {
|
.main {
|
||||||
display: flex;
|
display: flex;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
@ -322,6 +317,10 @@ select {
|
||||||
@apply bg-blue-50;
|
@apply bg-blue-50;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.btn:disabled {
|
||||||
|
@apply bg-grey-80;
|
||||||
|
}
|
||||||
|
|
||||||
.link-blue {
|
.link-blue {
|
||||||
@apply text-blue-40;
|
@apply text-blue-40;
|
||||||
}
|
}
|
||||||
|
@ -392,48 +391,3 @@ select {
|
||||||
.signin:hover:active {
|
.signin:hover:active {
|
||||||
transform: scale(0.9375);
|
transform: scale(0.9375);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* begin signin button color experiment */
|
|
||||||
|
|
||||||
.white-blue {
|
|
||||||
@apply border-blue-60;
|
|
||||||
@apply border-2;
|
|
||||||
@apply text-blue-60;
|
|
||||||
}
|
|
||||||
|
|
||||||
.white-blue:hover,
|
|
||||||
.white-blue:focus {
|
|
||||||
@apply bg-blue-60;
|
|
||||||
@apply text-white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.blue {
|
|
||||||
@apply bg-blue-60;
|
|
||||||
@apply text-white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.white-violet {
|
|
||||||
@apply border-violet;
|
|
||||||
@apply border-2;
|
|
||||||
@apply text-violet;
|
|
||||||
}
|
|
||||||
|
|
||||||
.white-violet:hover,
|
|
||||||
.white-violet:focus {
|
|
||||||
@apply bg-violet;
|
|
||||||
@apply text-white;
|
|
||||||
|
|
||||||
background-image: var(--violet-gradient);
|
|
||||||
}
|
|
||||||
|
|
||||||
.violet {
|
|
||||||
@apply bg-violet;
|
|
||||||
@apply text-white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.violet:hover,
|
|
||||||
.violet:focus {
|
|
||||||
background-image: var(--violet-gradient);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* end signin button color experiment */
|
|
||||||
|
|
|
@ -14,6 +14,7 @@ module.exports = function(app = choo({ hash: true })) {
|
||||||
emit('authenticate', state.query.code, state.query.state);
|
emit('authenticate', state.query.code, state.query.state);
|
||||||
});
|
});
|
||||||
app.route('/login', body(require('./ui/home')));
|
app.route('/login', body(require('./ui/home')));
|
||||||
|
app.route('/report', body(require('./ui/report')));
|
||||||
app.route('*', body(require('./ui/notFound')));
|
app.route('*', body(require('./ui/notFound')));
|
||||||
return app;
|
return app;
|
||||||
};
|
};
|
||||||
|
|
|
@ -482,6 +482,11 @@ module.exports.empty = function(state, emit) {
|
||||||
>
|
>
|
||||||
${state.translate('addFilesButton')}
|
${state.translate('addFilesButton')}
|
||||||
</label>
|
</label>
|
||||||
|
<p
|
||||||
|
class="font-normal text-sm text-grey-50 dark:text-grey-40 my-6 mx-12 text-center max-w-sm leading-loose"
|
||||||
|
>
|
||||||
|
${state.translate('trustWarningMessage')}
|
||||||
|
</p>
|
||||||
${upsell}
|
${upsell}
|
||||||
</send-upload-area>
|
</send-upload-area>
|
||||||
`;
|
`;
|
||||||
|
@ -517,13 +522,27 @@ module.exports.preview = function(state, emit) {
|
||||||
`;
|
`;
|
||||||
return html`
|
return html`
|
||||||
<send-archive
|
<send-archive
|
||||||
class="flex flex-col max-h-full bg-white p-4 w-full md:w-128 dark:bg-grey-90"
|
class="flex flex-col max-h-full bg-white w-full dark:bg-grey-90"
|
||||||
>
|
>
|
||||||
<div class="border rounded py-3 px-6 dark:border-grey-70">
|
<div class="border rounded py-3 px-4 dark:border-grey-70">
|
||||||
${archiveInfo(archive)} ${details}
|
${archiveInfo(archive)} ${details}
|
||||||
</div>
|
</div>
|
||||||
|
<div class="checkbox inline-block mt-6 mx-auto">
|
||||||
|
<input
|
||||||
|
id="trust-download"
|
||||||
|
type="checkbox"
|
||||||
|
autocomplete="off"
|
||||||
|
onchange="${toggleDownloadEnabled}"
|
||||||
|
/>
|
||||||
|
<label for="trust-download">
|
||||||
|
${state.translate('downloadTrustCheckbox', {
|
||||||
|
count: archive.manifest.files.length
|
||||||
|
})}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
<button
|
<button
|
||||||
id="download-btn"
|
id="download-btn"
|
||||||
|
disabled
|
||||||
class="btn rounded-lg mt-4 w-full flex-shrink-0 focus:outline"
|
class="btn rounded-lg mt-4 w-full flex-shrink-0 focus:outline"
|
||||||
title="${state.translate('downloadButtonLabel')}"
|
title="${state.translate('downloadButtonLabel')}"
|
||||||
onclick=${download}
|
onclick=${download}
|
||||||
|
@ -533,6 +552,13 @@ module.exports.preview = function(state, emit) {
|
||||||
</send-archive>
|
</send-archive>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
function toggleDownloadEnabled(event) {
|
||||||
|
event.stopPropagation();
|
||||||
|
const checked = event.target.checked;
|
||||||
|
const btn = document.getElementById('download-btn');
|
||||||
|
btn.disabled = !checked;
|
||||||
|
}
|
||||||
|
|
||||||
function download(event) {
|
function download(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.target.disabled = true;
|
event.target.disabled = true;
|
||||||
|
|
|
@ -10,11 +10,9 @@ module.exports = function(name, url) {
|
||||||
<h1 class="text-3xl font-bold my-4">
|
<h1 class="text-3xl font-bold my-4">
|
||||||
${state.translate('notifyUploadEncryptDone')}
|
${state.translate('notifyUploadEncryptDone')}
|
||||||
</h1>
|
</h1>
|
||||||
<p
|
<p class="font-normal leading-normal text-grey-80 dark:text-grey-40">
|
||||||
class="font-normal leading-normal text-grey-80 word-break-all dark:text-grey-40"
|
|
||||||
>
|
|
||||||
${state.translate('copyLinkDescription')} <br />
|
${state.translate('copyLinkDescription')} <br />
|
||||||
${name}
|
<span class="word-break-all">${name}</span>
|
||||||
</p>
|
</p>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
/* global downloadMetadata */
|
/* global downloadMetadata */
|
||||||
const html = require('choo/html');
|
const html = require('choo/html');
|
||||||
|
const assets = require('../../common/assets');
|
||||||
const archiveTile = require('./archiveTile');
|
const archiveTile = require('./archiveTile');
|
||||||
const modal = require('./modal');
|
const modal = require('./modal');
|
||||||
const noStreams = require('./noStreams');
|
const noStreams = require('./noStreams');
|
||||||
|
@ -31,22 +32,53 @@ function downloading(state, emit) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function preview(state, emit) {
|
function preview(state, emit) {
|
||||||
|
if (state.fileInfo.flagged) {
|
||||||
|
return html`
|
||||||
|
<div
|
||||||
|
class="flex flex-col w-full max-w-md h-full mx-auto items-center justify-center"
|
||||||
|
>
|
||||||
|
<h1 class="text-xl font-bold">${state.translate('downloadFlagged')}</h1>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
if (!state.capabilities.streamDownload && state.fileInfo.size > BIG_SIZE) {
|
if (!state.capabilities.streamDownload && state.fileInfo.size > BIG_SIZE) {
|
||||||
return noStreams(state, emit);
|
return noStreams(state, emit);
|
||||||
}
|
}
|
||||||
return html`
|
return html`
|
||||||
<div
|
<div
|
||||||
class="flex flex-col w-full max-w-md h-full mx-auto items-center justify-center"
|
class="w-full overflow-hidden md:flex md:flex-row items-stretch md:flex-1"
|
||||||
>
|
>
|
||||||
<h1 class="text-3xl font-bold mb-4">
|
<div
|
||||||
${state.translate('downloadTitle')}
|
class="px-2 w-full md:px-0 flex-half md:flex md:flex-col mt-12 md:pr-8 pb-4"
|
||||||
</h1>
|
|
||||||
<p
|
|
||||||
class="w-full text-grey-80 text-center leading-normal dark:text-grey-40"
|
|
||||||
>
|
>
|
||||||
${state.translate('downloadDescription')}
|
<h1 class="text-3xl font-bold mb-4 text-center md:text-left">
|
||||||
</p>
|
${state.translate('downloadTitle')}
|
||||||
${archiveTile.preview(state, emit)}
|
</h1>
|
||||||
|
<p
|
||||||
|
class="text-grey-80 leading-normal dark:text-grey-40 mb-4 text-center md:text-left"
|
||||||
|
>
|
||||||
|
${state.translate('downloadDescription')}
|
||||||
|
</p>
|
||||||
|
<p
|
||||||
|
class="text-grey-80 leading-normal dark:text-grey-40 font-semibold text-center md:mb-8 md:text-left"
|
||||||
|
>
|
||||||
|
${state.translate('downloadConfirmDescription')}
|
||||||
|
</p>
|
||||||
|
<img
|
||||||
|
class="hidden md:block dl-bg w-full"
|
||||||
|
src="${assets.get('intro.svg')}"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="w-full flex-half flex-half md:flex md:flex-col md:justify-center"
|
||||||
|
>
|
||||||
|
${archiveTile.preview(state, emit)}
|
||||||
|
<a href="/report" class="link-blue mt-4 text-center block"
|
||||||
|
>${state.translate('reportFile', {
|
||||||
|
count: state.fileInfo.manifest.files.length
|
||||||
|
})}</a
|
||||||
|
>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
@ -83,7 +115,7 @@ module.exports = function(state, emit) {
|
||||||
<main class="main">
|
<main class="main">
|
||||||
${state.modal && modal(state, emit)}
|
${state.modal && modal(state, emit)}
|
||||||
<section
|
<section
|
||||||
class="relative h-full w-full p-6 md:p-8 md:rounded-xl md:shadow-big"
|
class="relative h-full w-full p-6 md:p-8 md:rounded-xl md:shadow-big md:flex md:flex-col"
|
||||||
>
|
>
|
||||||
${content}
|
${content}
|
||||||
</section>
|
</section>
|
||||||
|
|
|
@ -10,7 +10,7 @@ module.exports = function(state) {
|
||||||
<h1 class="text-center text-3xl font-bold my-2">
|
<h1 class="text-center text-3xl font-bold my-2">
|
||||||
${state.translate('downloadFinish')}
|
${state.translate('downloadFinish')}
|
||||||
</h1>
|
</h1>
|
||||||
<img src="${assets.get('completed.svg')}" class="my-12 h-48" />
|
<img src="${assets.get('completed.svg')}" class="my-8 h-48" />
|
||||||
<p class="text-grey-80 leading-normal dark:text-grey-40">
|
<p class="text-grey-80 leading-normal dark:text-grey-40">
|
||||||
${state.translate('trySendDescription')}
|
${state.translate('trySendDescription')}
|
||||||
</p>
|
</p>
|
||||||
|
@ -19,6 +19,9 @@ module.exports = function(state) {
|
||||||
>${state.translate('sendYourFilesLink')}</a
|
>${state.translate('sendYourFilesLink')}</a
|
||||||
>
|
>
|
||||||
</p>
|
</p>
|
||||||
|
<p class="">
|
||||||
|
<a href="/report" class="link-blue">${state.translate('reportFile')}</a>
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,58 @@
|
||||||
|
const html = require('choo/html');
|
||||||
|
|
||||||
|
module.exports = function() {
|
||||||
|
return function(state, emit, close) {
|
||||||
|
const archive = state.fileInfo;
|
||||||
|
return html`
|
||||||
|
<send-download-dialog
|
||||||
|
class="flex flex-col w-full max-w-sm h-full mx-auto items-center justify-center"
|
||||||
|
>
|
||||||
|
<h1 class="text-3xl font-bold mb-4">
|
||||||
|
${state.translate('downloadConfirmTitle')}
|
||||||
|
</h1>
|
||||||
|
<p
|
||||||
|
class="w-full text-grey-80 text-center leading-normal dark:text-grey-40 mb-8"
|
||||||
|
>
|
||||||
|
${state.translate('downloadConfirmDescription')}
|
||||||
|
</p>
|
||||||
|
<div class="checkbox inline-block mr-3 mb-8">
|
||||||
|
<input
|
||||||
|
id="trust-download"
|
||||||
|
type="checkbox"
|
||||||
|
autocomplete="off"
|
||||||
|
onchange="${toggleDownloadEnabled}"
|
||||||
|
/>
|
||||||
|
<label for="trust-download">
|
||||||
|
${state.translate('downloadTrustCheckbox')}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
id="download-btn"
|
||||||
|
disabled
|
||||||
|
class="btn rounded-lg w-full flex-shrink-0"
|
||||||
|
onclick="${download}"
|
||||||
|
title="${state.translate('downloadButtonLabel')}"
|
||||||
|
>
|
||||||
|
${state.translate('downloadButtonLabel')}
|
||||||
|
</button>
|
||||||
|
<a href="/report" class="link-blue mt-8"
|
||||||
|
>${state.translate('reportFile')}</a
|
||||||
|
>
|
||||||
|
</send-download-dialog>
|
||||||
|
`;
|
||||||
|
|
||||||
|
function toggleDownloadEnabled(event) {
|
||||||
|
event.stopPropagation();
|
||||||
|
const checked = event.target.checked;
|
||||||
|
const btn = document.getElementById('download-btn');
|
||||||
|
btn.disabled = !checked;
|
||||||
|
}
|
||||||
|
|
||||||
|
function download(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
close();
|
||||||
|
event.target.disabled = true;
|
||||||
|
emit('download', archive);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
|
@ -1,7 +1,5 @@
|
||||||
const html = require('choo/html');
|
const html = require('choo/html');
|
||||||
const Component = require('choo/component');
|
const Component = require('choo/component');
|
||||||
const version = require('../../package.json').version;
|
|
||||||
const { browserName } = require('../utils');
|
|
||||||
|
|
||||||
class Footer extends Component {
|
class Footer extends Component {
|
||||||
constructor(name, state) {
|
constructor(name, state) {
|
||||||
|
@ -15,8 +13,6 @@ class Footer extends Component {
|
||||||
|
|
||||||
createElement() {
|
createElement() {
|
||||||
const translate = this.state.translate;
|
const translate = this.state.translate;
|
||||||
const browser = browserName();
|
|
||||||
const feedbackUrl = `https://qsurvey.mozilla.com/s3/Firefox-Send-Product-Feedback?ver=${version}&browser=${browser}`;
|
|
||||||
return html`
|
return html`
|
||||||
<footer
|
<footer
|
||||||
class="flex flex-col md:flex-row items-start w-full flex-none self-start p-6 md:p-8 font-medium text-xs text-grey-60 dark:text-grey-40 md:items-center justify-between"
|
class="flex flex-col md:flex-row items-start w-full flex-none self-start p-6 md:p-8 font-medium text-xs text-grey-60 dark:text-grey-40 md:items-center justify-between"
|
||||||
|
@ -43,17 +39,6 @@ class Footer extends Component {
|
||||||
<li class="m-2">
|
<li class="m-2">
|
||||||
<a href="https://github.com/mozilla/send">GitHub </a>
|
<a href="https://github.com/mozilla/send">GitHub </a>
|
||||||
</li>
|
</li>
|
||||||
<li class="m-2">
|
|
||||||
<a
|
|
||||||
href="${feedbackUrl}"
|
|
||||||
rel="noreferrer noopener"
|
|
||||||
class="feedback-link"
|
|
||||||
alt="Feedback"
|
|
||||||
target="_blank"
|
|
||||||
>
|
|
||||||
${translate('siteFeedback')}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
</ul>
|
||||||
</footer>
|
</footer>
|
||||||
`;
|
`;
|
||||||
|
|
|
@ -5,6 +5,9 @@ const modal = require('./modal');
|
||||||
const intro = require('./intro');
|
const intro = require('./intro');
|
||||||
|
|
||||||
module.exports = function(state, emit) {
|
module.exports = function(state, emit) {
|
||||||
|
if (state.user.loginRequired && !state.user.loggedIn) {
|
||||||
|
emit('signup-cta', 'required');
|
||||||
|
}
|
||||||
const archives = state.storage.files
|
const archives = state.storage.files
|
||||||
.filter(archive => !archive.expired)
|
.filter(archive => !archive.expired)
|
||||||
.map(archive => archiveTile(state, emit, archive));
|
.map(archive => archiveTile(state, emit, archive));
|
||||||
|
|
|
@ -21,6 +21,11 @@ module.exports = function(state, emit) {
|
||||||
>${state.translate('sendYourFilesLink')}</a
|
>${state.translate('sendYourFilesLink')}</a
|
||||||
>
|
>
|
||||||
</p>
|
</p>
|
||||||
|
<p class="">
|
||||||
|
<a href="/report" class="link-blue"
|
||||||
|
>${state.translate('reportFile')}</a
|
||||||
|
>
|
||||||
|
</p>
|
||||||
</section>
|
</section>
|
||||||
</main>
|
</main>
|
||||||
`;
|
`;
|
||||||
|
|
|
@ -0,0 +1,132 @@
|
||||||
|
const html = require('choo/html');
|
||||||
|
const raw = require('choo/html/raw');
|
||||||
|
const assets = require('../../common/assets');
|
||||||
|
|
||||||
|
const REPORTABLES = ['Malware', 'Pii', 'Abuse'];
|
||||||
|
|
||||||
|
module.exports = function(state, emit) {
|
||||||
|
let submitting = false;
|
||||||
|
const file = state.fileInfo;
|
||||||
|
if (!file) {
|
||||||
|
return html`
|
||||||
|
<main class="main">
|
||||||
|
<section
|
||||||
|
class="flex flex-col items-center justify-center h-full w-full p-6 md:p-8 overflow-hidden md:rounded-xl md:shadow-big"
|
||||||
|
>
|
||||||
|
<p class="mb-4 leading-normal">
|
||||||
|
${state.translate('reportUnknownDescription')}
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
if (file.reported) {
|
||||||
|
return html`
|
||||||
|
<main class="main">
|
||||||
|
<section
|
||||||
|
class="flex flex-col items-center justify-center h-full w-full p-6 md:p-8 overflow-hidden md:rounded-xl md:shadow-big"
|
||||||
|
>
|
||||||
|
<h1 class="text-center text-3xl font-bold my-2">
|
||||||
|
${state.translate('reportedTitle')}
|
||||||
|
</h1>
|
||||||
|
<p class="max-w-md text-center text-grey-80 leading-normal">
|
||||||
|
${state.translate('reportedDescription')}
|
||||||
|
</p>
|
||||||
|
<img src="${assets.get('notFound.svg')}" class="my-12" />
|
||||||
|
<p class="my-5">
|
||||||
|
<a href="/" class="btn rounded-lg flex items-center" role="button"
|
||||||
|
>${state.translate('okButton')}</a
|
||||||
|
>
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
return html`
|
||||||
|
<main class="main">
|
||||||
|
<section
|
||||||
|
class="relative h-full w-full p-6 md:p-8 md:rounded-xl md:shadow-big"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="flex flex-col w-full max-w-sm h-full mx-auto items-center justify-center"
|
||||||
|
>
|
||||||
|
<h1 class="text-2xl font-bold mb-4">
|
||||||
|
${state.translate('reportFile')}
|
||||||
|
</h1>
|
||||||
|
<p class="mb-4 leading-normal font-semibold">
|
||||||
|
${state.translate('reportDescription')}
|
||||||
|
</p>
|
||||||
|
<form onsubmit="${report}" data-no-csrf>
|
||||||
|
<fieldset onchange="${optionChanged}">
|
||||||
|
<ul
|
||||||
|
class="list-none p-4 mb-6 rounded-sm bg-grey-10 dark:bg-black"
|
||||||
|
>
|
||||||
|
${REPORTABLES.map(
|
||||||
|
reportable =>
|
||||||
|
html`
|
||||||
|
<li class="mb-2 leading-normal">
|
||||||
|
<label
|
||||||
|
for="${reportable.toLowerCase()}"
|
||||||
|
class="flex items-center"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
name="reason"
|
||||||
|
id="${reportable.toLowerCase()}"
|
||||||
|
value="${reportable.toLowerCase()}"
|
||||||
|
class="mr-2 my-2 w-4 h-4"
|
||||||
|
/>
|
||||||
|
${state.translate(`reportReason${reportable}`)}
|
||||||
|
</label>
|
||||||
|
</li>
|
||||||
|
`
|
||||||
|
)}
|
||||||
|
<li class="mt-4 mb-2 leading-normal">
|
||||||
|
${raw(
|
||||||
|
replaceLinks(state.translate('reportReasonCopyright'), [
|
||||||
|
'https://www.mozilla.org/about/legal/report-infringement/'
|
||||||
|
])
|
||||||
|
)}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</fieldset>
|
||||||
|
<input
|
||||||
|
type="submit"
|
||||||
|
disabled
|
||||||
|
class="btn rounded-lg w-full flex-shrink-0 focus:outline"
|
||||||
|
title="${state.translate('reportButton')}"
|
||||||
|
value="${state.translate('reportButton')}"
|
||||||
|
/>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
`;
|
||||||
|
|
||||||
|
function optionChanged(event) {
|
||||||
|
event.stopPropagation();
|
||||||
|
const button = event.currentTarget.nextElementSibling;
|
||||||
|
button.disabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function report(event) {
|
||||||
|
event.stopPropagation();
|
||||||
|
event.preventDefault();
|
||||||
|
if (submitting) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
submitting = true;
|
||||||
|
state.fileInfo.reported = true;
|
||||||
|
const form = event.target;
|
||||||
|
emit('report', { reason: form.reason.value });
|
||||||
|
}
|
||||||
|
|
||||||
|
function replaceLinks(str, urls) {
|
||||||
|
let i = 0;
|
||||||
|
const s = str.replace(
|
||||||
|
/<a>([^<]+)<\/a>/g,
|
||||||
|
(m, v) => `<a class="text-blue" href="${urls[i++]}">${v}</a>`
|
||||||
|
);
|
||||||
|
return `<p>${s}</p>`;
|
||||||
|
}
|
||||||
|
};
|
|
@ -9,11 +9,9 @@ module.exports = function(name, url) {
|
||||||
<h1 class="text-3xl font-bold my-4">
|
<h1 class="text-3xl font-bold my-4">
|
||||||
${state.translate('notifyUploadEncryptDone')}
|
${state.translate('notifyUploadEncryptDone')}
|
||||||
</h1>
|
</h1>
|
||||||
<p
|
<p class="font-normal leading-normal text-grey-80 dark:text-grey-40">
|
||||||
class="font-normal leading-normal text-grey-80 word-break-all dark:text-grey-40"
|
|
||||||
>
|
|
||||||
${state.translate('shareLinkDescription')}<br />
|
${state.translate('shareLinkDescription')}<br />
|
||||||
${name}
|
<span class="word-break-all">${name}</span>
|
||||||
</p>
|
</p>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
|
|
|
@ -94,6 +94,10 @@ export default class User {
|
||||||
: this.limits.ANON.MAX_DOWNLOADS;
|
: this.limits.ANON.MAX_DOWNLOADS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get loginRequired() {
|
||||||
|
return this.authConfig.fxa_required;
|
||||||
|
}
|
||||||
|
|
||||||
async metricId() {
|
async metricId() {
|
||||||
return this.loggedIn ? hashId(this.info.uid) : undefined;
|
return this.loggedIn ? hashId(this.info.uid) : undefined;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1144,6 +1144,56 @@
|
||||||
"fastq": "^1.6.0"
|
"fastq": "^1.6.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@peculiar/asn1-schema": {
|
||||||
|
"version": "2.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@peculiar/asn1-schema/-/asn1-schema-2.0.5.tgz",
|
||||||
|
"integrity": "sha512-VIKJjsgMkv+yyWx3C+D4xo6/NeCg0XFBgNlavtkxELijV+aKAq53du5KkOJbeZtm1nn9CinQKny2PqL8zCfpeA==",
|
||||||
|
"requires": {
|
||||||
|
"@types/asn1js": "^0.0.1",
|
||||||
|
"asn1js": "^2.0.26",
|
||||||
|
"pvtsutils": "^1.0.10",
|
||||||
|
"tslib": "^1.11.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@peculiar/json-schema": {
|
||||||
|
"version": "1.1.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/@peculiar/json-schema/-/json-schema-1.1.10.tgz",
|
||||||
|
"integrity": "sha512-kbpnG9CkF1y6wwGkW7YtSA+yYK4X5uk4rAwsd1hxiaYE3Hkw2EsGlbGh/COkMLyFf+Fe830BoFiMSB3QnC/ItA==",
|
||||||
|
"requires": {
|
||||||
|
"tslib": "^1.11.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@peculiar/webcrypto": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@peculiar/webcrypto/-/webcrypto-1.1.1.tgz",
|
||||||
|
"integrity": "sha512-Bu2XgOvzirnLcojZYs4KQ8hOLf2ETpa0NL6btQt5NgsAwctI6yVkzgYP+EcG7Mm579RBP+V0LM5rXyMlTVx23A==",
|
||||||
|
"requires": {
|
||||||
|
"@peculiar/asn1-schema": "^2.0.3",
|
||||||
|
"@peculiar/json-schema": "^1.1.10",
|
||||||
|
"pvtsutils": "^1.0.10",
|
||||||
|
"tslib": "^1.11.2",
|
||||||
|
"webcrypto-core": "^1.1.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": {
|
||||||
|
"version": "1.13.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz",
|
||||||
|
"integrity": "sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q=="
|
||||||
|
},
|
||||||
|
"webcrypto-core": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/webcrypto-core/-/webcrypto-core-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-LxM/dTcXr/ZnwwKLox0tGEOIqvP7KIJ4Hk/fFPX20tr1EgqTmpEFZinmu4FzoGVbs6e4jI1priQKCDrOBD3L6w==",
|
||||||
|
"requires": {
|
||||||
|
"@peculiar/asn1-schema": "^2.0.1",
|
||||||
|
"@peculiar/json-schema": "^1.1.10",
|
||||||
|
"asn1js": "^2.0.26",
|
||||||
|
"pvtsutils": "^1.0.10",
|
||||||
|
"tslib": "^1.11.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"@samverschueren/stream-to-observable": {
|
"@samverschueren/stream-to-observable": {
|
||||||
"version": "0.3.0",
|
"version": "0.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/@samverschueren/stream-to-observable/-/stream-to-observable-0.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/@samverschueren/stream-to-observable/-/stream-to-observable-0.3.0.tgz",
|
||||||
|
@ -1296,6 +1346,14 @@
|
||||||
"resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz",
|
||||||
"integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw=="
|
"integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw=="
|
||||||
},
|
},
|
||||||
|
"@types/asn1js": {
|
||||||
|
"version": "0.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/asn1js/-/asn1js-0.0.1.tgz",
|
||||||
|
"integrity": "sha1-74uflwjLFjKhw6nNJ3F8qr55O8I=",
|
||||||
|
"requires": {
|
||||||
|
"@types/pvutils": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@types/color-name": {
|
"@types/color-name": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz",
|
||||||
|
@ -1343,6 +1401,11 @@
|
||||||
"integrity": "sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA==",
|
"integrity": "sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"@types/pvutils": {
|
||||||
|
"version": "0.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/pvutils/-/pvutils-0.0.2.tgz",
|
||||||
|
"integrity": "sha512-CgQAm7pjyeF3Gnv78ty4RBVIfluB+Td+2DR8iPaU0prF18pkzptHHP+DoKPfpsJYknKsVZyVsJEu5AuGgAqQ5w=="
|
||||||
|
},
|
||||||
"@types/q": {
|
"@types/q": {
|
||||||
"version": "1.5.2",
|
"version": "1.5.2",
|
||||||
"resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.2.tgz",
|
"resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.2.tgz",
|
||||||
|
@ -1966,6 +2029,14 @@
|
||||||
"minimalistic-assert": "^1.0.0"
|
"minimalistic-assert": "^1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"asn1js": {
|
||||||
|
"version": "2.0.26",
|
||||||
|
"resolved": "https://registry.npmjs.org/asn1js/-/asn1js-2.0.26.tgz",
|
||||||
|
"integrity": "sha512-yG89F0j9B4B0MKIcFyWWxnpZPLaNTjCj4tkE3fjbAoo0qmpGw0PYYqSbX/4ebnd9Icn8ZgK4K1fvDyEtW1JYtQ==",
|
||||||
|
"requires": {
|
||||||
|
"pvutils": "^1.0.17"
|
||||||
|
}
|
||||||
|
},
|
||||||
"assert": {
|
"assert": {
|
||||||
"version": "1.5.0",
|
"version": "1.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/assert/-/assert-1.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/assert/-/assert-1.5.0.tgz",
|
||||||
|
@ -3662,6 +3733,11 @@
|
||||||
"randomfill": "^1.0.3"
|
"randomfill": "^1.0.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"crypto-random-string": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA=="
|
||||||
|
},
|
||||||
"css-blank-pseudo": {
|
"css-blank-pseudo": {
|
||||||
"version": "0.1.4",
|
"version": "0.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/css-blank-pseudo/-/css-blank-pseudo-0.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/css-blank-pseudo/-/css-blank-pseudo-0.1.4.tgz",
|
||||||
|
@ -4277,6 +4353,11 @@
|
||||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||||
"integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk="
|
"integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk="
|
||||||
},
|
},
|
||||||
|
"denque": {
|
||||||
|
"version": "1.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/denque/-/denque-1.4.1.tgz",
|
||||||
|
"integrity": "sha512-OfzPuSZKGcgr96rf1oODnfjqBFmr1DVoc/TrItj3Ohe0Ah1C5WX5Baquw/9U9KovnQ88EqmJbD66rKYUQYN1tQ=="
|
||||||
|
},
|
||||||
"depd": {
|
"depd": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
|
||||||
|
@ -4662,11 +4743,6 @@
|
||||||
"is-obj": "^2.0.0"
|
"is-obj": "^2.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"double-ended-queue": {
|
|
||||||
"version": "2.1.0-0",
|
|
||||||
"resolved": "https://registry.npmjs.org/double-ended-queue/-/double-ended-queue-2.1.0-0.tgz",
|
|
||||||
"integrity": "sha1-ED01J/0xUo9AGIEwyEHv3XgmTlw="
|
|
||||||
},
|
|
||||||
"duplexer": {
|
"duplexer": {
|
||||||
"version": "0.1.1",
|
"version": "0.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz",
|
||||||
|
@ -6875,6 +6951,19 @@
|
||||||
"stream-events": "^1.0.4"
|
"stream-events": "^1.0.4"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"configstore": {
|
||||||
|
"version": "5.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz",
|
||||||
|
"integrity": "sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==",
|
||||||
|
"requires": {
|
||||||
|
"dot-prop": "^5.2.0",
|
||||||
|
"graceful-fs": "^4.1.2",
|
||||||
|
"make-dir": "^3.0.0",
|
||||||
|
"unique-string": "^2.0.0",
|
||||||
|
"write-file-atomic": "^3.0.0",
|
||||||
|
"xdg-basedir": "^4.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"gaxios": {
|
"gaxios": {
|
||||||
"version": "2.3.4",
|
"version": "2.3.4",
|
||||||
"resolved": "https://registry.npmjs.org/gaxios/-/gaxios-2.3.4.tgz",
|
"resolved": "https://registry.npmjs.org/gaxios/-/gaxios-2.3.4.tgz",
|
||||||
|
@ -6886,6 +6975,30 @@
|
||||||
"is-stream": "^2.0.0",
|
"is-stream": "^2.0.0",
|
||||||
"node-fetch": "^2.3.0"
|
"node-fetch": "^2.3.0"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"make-dir": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==",
|
||||||
|
"requires": {
|
||||||
|
"semver": "^6.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"semver": {
|
||||||
|
"version": "6.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
|
||||||
|
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw=="
|
||||||
|
},
|
||||||
|
"write-file-atomic": {
|
||||||
|
"version": "3.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz",
|
||||||
|
"integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==",
|
||||||
|
"requires": {
|
||||||
|
"imurmurhash": "^0.1.4",
|
||||||
|
"is-typedarray": "^1.0.0",
|
||||||
|
"signal-exit": "^3.0.2",
|
||||||
|
"typedarray-to-buffer": "^3.1.5"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -7109,8 +7222,7 @@
|
||||||
"graceful-fs": {
|
"graceful-fs": {
|
||||||
"version": "4.2.4",
|
"version": "4.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz",
|
||||||
"integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==",
|
"integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"growl": {
|
"growl": {
|
||||||
"version": "1.10.5",
|
"version": "1.10.5",
|
||||||
|
@ -7994,8 +8106,7 @@
|
||||||
"imurmurhash": {
|
"imurmurhash": {
|
||||||
"version": "0.1.4",
|
"version": "0.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
|
||||||
"integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=",
|
"integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"indent-string": {
|
"indent-string": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
|
@ -13030,6 +13141,19 @@
|
||||||
"yargs": "^14.0.0"
|
"yargs": "^14.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"pvtsutils": {
|
||||||
|
"version": "1.0.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/pvtsutils/-/pvtsutils-1.0.10.tgz",
|
||||||
|
"integrity": "sha512-8ZKQcxnZKTn+fpDh7wL4yKax5fdl3UJzT8Jv49djZpB/dzPxacyN1Sez90b6YLdOmvIr9vaySJ5gw4aUA1EdSw==",
|
||||||
|
"requires": {
|
||||||
|
"tslib": "^1.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pvutils": {
|
||||||
|
"version": "1.0.17",
|
||||||
|
"resolved": "https://registry.npmjs.org/pvutils/-/pvutils-1.0.17.tgz",
|
||||||
|
"integrity": "sha512-wLHYUQxWaXVQvKnwIDWFVKDJku9XDCvyhhxoq8dc5MFdIlRenyPI9eSfEtcvgHgD7FlvCyGAlWgOzRnZD99GZQ=="
|
||||||
|
},
|
||||||
"q": {
|
"q": {
|
||||||
"version": "1.5.1",
|
"version": "1.5.1",
|
||||||
"resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz",
|
"resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz",
|
||||||
|
@ -13361,13 +13485,14 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"redis": {
|
"redis": {
|
||||||
"version": "2.8.0",
|
"version": "3.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/redis/-/redis-2.8.0.tgz",
|
"resolved": "https://registry.npmjs.org/redis/-/redis-3.0.2.tgz",
|
||||||
"integrity": "sha512-M1OkonEQwtRmZv4tEWF2VgpG0JWJ8Fv1PhlgT5+B+uNq2cA3Rt1Yt/ryoR+vQNOQcIEgdCdfH0jr3bDpihAw1A==",
|
"integrity": "sha512-PNhLCrjU6vKVuMOyFu7oSP296mwBkcE6lrAjruBYG5LgdSqtRBoVQIylrMyVZD/lkF24RSNNatzvYag6HRBHjQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"double-ended-queue": "^2.1.0-0",
|
"denque": "^1.4.1",
|
||||||
"redis-commands": "^1.2.0",
|
"redis-commands": "^1.5.0",
|
||||||
"redis-parser": "^2.6.0"
|
"redis-errors": "^1.2.0",
|
||||||
|
"redis-parser": "^3.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"redis-commands": {
|
"redis-commands": {
|
||||||
|
@ -13375,6 +13500,11 @@
|
||||||
"resolved": "https://registry.npmjs.org/redis-commands/-/redis-commands-1.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/redis-commands/-/redis-commands-1.5.0.tgz",
|
||||||
"integrity": "sha512-6KxamqpZ468MeQC3bkWmCB1fp56XL64D4Kf0zJSwDZbVLLm7KFkoIcHrgRvQ+sk8dnhySs7+yBg94yIkAK7aJg=="
|
"integrity": "sha512-6KxamqpZ468MeQC3bkWmCB1fp56XL64D4Kf0zJSwDZbVLLm7KFkoIcHrgRvQ+sk8dnhySs7+yBg94yIkAK7aJg=="
|
||||||
},
|
},
|
||||||
|
"redis-errors": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz",
|
||||||
|
"integrity": "sha1-62LSrbFeTq9GEMBK/hUpOEJQq60="
|
||||||
|
},
|
||||||
"redis-mock": {
|
"redis-mock": {
|
||||||
"version": "0.47.0",
|
"version": "0.47.0",
|
||||||
"resolved": "https://registry.npmjs.org/redis-mock/-/redis-mock-0.47.0.tgz",
|
"resolved": "https://registry.npmjs.org/redis-mock/-/redis-mock-0.47.0.tgz",
|
||||||
|
@ -13382,9 +13512,12 @@
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"redis-parser": {
|
"redis-parser": {
|
||||||
"version": "2.6.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-2.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz",
|
||||||
"integrity": "sha1-Uu0J2srBCPGmMcB+m2mUHnoZUEs="
|
"integrity": "sha1-tm2CjNyv5rS4pCin3vTGvKwxyLQ=",
|
||||||
|
"requires": {
|
||||||
|
"redis-errors": "^1.0.0"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"reduce-css-calc": {
|
"reduce-css-calc": {
|
||||||
"version": "2.1.7",
|
"version": "2.1.7",
|
||||||
|
@ -14143,8 +14276,7 @@
|
||||||
"signal-exit": {
|
"signal-exit": {
|
||||||
"version": "3.0.3",
|
"version": "3.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz",
|
||||||
"integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==",
|
"integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"simple-swizzle": {
|
"simple-swizzle": {
|
||||||
"version": "0.2.2",
|
"version": "0.2.2",
|
||||||
|
@ -15885,6 +16017,14 @@
|
||||||
"resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
|
||||||
"integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c="
|
"integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c="
|
||||||
},
|
},
|
||||||
|
"typedarray-to-buffer": {
|
||||||
|
"version": "3.1.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz",
|
||||||
|
"integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==",
|
||||||
|
"requires": {
|
||||||
|
"is-typedarray": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"ua-parser-js": {
|
"ua-parser-js": {
|
||||||
"version": "0.7.21",
|
"version": "0.7.21",
|
||||||
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.21.tgz",
|
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.21.tgz",
|
||||||
|
@ -16025,6 +16165,14 @@
|
||||||
"imurmurhash": "^0.1.4"
|
"imurmurhash": "^0.1.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"unique-string": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==",
|
||||||
|
"requires": {
|
||||||
|
"crypto-random-string": "^2.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"unist-util-find-all-after": {
|
"unist-util-find-all-after": {
|
||||||
"version": "1.0.5",
|
"version": "1.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/unist-util-find-all-after/-/unist-util-find-all-after-1.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/unist-util-find-all-after/-/unist-util-find-all-after-1.0.5.tgz",
|
||||||
|
|
|
@ -23,11 +23,11 @@
|
||||||
"release": "npm-run-all contributors changelog",
|
"release": "npm-run-all contributors changelog",
|
||||||
"test": "npm-run-all test:*",
|
"test": "npm-run-all test:*",
|
||||||
"test:backend": "nyc --reporter=lcovonly mocha --reporter=min test/backend",
|
"test:backend": "nyc --reporter=lcovonly mocha --reporter=min test/backend",
|
||||||
"test:frontend": "cross-env NODE_ENV=development node test/frontend/runner.js",
|
"test:frontend": "cross-env NODE_ENV=development FXA_REQUIRED=false node test/frontend/runner.js",
|
||||||
"test:report": "nyc report --reporter=html",
|
"test:report": "nyc report --reporter=html",
|
||||||
"test-integration": "cross-env NODE_ENV=development wdio test/wdio.docker.conf.js",
|
"test-integration": "cross-env NODE_ENV=development wdio test/wdio.docker.conf.js",
|
||||||
"circleci-test-integration": "echo 'webdriverio tests need to be updated to node 12'",
|
"circleci-test-integration": "echo 'webdriverio tests need to be updated to node 12'",
|
||||||
"start": "npm run clean && cross-env NODE_ENV=development L10N_DEV=true FXA_CLIENT_ID=fced6b5e3f4c66b9 BASE_URL=http://localhost:8080 webpack-dev-server --mode=development",
|
"start": "npm run clean && cross-env NODE_ENV=development L10N_DEV=true FXA_CLIENT_ID=fced6b5e3f4c66b9 BASE_URL=http://localhost:1337 webpack-dev-server --port=1337 --mode=development",
|
||||||
"android": "cross-env ANDROID=1 npm start",
|
"android": "cross-env ANDROID=1 npm start",
|
||||||
"prod": "node server/bin/prod.js"
|
"prod": "node server/bin/prod.js"
|
||||||
},
|
},
|
||||||
|
@ -134,6 +134,7 @@
|
||||||
"@fluent/bundle": "^0.13.0",
|
"@fluent/bundle": "^0.13.0",
|
||||||
"@fluent/langneg": "^0.3.0",
|
"@fluent/langneg": "^0.3.0",
|
||||||
"@google-cloud/storage": "^4.1.1",
|
"@google-cloud/storage": "^4.1.1",
|
||||||
|
"@peculiar/webcrypto": "^1.1.1",
|
||||||
"@sentry/node": "^5.8.0",
|
"@sentry/node": "^5.8.0",
|
||||||
"aws-sdk": "^2.568.0",
|
"aws-sdk": "^2.568.0",
|
||||||
"body-parser": "^1.19.0",
|
"body-parser": "^1.19.0",
|
||||||
|
@ -147,7 +148,7 @@
|
||||||
"mkdirp": "^0.5.1",
|
"mkdirp": "^0.5.1",
|
||||||
"mozlog": "^2.2.0",
|
"mozlog": "^2.2.0",
|
||||||
"node-fetch": "^2.6.0",
|
"node-fetch": "^2.6.0",
|
||||||
"redis": "^2.8.0",
|
"redis": "^3.0.2",
|
||||||
"selenium-standalone": "^6.15.6",
|
"selenium-standalone": "^6.15.6",
|
||||||
"ua-parser-js": "^0.7.20"
|
"ua-parser-js": "^0.7.20"
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
# Firefox Send is a brand name and should not be localized.
|
# Firefox Send is a brand name and should not be localized.
|
||||||
title = Firefox Send
|
title = Firefox Send
|
||||||
siteFeedback = Feedback
|
|
||||||
importingFile = Importing…
|
importingFile = Importing…
|
||||||
encryptingFile = Encrypting…
|
encryptingFile = Encrypting…
|
||||||
decryptingFile = Decrypting…
|
decryptingFile = Decrypting…
|
||||||
|
@ -109,6 +108,7 @@ legalDateStamp = Version 1.0, dated March 12, 2019
|
||||||
# A short representation of a countdown timer containing the number of days, hours, and minutes remaining as digits, example "2d 11h 56m"
|
# A short representation of a countdown timer containing the number of days, hours, and minutes remaining as digits, example "2d 11h 56m"
|
||||||
expiresDaysHoursMinutes = { $days }d { $hours }h { $minutes }m
|
expiresDaysHoursMinutes = { $days }d { $hours }h { $minutes }m
|
||||||
addFilesButton = Select files to upload
|
addFilesButton = Select files to upload
|
||||||
|
trustWarningMessage = Make sure you trust your recipient when sharing sensitive data.
|
||||||
uploadButton = Upload
|
uploadButton = Upload
|
||||||
# the first part of the string 'Drag and drop files or click to send up to 1GB'
|
# the first part of the string 'Drag and drop files or click to send up to 1GB'
|
||||||
dragAndDropFiles = Drag and drop files
|
dragAndDropFiles = Drag and drop files
|
||||||
|
@ -145,3 +145,33 @@ shareLinkButton = Share link
|
||||||
shareMessage = Download “{ $name }” with { -send-brand }: simple, safe file sharing
|
shareMessage = Download “{ $name }” with { -send-brand }: simple, safe file sharing
|
||||||
trailheadPromo = There is a way to protect your privacy. Join Firefox.
|
trailheadPromo = There is a way to protect your privacy. Join Firefox.
|
||||||
learnMore = Learn more.
|
learnMore = Learn more.
|
||||||
|
downloadFlagged = This link has been disabled for violating the terms of service.
|
||||||
|
downloadConfirmTitle = One more thing
|
||||||
|
downloadConfirmDescription = Make sure you trust the person who sent you this file because we can’t verify that it will not harm your device.
|
||||||
|
# This string has a special case for '1' and [other] (default). If necessary for
|
||||||
|
# your language, you can add {$count} to your translations and use the
|
||||||
|
# standard CLDR forms, or only use the form for [other] if both strings should
|
||||||
|
# be identical.
|
||||||
|
downloadTrustCheckbox =
|
||||||
|
{ $count ->
|
||||||
|
[one] I trust the person who sent this file
|
||||||
|
*[other] I trust the person who sent these files
|
||||||
|
}
|
||||||
|
# This string has a special case for '1' and [other] (default). If necessary for
|
||||||
|
# your language, you can add {$count} to your translations and use the
|
||||||
|
# standard CLDR forms, or only use the form for [other] if both strings should
|
||||||
|
# be identical.
|
||||||
|
reportFile =
|
||||||
|
{ $count ->
|
||||||
|
[one] Report this file as suspicious
|
||||||
|
*[other] Report these files as suspicious
|
||||||
|
}
|
||||||
|
reportDescription = Help us understand what’s going on. What do you think is wrong with these files?
|
||||||
|
reportUnknownDescription = Please go to the url of the link you wish to report and click “{ reportFile }”.
|
||||||
|
reportButton = Report
|
||||||
|
reportReasonMalware = These files contain malware or are part of a phishing attack.
|
||||||
|
reportReasonPii = These files contain personally identifiable information about me.
|
||||||
|
reportReasonAbuse = These files contain illegal or abusive content.
|
||||||
|
reportReasonCopyright = To report copyright or trademark infringement, use the process described at <a>this page</a>.
|
||||||
|
reportedTitle = Files Reported
|
||||||
|
reportedDescription = Thank you. We have received your report on these files.
|
||||||
|
|
|
@ -96,6 +96,28 @@ function statDeleteEvent(data) {
|
||||||
return sendBatch([event]);
|
return sendBatch([event]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function statReportEvent(data) {
|
||||||
|
const loc = location(data.ip);
|
||||||
|
const event = {
|
||||||
|
session_id: -1,
|
||||||
|
country: loc.country,
|
||||||
|
region: loc.state,
|
||||||
|
user_id: userId(data.id, data.owner),
|
||||||
|
app_version: pkg.version,
|
||||||
|
time: truncateToHour(Date.now()),
|
||||||
|
event_type: 'server_report',
|
||||||
|
event_properties: {
|
||||||
|
reason: data.reason,
|
||||||
|
agent: data.agent,
|
||||||
|
download_limit: data.dlimit,
|
||||||
|
download_count: data.download_count,
|
||||||
|
ttl: data.ttl
|
||||||
|
},
|
||||||
|
event_id: data.download_count + 1
|
||||||
|
};
|
||||||
|
return sendBatch([event]);
|
||||||
|
}
|
||||||
|
|
||||||
function clientEvent(event, ua, language, session_id, deltaT, platform, ip) {
|
function clientEvent(event, ua, language, session_id, deltaT, platform, ip) {
|
||||||
const loc = location(ip);
|
const loc = location(ip);
|
||||||
const ep = event.event_properties || {};
|
const ep = event.event_properties || {};
|
||||||
|
@ -173,6 +195,7 @@ module.exports = {
|
||||||
statUploadEvent,
|
statUploadEvent,
|
||||||
statDownloadEvent,
|
statDownloadEvent,
|
||||||
statDeleteEvent,
|
statDeleteEvent,
|
||||||
|
statReportEvent,
|
||||||
clientEvent,
|
clientEvent,
|
||||||
sendBatch
|
sendBatch
|
||||||
};
|
};
|
||||||
|
|
|
@ -14,7 +14,7 @@ module.exports = function(app, devServer) {
|
||||||
expressWs(wsapp, null, { perMessageDeflate: false });
|
expressWs(wsapp, null, { perMessageDeflate: false });
|
||||||
routes(wsapp);
|
routes(wsapp);
|
||||||
wsapp.ws('/api/ws', require('../routes/ws'));
|
wsapp.ws('/api/ws', require('../routes/ws'));
|
||||||
wsapp.listen(8081, config.listen_address);
|
wsapp.listen(1338, config.listen_address);
|
||||||
|
|
||||||
assets.setMiddleware(devServer.middleware);
|
assets.setMiddleware(devServer.middleware);
|
||||||
app.use(morgan('dev', { stream: process.stderr }));
|
app.use(morgan('dev', { stream: process.stderr }));
|
||||||
|
|
|
@ -120,6 +120,11 @@ const conf = convict({
|
||||||
default: '',
|
default: '',
|
||||||
env: 'SENTRY_DSN'
|
env: 'SENTRY_DSN'
|
||||||
},
|
},
|
||||||
|
sentry_host: {
|
||||||
|
format: String,
|
||||||
|
default: 'https://sentry.prod.mozaws.net',
|
||||||
|
env: 'SENTRY_HOST'
|
||||||
|
},
|
||||||
env: {
|
env: {
|
||||||
format: ['production', 'development', 'test'],
|
format: ['production', 'development', 'test'],
|
||||||
default: 'development',
|
default: 'development',
|
||||||
|
@ -150,9 +155,14 @@ const conf = convict({
|
||||||
default: `${tmpdir()}${path.sep}send-${randomBytes(4).toString('hex')}`,
|
default: `${tmpdir()}${path.sep}send-${randomBytes(4).toString('hex')}`,
|
||||||
env: 'FILE_DIR'
|
env: 'FILE_DIR'
|
||||||
},
|
},
|
||||||
|
fxa_required: {
|
||||||
|
format: Boolean,
|
||||||
|
default: true,
|
||||||
|
env: 'FXA_REQUIRED'
|
||||||
|
},
|
||||||
fxa_url: {
|
fxa_url: {
|
||||||
format: 'url',
|
format: 'url',
|
||||||
default: 'https://send-fxa.dev.lcip.org',
|
default: 'http://localhost:3030',
|
||||||
env: 'FXA_URL'
|
env: 'FXA_URL'
|
||||||
},
|
},
|
||||||
fxa_client_id: {
|
fxa_client_id: {
|
||||||
|
|
|
@ -0,0 +1,53 @@
|
||||||
|
const { Crypto } = require('@peculiar/webcrypto');
|
||||||
|
const crypto = new Crypto();
|
||||||
|
|
||||||
|
const encoder = new TextEncoder();
|
||||||
|
const decoder = new TextDecoder();
|
||||||
|
|
||||||
|
module.exports = class Keychain {
|
||||||
|
constructor(secretKeyB64) {
|
||||||
|
if (secretKeyB64) {
|
||||||
|
this.rawSecret = new Uint8Array(Buffer.from(secretKeyB64, 'base64'));
|
||||||
|
} else {
|
||||||
|
throw new Error('key is required');
|
||||||
|
}
|
||||||
|
this.secretKeyPromise = crypto.subtle.importKey(
|
||||||
|
'raw',
|
||||||
|
this.rawSecret,
|
||||||
|
'HKDF',
|
||||||
|
false,
|
||||||
|
['deriveKey']
|
||||||
|
);
|
||||||
|
this.metaKeyPromise = this.secretKeyPromise.then(function(secretKey) {
|
||||||
|
return crypto.subtle.deriveKey(
|
||||||
|
{
|
||||||
|
name: 'HKDF',
|
||||||
|
salt: new Uint8Array(),
|
||||||
|
info: encoder.encode('metadata'),
|
||||||
|
hash: 'SHA-256'
|
||||||
|
},
|
||||||
|
secretKey,
|
||||||
|
{
|
||||||
|
name: 'AES-GCM',
|
||||||
|
length: 128
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
['decrypt']
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async decryptMetadata(ciphertext) {
|
||||||
|
const metaKey = await this.metaKeyPromise;
|
||||||
|
const plaintext = await crypto.subtle.decrypt(
|
||||||
|
{
|
||||||
|
name: 'AES-GCM',
|
||||||
|
iv: new Uint8Array(12),
|
||||||
|
tagLength: 128
|
||||||
|
},
|
||||||
|
metaKey,
|
||||||
|
ciphertext
|
||||||
|
);
|
||||||
|
return JSON.parse(decoder.decode(plaintext));
|
||||||
|
}
|
||||||
|
};
|
|
@ -7,6 +7,9 @@ class Metadata {
|
||||||
this.metadata = obj.metadata;
|
this.metadata = obj.metadata;
|
||||||
this.auth = obj.auth;
|
this.auth = obj.auth;
|
||||||
this.nonce = obj.nonce;
|
this.nonce = obj.nonce;
|
||||||
|
this.flagged = !!obj.flagged;
|
||||||
|
this.dead = !!obj.dead;
|
||||||
|
this.key = obj.key;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -46,7 +46,7 @@ module.exports = {
|
||||||
if (id && ownerToken) {
|
if (id && ownerToken) {
|
||||||
try {
|
try {
|
||||||
req.meta = await storage.metadata(id);
|
req.meta = await storage.metadata(id);
|
||||||
if (!req.meta) {
|
if (!req.meta || req.meta.dead) {
|
||||||
return res.sendStatus(404);
|
return res.sendStatus(404);
|
||||||
}
|
}
|
||||||
const metaOwner = Buffer.from(req.meta.owner, 'utf8');
|
const metaOwner = Buffer.from(req.meta.owner, 'utf8');
|
||||||
|
|
|
@ -6,7 +6,7 @@ module.exports = async function(req, res) {
|
||||||
const id = req.params.id;
|
const id = req.params.id;
|
||||||
const meta = req.meta;
|
const meta = req.meta;
|
||||||
const ttl = await storage.ttl(id);
|
const ttl = await storage.ttl(id);
|
||||||
await storage.del(id);
|
await storage.kill(id);
|
||||||
res.sendStatus(200);
|
res.sendStatus(200);
|
||||||
statDeleteEvent({
|
statDeleteEvent({
|
||||||
id,
|
id,
|
||||||
|
|
|
@ -7,6 +7,9 @@ module.exports = async function(req, res) {
|
||||||
const id = req.params.id;
|
const id = req.params.id;
|
||||||
try {
|
try {
|
||||||
const meta = req.meta;
|
const meta = req.meta;
|
||||||
|
if (meta.dead || meta.flagged) {
|
||||||
|
return res.sendStatus(404);
|
||||||
|
}
|
||||||
const fileStream = await storage.get(id);
|
const fileStream = await storage.get(id);
|
||||||
let cancelled = false;
|
let cancelled = false;
|
||||||
|
|
||||||
|
@ -33,7 +36,7 @@ module.exports = async function(req, res) {
|
||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
if (dl >= dlimit) {
|
if (dl >= dlimit) {
|
||||||
await storage.del(id);
|
await storage.kill(id);
|
||||||
} else {
|
} else {
|
||||||
await storage.incrementField(id, 'dl');
|
await storage.incrementField(id, 'dl');
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,9 @@ const storage = require('../storage');
|
||||||
module.exports = async (req, res) => {
|
module.exports = async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const meta = await storage.metadata(req.params.id);
|
const meta = await storage.metadata(req.params.id);
|
||||||
|
if (!meta || meta.dead) {
|
||||||
|
return res.sendStatus(404);
|
||||||
|
}
|
||||||
res.set('WWW-Authenticate', `send-v1 ${meta.nonce}`);
|
res.set('WWW-Authenticate', `send-v1 ${meta.nonce}`);
|
||||||
res.send({
|
res.send({
|
||||||
requiresPassword: meta.pwd
|
requiresPassword: meta.pwd
|
||||||
|
|
|
@ -32,55 +32,57 @@ module.exports = function(app) {
|
||||||
});
|
});
|
||||||
if (!IS_DEV) {
|
if (!IS_DEV) {
|
||||||
let csp = {
|
let csp = {
|
||||||
directives: {
|
directives: {
|
||||||
defaultSrc: ["'self'"],
|
defaultSrc: ["'self'"],
|
||||||
connectSrc: [
|
connectSrc: [
|
||||||
"'self'",
|
"'self'",
|
||||||
'wss://*.dev.lcip.org',
|
config.base_url.replace(/^https:\/\//, 'wss://')
|
||||||
'wss://*.send.nonprod.cloudops.mozgcp.net',
|
],
|
||||||
config.base_url.replace(/^https:\/\//, 'wss://'),
|
imgSrc: ["'self'"],
|
||||||
'https://*.dev.lcip.org',
|
scriptSrc: [
|
||||||
'https://accounts.firefox.com',
|
"'self'",
|
||||||
'https://*.accounts.firefox.com',
|
function(req) {
|
||||||
'https://sentry.prod.mozaws.net'
|
return `'nonce-${req.cspNonce}'`;
|
||||||
],
|
}
|
||||||
imgSrc: [
|
],
|
||||||
"'self'",
|
formAction: ["'none'"],
|
||||||
'https://*.dev.lcip.org',
|
frameAncestors: ["'none'"],
|
||||||
'https://firefoxusercontent.com',
|
objectSrc: ["'none'"],
|
||||||
'https://secure.gravatar.com'
|
reportUri: '/__cspreport__'
|
||||||
],
|
|
||||||
scriptSrc: [
|
|
||||||
"'self'",
|
|
||||||
function(req) {
|
|
||||||
return `'nonce-${req.cspNonce}'`;
|
|
||||||
}
|
|
||||||
],
|
|
||||||
formAction: ["'none'"],
|
|
||||||
frameAncestors: ["'none'"],
|
|
||||||
objectSrc: ["'none'"],
|
|
||||||
reportUri: '/__cspreport__'
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
csp.directives.connectSrc.push(config.base_url.replace(/^https:\/\//,'wss://'))
|
if (config.fxa_client_id) {
|
||||||
if(config.fxa_csp_oauth_url != ""){
|
csp.directives.connectSrc.push('https://accounts.firefox.com');
|
||||||
csp.directives.connectSrc.push(config.fxa_csp_oauth_url)
|
csp.directives.connectSrc.push('https://*.accounts.firefox.com');
|
||||||
|
csp.directives.imgSrc.push('https://firefoxusercontent.com');
|
||||||
|
csp.directives.imgSrc.push('https://secure.gravatar.com');
|
||||||
}
|
}
|
||||||
if(config.fxa_csp_content_url != "" ){
|
if (config.sentry_id) {
|
||||||
csp.directives.connectSrc.push(config.fxa_csp_content_url)
|
csp.directives.connectSrc.push(config.sentry_host);
|
||||||
}
|
}
|
||||||
if(config.fxa_csp_profile_url != "" ){
|
if (
|
||||||
csp.directives.connectSrc.push(config.fxa_csp_profile_url)
|
config.base_url.test(/^https:\/\/.*\.dev\.lcip\.org$/) ||
|
||||||
|
config.base_url.test(
|
||||||
|
/^https:\/\/.*\.send\.nonprod\.cloudops\.mozgcp\.net$/
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
csp.directives.connectSrc.push('https://*.dev.lcip.org');
|
||||||
|
csp.directives.imgSrc.push('https://*.dev.lcip.org');
|
||||||
}
|
}
|
||||||
if(config.fxa_csp_profileimage_url != ""){
|
if (config.fxa_csp_oauth_url != '') {
|
||||||
csp.directives.imgSrc.push(config.fxa_csp_profileimage_url)
|
csp.directives.connectSrc.push(config.fxa_csp_oauth_url);
|
||||||
|
}
|
||||||
|
if (config.fxa_csp_content_url != '') {
|
||||||
|
csp.directives.connectSrc.push(config.fxa_csp_content_url);
|
||||||
|
}
|
||||||
|
if (config.fxa_csp_profile_url != '') {
|
||||||
|
csp.directives.connectSrc.push(config.fxa_csp_profile_url);
|
||||||
|
}
|
||||||
|
if (config.fxa_csp_profileimage_url != '') {
|
||||||
|
csp.directives.imgSrc.push(config.fxa_csp_profileimage_url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
app.use(helmet.contentSecurityPolicy(csp));
|
||||||
app.use(
|
|
||||||
helmet.contentSecurityPolicy(csp)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
app.use(function(req, res, next) {
|
app.use(function(req, res, next) {
|
||||||
|
@ -101,6 +103,7 @@ module.exports = function(app) {
|
||||||
app.get('/oauth', language, pages.blank);
|
app.get('/oauth', language, pages.blank);
|
||||||
app.get('/legal', language, pages.legal);
|
app.get('/legal', language, pages.legal);
|
||||||
app.get('/login', language, pages.index);
|
app.get('/login', language, pages.index);
|
||||||
|
app.get('/report', language, pages.blank);
|
||||||
app.get('/app.webmanifest', language, require('./webmanifest'));
|
app.get('/app.webmanifest', language, require('./webmanifest'));
|
||||||
app.get(`/download/:id${ID_REGEX}`, language, pages.download);
|
app.get(`/download/:id${ID_REGEX}`, language, pages.download);
|
||||||
app.get('/unsupported/:reason', language, pages.unsupported);
|
app.get('/unsupported/:reason', language, pages.unsupported);
|
||||||
|
@ -114,7 +117,7 @@ module.exports = function(app) {
|
||||||
app.get(`/api/metadata/:id${ID_REGEX}`, auth.hmac, require('./metadata'));
|
app.get(`/api/metadata/:id${ID_REGEX}`, auth.hmac, require('./metadata'));
|
||||||
app.get('/api/filelist/:id([\\w-]{16})', auth.fxa, filelist.get);
|
app.get('/api/filelist/:id([\\w-]{16})', auth.fxa, filelist.get);
|
||||||
app.post('/api/filelist/:id([\\w-]{16})', auth.fxa, filelist.post);
|
app.post('/api/filelist/:id([\\w-]{16})', auth.fxa, filelist.post);
|
||||||
app.post('/api/upload', auth.fxa, require('./upload'));
|
// app.post('/api/upload', auth.fxa, require('./upload'));
|
||||||
app.post(`/api/delete/:id${ID_REGEX}`, auth.owner, require('./delete'));
|
app.post(`/api/delete/:id${ID_REGEX}`, auth.owner, require('./delete'));
|
||||||
app.post(`/api/password/:id${ID_REGEX}`, auth.owner, require('./password'));
|
app.post(`/api/password/:id${ID_REGEX}`, auth.owner, require('./password'));
|
||||||
app.post(
|
app.post(
|
||||||
|
@ -124,6 +127,7 @@ module.exports = function(app) {
|
||||||
require('./params')
|
require('./params')
|
||||||
);
|
);
|
||||||
app.post(`/api/info/:id${ID_REGEX}`, auth.owner, require('./info'));
|
app.post(`/api/info/:id${ID_REGEX}`, auth.owner, require('./info'));
|
||||||
|
app.post(`/api/report/:id${ID_REGEX}`, require('./report'));
|
||||||
app.post('/api/metrics', require('./metrics'));
|
app.post('/api/metrics', require('./metrics'));
|
||||||
app.get('/__version__', function(req, res) {
|
app.get('/__version__', function(req, res) {
|
||||||
// eslint-disable-next-line node/no-missing-require
|
// eslint-disable-next-line node/no-missing-require
|
||||||
|
|
|
@ -4,9 +4,13 @@ module.exports = async function(req, res) {
|
||||||
const id = req.params.id;
|
const id = req.params.id;
|
||||||
const meta = req.meta;
|
const meta = req.meta;
|
||||||
try {
|
try {
|
||||||
|
if (meta.dead && !meta.flagged) {
|
||||||
|
return res.sendStatus(404);
|
||||||
|
}
|
||||||
const ttl = await storage.ttl(id);
|
const ttl = await storage.ttl(id);
|
||||||
res.send({
|
res.send({
|
||||||
metadata: meta.metadata,
|
metadata: meta.metadata,
|
||||||
|
flagged: !!meta.flagged,
|
||||||
finalDownload: meta.dl + 1 === meta.dlimit,
|
finalDownload: meta.dl + 1 === meta.dlimit,
|
||||||
ttl
|
ttl
|
||||||
});
|
});
|
||||||
|
|
|
@ -23,14 +23,17 @@ module.exports = {
|
||||||
const id = req.params.id;
|
const id = req.params.id;
|
||||||
const appState = await state(req);
|
const appState = await state(req);
|
||||||
try {
|
try {
|
||||||
const { nonce, pwd } = await storage.metadata(id);
|
const { nonce, pwd, dead, flagged } = await storage.metadata(id);
|
||||||
|
if (dead && !flagged) {
|
||||||
|
return next();
|
||||||
|
}
|
||||||
res.set('WWW-Authenticate', `send-v1 ${nonce}`);
|
res.set('WWW-Authenticate', `send-v1 ${nonce}`);
|
||||||
res.send(
|
res.send(
|
||||||
stripEvents(
|
stripEvents(
|
||||||
routes().toString(
|
routes().toString(
|
||||||
`/download/${id}`,
|
`/download/${id}`,
|
||||||
Object.assign(appState, {
|
Object.assign(appState, {
|
||||||
downloadMetadata: { nonce, pwd }
|
downloadMetadata: { nonce, pwd, flagged }
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
const storage = require('../storage');
|
||||||
|
const Keychain = require('../keychain');
|
||||||
|
const { statReportEvent } = require('../amplitude');
|
||||||
|
|
||||||
|
module.exports = async function(req, res) {
|
||||||
|
try {
|
||||||
|
const id = req.params.id;
|
||||||
|
const meta = await storage.metadata(id);
|
||||||
|
if (meta.flagged) {
|
||||||
|
return res.sendStatus(200);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const key = req.body.key;
|
||||||
|
const keychain = new Keychain(key);
|
||||||
|
const metadata = await keychain.decryptMetadata(
|
||||||
|
Buffer.from(meta.metadata, 'base64')
|
||||||
|
);
|
||||||
|
if (metadata.manifest) {
|
||||||
|
storage.flag(id, key);
|
||||||
|
statReportEvent({
|
||||||
|
id,
|
||||||
|
ip: req.ip,
|
||||||
|
owner: meta.owner,
|
||||||
|
reason: req.body.reason,
|
||||||
|
download_limit: meta.dlimit,
|
||||||
|
download_count: meta.dl,
|
||||||
|
agent: req.ua.browser.name || req.ua.ua.substring(0, 6)
|
||||||
|
});
|
||||||
|
return res.sendStatus(200);
|
||||||
|
}
|
||||||
|
res.sendStatus(400);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
res.sendStatus(400);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
res.sendStatus(404);
|
||||||
|
}
|
||||||
|
};
|
|
@ -46,7 +46,8 @@ module.exports = function(ws, req) {
|
||||||
!auth ||
|
!auth ||
|
||||||
timeLimit <= 0 ||
|
timeLimit <= 0 ||
|
||||||
timeLimit > maxExpireSeconds ||
|
timeLimit > maxExpireSeconds ||
|
||||||
dlimit > maxDownloads
|
dlimit > maxDownloads ||
|
||||||
|
(config.fxa_required && !user)
|
||||||
) {
|
) {
|
||||||
ws.send(
|
ws.send(
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
|
|
|
@ -15,7 +15,11 @@ module.exports = async function(req) {
|
||||||
try {
|
try {
|
||||||
authConfig = await getFxaConfig();
|
authConfig = await getFxaConfig();
|
||||||
authConfig.client_id = config.fxa_client_id;
|
authConfig.client_id = config.fxa_client_id;
|
||||||
|
authConfig.fxa_required = config.fxa_required;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
if (config.auth_required) {
|
||||||
|
throw new Error('fxa_required is set but no config was found');
|
||||||
|
}
|
||||||
// continue without accounts
|
// continue without accounts
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,7 +33,15 @@ class DB {
|
||||||
}
|
}
|
||||||
|
|
||||||
async getPrefixedId(id) {
|
async getPrefixedId(id) {
|
||||||
const prefix = await this.redis.hgetAsync(id, 'prefix');
|
const [prefix, dead, flagged] = await this.redis.hmgetAsync(
|
||||||
|
id,
|
||||||
|
'prefix',
|
||||||
|
'dead',
|
||||||
|
'flagged'
|
||||||
|
);
|
||||||
|
if (dead || flagged) {
|
||||||
|
throw new Error('id not available');
|
||||||
|
}
|
||||||
return `${prefix}-${id}`;
|
return `${prefix}-${id}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,9 +59,10 @@ class DB {
|
||||||
const prefix = getPrefix(expireSeconds);
|
const prefix = getPrefix(expireSeconds);
|
||||||
const filePath = `${prefix}-${id}`;
|
const filePath = `${prefix}-${id}`;
|
||||||
await this.storage.set(filePath, file);
|
await this.storage.set(filePath, file);
|
||||||
this.redis.hset(id, 'prefix', prefix);
|
|
||||||
if (meta) {
|
if (meta) {
|
||||||
this.redis.hmset(id, meta);
|
this.redis.hmset(id, { prefix, ...meta });
|
||||||
|
} else {
|
||||||
|
this.redis.hset(id, 'prefix', prefix);
|
||||||
}
|
}
|
||||||
this.redis.expire(id, expireSeconds);
|
this.redis.expire(id, expireSeconds);
|
||||||
}
|
}
|
||||||
|
@ -66,6 +75,16 @@ class DB {
|
||||||
this.redis.hincrby(id, key, increment);
|
this.redis.hincrby(id, key, increment);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
kill(id) {
|
||||||
|
this.redis.hset(id, 'dead', 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
async flag(id, key) {
|
||||||
|
// this.redis.persist(id);
|
||||||
|
this.redis.hmset(id, { flagged: 1, key });
|
||||||
|
this.redis.sadd('flagged', id);
|
||||||
|
}
|
||||||
|
|
||||||
async del(id) {
|
async del(id) {
|
||||||
const filePath = await this.getPrefixedId(id);
|
const filePath = await this.getPrefixedId(id);
|
||||||
this.storage.del(filePath);
|
this.storage.del(filePath);
|
||||||
|
|
|
@ -23,6 +23,8 @@ module.exports = function(config) {
|
||||||
client.ttlAsync = promisify(client.ttl);
|
client.ttlAsync = promisify(client.ttl);
|
||||||
client.hgetallAsync = promisify(client.hgetall);
|
client.hgetallAsync = promisify(client.hgetall);
|
||||||
client.hgetAsync = promisify(client.hget);
|
client.hgetAsync = promisify(client.hget);
|
||||||
|
client.hmgetAsync = promisify(client.hmget);
|
||||||
client.pingAsync = promisify(client.ping);
|
client.pingAsync = promisify(client.ping);
|
||||||
|
client.existsAsync = promisify(client.exists);
|
||||||
return client;
|
return client;
|
||||||
};
|
};
|
||||||
|
|
|
@ -259,6 +259,14 @@ module.exports = {
|
||||||
full: '100%',
|
full: '100%',
|
||||||
screen: '100vh'
|
screen: '100vh'
|
||||||
},
|
},
|
||||||
|
flex: {
|
||||||
|
'1': '1 1 0%',
|
||||||
|
auto: '1 1 auto',
|
||||||
|
initial: '0 1 auto',
|
||||||
|
none: 'none',
|
||||||
|
half: '0 0 50%',
|
||||||
|
full: '0 0 100%'
|
||||||
|
},
|
||||||
minWidth: {
|
minWidth: {
|
||||||
'0': '0',
|
'0': '0',
|
||||||
full: '100%'
|
full: '100%'
|
||||||
|
|
|
@ -2,7 +2,7 @@ const sinon = require('sinon');
|
||||||
const proxyquire = require('proxyquire').noCallThru();
|
const proxyquire = require('proxyquire').noCallThru();
|
||||||
|
|
||||||
const storage = {
|
const storage = {
|
||||||
del: sinon.stub(),
|
kill: sinon.stub(),
|
||||||
ttl: sinon.stub()
|
ttl: sinon.stub()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -24,19 +24,19 @@ const delRoute = proxyquire('../../server/routes/delete', {
|
||||||
|
|
||||||
describe('/api/delete', function() {
|
describe('/api/delete', function() {
|
||||||
afterEach(function() {
|
afterEach(function() {
|
||||||
storage.del.reset();
|
storage.kill.reset();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('calls storage.del with the id parameter', async function() {
|
it('calls storage.kill with the id parameter', async function() {
|
||||||
const req = request('x');
|
const req = request('x');
|
||||||
const res = response();
|
const res = response();
|
||||||
await delRoute(req, res);
|
await delRoute(req, res);
|
||||||
sinon.assert.calledWith(storage.del, 'x');
|
sinon.assert.calledWith(storage.kill, 'x');
|
||||||
sinon.assert.calledWith(res.sendStatus, 200);
|
sinon.assert.calledWith(res.sendStatus, 200);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('sends a 404 on failure', async function() {
|
it('sends a 404 on failure', async function() {
|
||||||
storage.del.returns(Promise.reject(new Error()));
|
storage.kill.returns(Promise.reject(new Error()));
|
||||||
const res = response();
|
const res = response();
|
||||||
await delRoute(request('x'), res);
|
await delRoute(request('x'), res);
|
||||||
sinon.assert.calledWith(res.sendStatus, 404);
|
sinon.assert.calledWith(res.sendStatus, 404);
|
||||||
|
|
|
@ -6,7 +6,7 @@ const storage = {
|
||||||
length: sinon.stub()
|
length: sinon.stub()
|
||||||
};
|
};
|
||||||
|
|
||||||
function request(id, meta) {
|
function request(id, meta = {}) {
|
||||||
return {
|
return {
|
||||||
params: { id },
|
params: { id },
|
||||||
meta
|
meta
|
||||||
|
|
|
@ -133,7 +133,12 @@ describe('Storage', function() {
|
||||||
};
|
};
|
||||||
await storage.set('x', null, m);
|
await storage.set('x', null, m);
|
||||||
const meta = await storage.metadata('x');
|
const meta = await storage.metadata('x');
|
||||||
assert.deepEqual(meta, m);
|
assert.deepEqual(meta, {
|
||||||
|
...m,
|
||||||
|
dead: false,
|
||||||
|
flagged: false,
|
||||||
|
key: undefined
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -207,7 +207,7 @@ const web = {
|
||||||
host: '0.0.0.0',
|
host: '0.0.0.0',
|
||||||
proxy: {
|
proxy: {
|
||||||
'/api/ws': {
|
'/api/ws': {
|
||||||
target: 'ws://localhost:8081',
|
target: 'ws://localhost:1338',
|
||||||
ws: true,
|
ws: true,
|
||||||
secure: false
|
secure: false
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue