Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#565 Provide optional SRI hash when displaying embed code #730

Draft
wants to merge 11 commits into
base: development
Choose a base branch
from
84 changes: 54 additions & 30 deletions auditorium/gulpfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
*/

var fs = require('fs')
var path = require('path');
var gulp = require('gulp')
var clean = require('gulp-clean')
var browserify = require('browserify')
Expand All @@ -13,17 +14,21 @@ var buffer = require('vinyl-buffer')
var gap = require('gulp-append-prepend')
var Readable = require('stream').Readable
var linguasFile = require('linguas-file')
var crypto = require('crypto');

var pkg = require('./package.json')

var defaultLocale = 'en'
var linguas = !process.env.SKIP_LOCALES
? linguasFile.parse(fs.readFileSync('./locales/LINGUAS', 'utf-8'))
: []
var linguas = !process.env.SKIP_LOCALES ?
linguasFile.parse(fs.readFileSync('./locales/LINGUAS', 'utf-8')) :
[]

gulp.task('clean:pre', function () {
return gulp
.src('./dist', { read: false, allowEmpty: true })
.src('./dist', {
read: false,
allowEmpty: true
})
.pipe(clean())
})

Expand All @@ -37,7 +42,7 @@ gulp.task('default', gulp.series(
}))
))

function createLocalizedBundle (locale) {
function createLocalizedBundle(locale) {
var dest = './dist/' + locale + '/auditorium/'
var scriptTask = makeScriptTask(dest, locale)
scriptTask.displayName = 'script:' + locale
Expand All @@ -46,32 +51,43 @@ function createLocalizedBundle (locale) {
return gulp.parallel(scriptTask, vendorTask)
}

function makeScriptTask (dest, locale) {
function makeScriptTask(dest, locale) {
return function () {
var transforms = JSON.parse(JSON.stringify(pkg.browserify.transform))
// we are setting this at process level so that it propagates to
// dependencies that also require setting it
process.env.LOCALE = locale
process.env.SCRIPT_INTEGRITY_HASH = "unkown"
var b = browserify({
entries: './index.js',
// See: https://github.com/nikku/karma-browserify/issues/130#issuecomment-120036815
postFilter: function (id, file, currentPkg) {
if (currentPkg && currentPkg.name === pkg.name) {
currentPkg.browserify.transform = []
}
return true
},
transform: transforms.map(function (transform) {
if (transform === '@offen/l10nify' || (Array.isArray(transform) && transform[0] === '@offen/l10nify')) {
return ['@offen/l10nify']
}
if (transform === 'envify' || (Array.isArray(transform) && transform[0] === 'envify')) {
return ['envify', { LOCALE: locale }]
}
return transform
entries: './index.js',
// See: https://github.com/nikku/karma-browserify/issues/130#issuecomment-120036815
postFilter: function (id, file, currentPkg) {
if (currentPkg && currentPkg.name === pkg.name) {
currentPkg.browserify.transform = []
}
return true
},
transform: transforms.map(function (transform) {
if (transform === '@offen/l10nify' || (Array.isArray(transform) && transform[0] === '@offen/l10nify')) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be good if you could undo these formatting only changes as well even if standard is ok with them. It'll be easier to understand what changed if we don't have the additional noise in the diff.

return ['@offen/l10nify']
}
if (transform === 'envify' || (Array.isArray(transform) && transform[0] === 'envify')) {
let scriptContent = fs.readFileSync('./script/index.js', 'utf8')

console.log(scriptContent);
process.env.SCRIPT_INTEGRITY_HASH = crypto.createHash('sha256').update(scriptContent).digest('base64')
console.log(process.env.SCRIPT_INTEGRITY_HASH)

return ['envify', {
LOCALE: locale
}]
}
return transform
})
})
.transform('aliasify', {
global: true
})
})
.transform('aliasify', { global: true })

if (locale !== defaultLocale) {
b.add(configureDatepickerLocale(locale))
Expand All @@ -90,18 +106,23 @@ function makeScriptTask (dest, locale) {
.pipe(gap.prependText('/**'))
.pipe(rev())
.pipe(gulp.dest(dest))
.pipe(rev.manifest(dest + '/rev-manifest.json', { base: dest, merge: true }))
.pipe(rev.manifest(dest + '/rev-manifest.json', {
base: dest,
merge: true
}))
.pipe(gulp.dest(dest))
}
}

function makeVendorTask (dest) {
function makeVendorTask(dest) {
return function () {
var b = browserify()
return b
.require('plotly.js-basic-dist')
.require('zxcvbn')
.plugin('tinyify', { noFlat: true })
.plugin('tinyify', {
noFlat: true
})
.bundle()
.pipe(source('vendor.js'))
.pipe(buffer())
Expand All @@ -110,7 +131,10 @@ function makeVendorTask (dest) {
.pipe(gap.prependText('/**'))
.pipe(rev())
.pipe(gulp.dest(dest))
.pipe(rev.manifest(dest + '/rev-manifest.json', { base: dest, merge: true }))
.pipe(rev.manifest(dest + '/rev-manifest.json', {
base: dest,
merge: true
}))
.pipe(gulp.dest(dest))
}
}
Expand All @@ -119,15 +143,15 @@ function makeVendorTask (dest) {
// `react-datepicker`. Handling this at build time allows us to avoid including
// unused locales for non-English bundles. At development time, the default
// locale (en) will be included implicitly.
function configureDatepickerLocale (locale) {
function configureDatepickerLocale(locale) {
return Readable.from([`
const { registerLocale } = require('react-datepicker')
const locale = require('date-fns/locale/${locale}')
registerLocale('${locale}', locale)
`])
}

function configureCountriesLocale (locale) {
function configureCountriesLocale(locale) {
return Readable.from([`
const countries = require('i18n-iso-countries')
countries.registerLocale(require('i18n-iso-countries/langs/${locale}.json'))
Expand Down
19 changes: 16 additions & 3 deletions auditorium/src/views/components/auditorium/embed-code.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const { h, Fragment } = require('preact')
const { CopyToClipboard } = require('react-copy-to-clipboard')
const classnames = require('classnames')
const escapeHtml = require('escape-html')

const { useState } = require('preact/hooks')
const Collapsible = require('./../_shared/collapsible')
const Paragraph = require('./../_shared/paragraph')

Expand All @@ -19,6 +19,10 @@ const EmbedCode = (props) => {
onCopy(__('Successfully copied embed code to clipboard.'))
}

function toggleScriptDisplay () {
setActive(!useSnippetWithSRI)
}

const renderHeader = (props = {}) => {
const { handleToggle = null, isCollapsed } = props
return (
Expand Down Expand Up @@ -50,6 +54,10 @@ const EmbedCode = (props) => {
}

const snippet = `<script async src="${window.location.origin}/script.js" data-account-id="${model.account.accountId}"></script>`
const snippetWithSRI = `<script async src="${window.location.origin}/script.js" data-account-id="${model.account.accountId}" integrity="sha256-${process.env.SCRIPT_INTEGRITY_HASH}"></script>`
const [useSnippetWithSRI, setActive] = useState(false)
const embeddedSnipped = useSnippetWithSRI ? snippetWithSRI : snippet
const buttonText = useSnippetWithSRI ? __('Hide integrity hash') : __('Show with integrity hash')

const renderBody = (props = {}) => (
<div class='mw6 center ph3 mt3 mb4'>
Expand All @@ -59,17 +67,22 @@ const EmbedCode = (props) => {
<Paragraph class='ma0 mb3'>
{__('In case you are serving multiple domains from your Offen instance, please double check that the domain in this snippet matches the target account.')}
</Paragraph>
<div class='flex items-end'>
<div class='w-100 tr bb b--light-gray'>
<button onClick={toggleScriptDisplay} class='pointer w-100 w-auto-ns fw1 f7 tc bn dib br1 ph2 pv1 black bg-black-10'><span class={classnames('ml2', 'dib', 'label-toggle', { 'label-toggle--rotate': useSnippetWithSRI })} /> {buttonText}</button>
</div>
</div>
<div class='w-100 br1 ph2 pv2 bg-black-10'>
<code
class='ma0 lh-solid word-wrap'
dangerouslySetInnerHTML={{
__html: escapeHtml(snippet)
__html: escapeHtml(embeddedSnipped)
}}
/>
</div>
<CopyToClipboard
onCopy={handleCopy}
text={snippet}
text={embeddedSnipped}
>
<div class='link dim'>
<button class='pointer w-100 w-auto-ns f5 tc bn dib br1 ph3 pv2 mv3 white bg-mid-gray'>
Expand Down
25 changes: 13 additions & 12 deletions build/Dockerfile.build
Original file line number Diff line number Diff line change
Expand Up @@ -10,32 +10,33 @@ ENV ADBLOCK true
ENV DISABLE_OPENCOLLECTIVE true
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD true

FROM offen_node as auditorium
FROM offen_node as script
ARG skip_locales
ENV SKIP_LOCALES=$skip_locales
COPY ./auditorium/package.json ./auditorium/package-lock.json /code/deps/
COPY ./script/package.json ./script/package-lock.json /code/deps/
WORKDIR /code/deps
RUN npm ci
COPY ./auditorium /code/auditorium
COPY ./script /code/script
COPY ./banner.txt /code/banner.txt
COPY ./locales /code/auditorium/locales
WORKDIR /code/auditorium
RUN cp -a /code/deps/node_modules /code/auditorium/
COPY ./locales /code/script/locales
WORKDIR /code/script
RUN cp -a /code/deps/node_modules /code/script/
ENV NODE_ENV production
RUN npm run build
RUN npm run licenses

FROM offen_node as script
FROM offen_node as auditorium
ARG skip_locales
ENV SKIP_LOCALES=$skip_locales
COPY ./script/package.json ./script/package-lock.json /code/deps/
COPY ./auditorium/package.json ./auditorium/package-lock.json /code/deps/
WORKDIR /code/deps
RUN npm ci
COPY ./script /code/script
COPY ./auditorium /code/auditorium
COPY --from=script ./code/script/index.js /code/auditorium/script/index.js
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This copies the entrypoint file when instead we need to copy the bundled versions. See my comment on how to do this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this invoke the behavior we need?

COPY --from=script /code/script/dist/index.js /script/script.js

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a bundle created per locale (the script logs user facing messages in some cases which are localized) so we need t copy the entire dist directory and then pick the correct one in the build for auditorium.

See:

offen/script/gulpfile.js

Lines 43 to 44 in c7aed87

var dest = './dist/' + locale + '/'
var scriptTask = makeScriptTask(dest, locale)
and

offen/script/gulpfile.js

Lines 76 to 84 in c7aed87

return b
.plugin('tinyify')
.bundle()
.pipe(source('script.js'))
.pipe(buffer())
.pipe(gap.prependText('*/'))
.pipe(gap.prependFile('./../banner.txt'))
.pipe(gap.prependText('/**'))
.pipe(gulp.dest(dest))

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To not bother you with more questions I'll need some time to work me through the build process. To be honest it is a lot magic for me right now.

I'm not quite sure how gulp works together with the dockerfile.build furthermore my overall gulp knowledge is very limited.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you have questions while doing so, don't worry about bothering me. In any case, take your time. Thanks.

COPY ./banner.txt /code/banner.txt
COPY ./locales /code/script/locales
WORKDIR /code/script
RUN cp -a /code/deps/node_modules /code/script/
COPY ./locales /code/auditorium/locales
WORKDIR /code/auditorium
RUN cp -a /code/deps/node_modules /code/auditorium/
ENV NODE_ENV production
RUN npm run build
RUN npm run licenses
Expand Down
1 change: 1 addition & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ services:
working_dir: /offen/auditorium
environment:
LOCALE: ${LOCALE:-en}
SCRIPT_INTEGRITY_HASH: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
raLaaaa marked this conversation as resolved.
Show resolved Hide resolved
PORT: 9955
volumes:
- .:/offen
Expand Down