├── .gitignore
├── favicon.ico
├── install
├── deploy
├── privacy
├── hetzner-dpa-2021-12-30.pdf
└── index.html
├── .dynamic
├── .get
│ ├── ban-list.txt.js
│ ├── ban-list.js
│ ├── error.js
│ ├── index.js
│ ├── cancel_code.js
│ ├── delete_code.js
│ └── confirm_code.js
├── redirectToError.js
├── package.json
├── header-template.js
├── footer-template.js
├── sendMail.js
├── index-template.html
├── .post
│ └── sign.js
├── package-lock.json
└── routes.js
├── .vscode
└── settings.json
├── index.html
├── checkmark.svg
├── README.md
├── spinner.svg
└── styles.css
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .db/
3 |
--------------------------------------------------------------------------------
/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/small-tech/web0/HEAD/favicon.ico
--------------------------------------------------------------------------------
/install:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | pushd .dynamic
4 | npm install
5 | popd
6 |
--------------------------------------------------------------------------------
/deploy:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | site --sync-to=site@web0.small-web.org:public
4 |
5 |
--------------------------------------------------------------------------------
/privacy/hetzner-dpa-2021-12-30.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/small-tech/web0/HEAD/privacy/hetzner-dpa-2021-12-30.pdf
--------------------------------------------------------------------------------
/.dynamic/.get/ban-list.txt.js:
--------------------------------------------------------------------------------
1 | module.exports = (request, response) => {
2 | response
3 | .contentType('text/plain')
4 | .end(db.banned.join(', \n'))
5 | }
6 |
--------------------------------------------------------------------------------
/.dynamic/redirectToError.js:
--------------------------------------------------------------------------------
1 | module.exports = function redirectToError(response, errorMessage) {
2 | response.redirect(`/error?message=${encodeURIComponent(errorMessage)}`)
3 | }
4 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "cSpell.words": [
3 | "apos",
4 | "checkmark",
5 | "greylist",
6 | "mailparser",
7 | "maxlength",
8 | "quot",
9 | "Rcpt"
10 | ]
11 | }
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Soon.
11 |
12 |
--------------------------------------------------------------------------------
/checkmark.svg:
--------------------------------------------------------------------------------
1 | Your request to sign the web0 manifesto on behalf of {{signatory}} has been cancelled as per your instructions.
10 |
11 | ${require('../footer-template')()}
12 | `
13 |
14 | module.exports = (request, response) => {
15 | const code = request.params.code
16 | console.log(`Asking to cancel signature with code ${code}`)
17 |
18 | // Make sure the code is the shape we expect it to be
19 | // before going any further.
20 | if (code.length !== 32 || isNaN(parseInt('0x' + code))) {
21 | return redirectToError(response, 'Invalid confirmation code.')
22 | }
23 |
24 | const signatoryEmail = db.confirmationCodesToSignatoryEmails[code]
25 |
26 | if (signatoryEmail) {
27 | const signatory = db.pendingSignatories[signatoryEmail]
28 |
29 | if (signatory) {
30 | // Cancel the signatory request.
31 | delete db.pendingSignatories[signatoryEmail]
32 |
33 | // Also delete the code to email map as it is no longer necessary.
34 | delete db.confirmationCodesToSignatoryEmails[code]
35 |
36 | // Inform person that the request has been cancelled.
37 | const page = template.replace('{{signatory}}', signatory.signatory)
38 | return response.html(page)
39 | } else {
40 | redirectToError(response, 'Sorry, it looks like the signatory has already been confirmed. TODO: Would you like to remove the signatory from the manifesto?')
41 | }
42 | } else {
43 | return redirectToError(response, 'Signatory not found.')
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/.dynamic/.get/delete_code.js:
--------------------------------------------------------------------------------
1 | const redirectToError = require('../redirectToError')
2 | const slugify = str => require('slugify')(str, {lower: true, strict: true})
3 |
4 | const template = `${require('../header-template')('Your signature has been removed')}
5 |
6 | Your signature for {{signatory}} as well as your linked data (your name and email address) has been removed.
9 |
10 | ${require('../footer-template')()}
11 | `
12 |
13 | module.exports = (request, response) => {
14 | const code = request.params.code
15 | console.log(`Asking to delete signature with code ${code}`)
16 |
17 | // Make sure the code is the shape we expect it to be
18 | // before going any further.
19 | if (code.length !== 32 || isNaN(parseInt('0x' + code))) {
20 | return redirectToError(response, 'Invalid confirmation code.')
21 | }
22 |
23 | let signatoryIndex = -1
24 | const signatory = db.confirmedSignatories.find((signatory, index) => {
25 | // Signatory could be undefined if it has been deleted as we aren’t
26 | // currently compacting the array structure.
27 | if (signatory !== undefined && signatory.id === code) {
28 | signatoryIndex = index
29 | return true
30 | }
31 | return false
32 | })
33 |
34 | if (signatory) {
35 | // Delete the signatory request.
36 | delete db.confirmedSignatories[signatoryIndex]
37 |
38 | // Also delete the code to email map as it is no longer necessary.
39 | delete db.confirmationCodesToSignatoryEmails[code]
40 |
41 | // Inform the person that their signature has been deleted.
42 | const page = template.replace('{{signatory}}', signatory.signatory)
43 | return response.html(page)
44 | } else {
45 | redirectToError(response, 'Signatory not found.')
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/spinner.svg:
--------------------------------------------------------------------------------
1 |
12 |
13 |
14 | Privacy Policy
15 |
16 | Our privacy policy is that we exist to protect your privacy and other human rights.
17 |
18 | It’s why we do what we do .
19 |
20 | What data do you gather when I visit this site?
21 |
22 | No data that is linked to you is collected or stored by us as you browse this site.
23 |
24 | This site runs on Site.js .
25 |
26 | Site.js collects aggregate access statistics that are ephemeral (they reset when the server restarts). This is mostly useful for us to see how much traffic the site is getting and notice broken links or the occasional misconfigured bot or hack attempt.
27 |
28 | View latest ephemeral statistics.
29 |
30 | What data do you gather if I sign the manifesto?
31 |
32 | When you sign the manifesto, we collect two types of data. Data about you and data about the signatory (which might also be you or it might be a project or organisation that you’re signing on behalf of).
33 |
34 | (A) Data about you:
35 |
36 |
37 | Your name
38 | Your email address
39 |
40 |
41 | (B) Data about the signatory:
42 |
43 |
44 | Name of signatory
45 | Web address of signatory
46 |
47 |
48 | Data about you (A) is not displayed on the site and only used for the following purposes:
49 |
50 |
51 | To know who is signing the manifesto on behalf of the signatory and to have a means to communicate with you should, for example, someone representing the signatory (e.g., project or organisation) inform us that you do not have permission to sign the manifesto on their behalf.
52 |
53 | To send you transactional email to confirm your email address (to reduce the likelihood of spam/fraudulent signatures) and to inform you that your signature has been accepted and provide you with a link to delete your signature and the data about you (A) that’s linked to it should you want to in the future.
54 |
55 | To reply to you should you contact us with questions or requests.
56 |
57 |
58 | We will not use your name or email address for any other purpose although we may have to hand over the information should we receive a subpoena to do so. (Although what use it will be to anyone is beyond us as we store no other data about you.)
59 |
60 | Data about the signatory (B) is displayed in the signatory list on the site.
61 |
62 | How long do you keep my data?
63 |
64 | If you sign the manifesto, we keep the data in Groups A and B for as long as your signature remains on the manifesto.
65 |
66 | How can I get my data removed?
67 |
68 | If you signed the manifesto, you will have received a confirmation email that contains a link you can follow to delete your signature.
69 |
70 | Deleting your signature from the manifesto will also delete the linked data that is not displayed (your name and your email address).
71 |
72 | If you lost your confirmation email with the deletion link, please email Laura and Aral at hello@small-tech.org from the address you used to sign the manifesto and we will delete it for you.
73 |
74 | Do you use any third-party services to host this site and what data do they have access to?
75 |
76 | This site is hosted on a Hetzner VPS.
77 |
78 | You can download and read the GDPR data processing agreement we have with Hetzner (PDF; 199KB) .
79 |
80 | Other than that we do not use any other third-party services for the hosting of this site.
81 |
82 | So you don’t use any third-party services to send emails?
83 |
84 | No. Emails are sent using code we’ve written that contacts your mail server directly.
85 |
86 | (Email is actually quite a simple protocol and the needs of this site are very basic. See Aral’s video on How to send an email by manually talking to an email server if you’re into that sort of thing.)
87 |
88 | Can I see exactly what the site is doing?
89 |
90 | Yes, of course.
91 |
92 | Our work is free and open source, released under the GNU AGPL version 3.0 license.
93 |
94 | View Source.
95 |
96 | (If you notice any bugs or have suggestions for improvements, please let us know by opening an issue. Thanks!)
97 |
98 | How has this privacy policy changed over time?
99 |
100 | Since this web site is free and open source, you can view every change that’s been made to this privacy policy over time.
101 |
102 | Can I talk to a human being?
103 |
104 | Always! 👋🤓
105 |
106 | Email hello@small-tech.org and either Laura or Aral will get back to you as soon as we can.
107 |
108 |
119 |
120 |
121 |
--------------------------------------------------------------------------------
/.dynamic/.post/sign.js:
--------------------------------------------------------------------------------
1 | const crypto = require('crypto')
2 | const sendMail = require('../sendMail')
3 | const redirectToError = require('../redirectToError')
4 | const urlExists = require('url-exist')
5 |
6 | const emailBlacklist = [
7 | // Disposable temporary email addresses.
8 | 'sharklasers.com'
9 | ]
10 |
11 | const headerTemplate = `
12 | ${require('../header-template')('Please wait, sending you a confirmation email…')}
13 |
14 |
15 | Please wait…
16 | We’re sending you a message at {{email_address}} to confirm your email address.
17 |
18 | `
19 |
20 | const fadeOutProgressMessageTemplate = `
21 |
22 | `
23 |
24 | const hideProgressMessageTemplate = `
25 |
26 | `
27 |
28 | const fadeInConfirmationEmailResultTemplate = `
29 |
30 | `
31 |
32 | const successTemplate = `
33 |
34 | Email sent!
35 | Please check your inbox and follow the link there to finish signing the web0 manifesto.
36 | Thanks!
37 |
38 | `
39 |
40 | const failureTemplate = (error, signatory, link, name, email) => `
41 |
42 | Sorry, we could not email you.
43 | We got the following error message from your email server:
44 | ${error}
45 |
46 | Greylist error?
47 |
48 |
62 |
63 | Blacklist error?
64 |
65 | If it looks like your server has blacklisted us, please contact your email provider and ask them to remove us from their blacklist.
66 |
67 | (This site is hosted on Hetzner and it’s possible that they’ve blacklisted a URL or IP address from Hetzner that was used for nefarious purposes in the past.)
68 |
69 | `
70 |
71 | const footerTemplate = `
72 | Back.
73 | ${require('../footer-template')()}
74 | `
75 |
76 | // This is the loose regular expression used in the HTML5 standard
77 | // extended to require a top-level domain.
78 | const validEmailRegExp = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+$/
79 |
80 | // Check for URL syntax (very loosely; as a simple first test).
81 | // Mirrored from the client-side.
82 | const validUrlRegExp = /^https:\/\/[^ "<>%{}|\\^`]+\.[^ "<>%{}|\\^`]+$/
83 |
84 | function htmlEncode (text) {
85 | return text
86 | .replace(/&/g, '&')
87 | .replace(//g, '>')
89 | .replace(/"/g, '"')
90 | .replace(/'/g, ''')
91 | }
92 |
93 | function delay (timeInMs) {
94 | return new Promise ((resolve, reject) => {
95 | setTimeout(() => {
96 | resolve()
97 | }, timeInMs)
98 | })
99 | }
100 |
101 | // Initialise the JSDB database table if if doesn’t already exist.
102 | if (db.pendingSignatories == undefined) {
103 | db.pendingSignatories = {}
104 | db.confirmationCodesToSignatoryEmails = {}
105 | db.confirmedSignatories = []
106 | }
107 |
108 | module.exports = async function (request, response) {
109 | let signatory = request.body.signatory
110 | let link = request.body.link
111 | const name = request.body.name
112 | const email = request.body.email
113 |
114 | // Basic validation on inputs.
115 | if (signatory === '' || link === '' || name === '' || email === '') {
116 | return redirectToError(response, 'All form fields are required.')
117 | }
118 |
119 | // Check lengths.
120 | if (signatory.length > 93) { return redirectToError(response, 'Signatory name too long (max 93 characters).') }
121 | if (name.length > 93) { return redirectToError(response, 'Name too long (max 93 characters)') }
122 | if (email.length > 254) { return redirectToError(response, 'Email too long (max 254 characters') }
123 | if (link.length > 256) { return redirectToError(response, 'Link too long (max 256 characters') }
124 |
125 | // Make sure signatory name doesn’t contain HTML.
126 | // (We don’t want any silly alerts popping up.)
127 | signatory = htmlEncode(signatory)
128 |
129 | // Basic email validation.
130 | if (validEmailRegExp.exec(email) === null) {
131 | return redirectToError(response, `Sorry, that does not look like a valid email address (${email}).`)
132 | }
133 |
134 | // Ensure signatory with given email is not waiting for confirmation.
135 | if (db.pendingSignatories[email] != undefined) {
136 | return redirectToError(response, `A request to sign the web0 manifesto with that email address (${email}) already exists, pending confirmation.
137 |
138 | Please follow the link in the email we sent you to finalise your submission.
`)
139 | }
140 |
141 | // Apply email blacklist.
142 | emailBlacklist.forEach(blackListedDomain => {
143 | if (email.indexOf(blackListedDomain) !== -1) {
144 | return redirectToError(response, `Sorry, we don’t accept submissions with email addresses from ${blackListedDomain}.`)
145 | }
146 | })
147 |
148 | // Apply ban list.
149 | db.banned.forEach(bannedEmail => {
150 | if (email === bannedEmail) {
151 | return redirectToError(response, `Sorry, that email address has been banned.`)
152 | }
153 | })
154 |
155 | // Basic URL massaging (we only accept https because it’s three days to 2022
156 | // for goodness’ sake), validation, and sanitisation.
157 | link = link.startsWith('http://') ? link.replace('http://', 'https://') : link
158 | link = link.startsWith('https://') ? link : `https://${link}`
159 |
160 | // Validate the link using a regular expression as the first step.
161 | if (validUrlRegExp.exec(link) === null) {
162 | return redirectToError(response, `Sorry, that does not look like a valid web address (${link}).`)
163 | }
164 |
165 | // Next validate the URL by creating a URL object from it.
166 | let url
167 | try {
168 | url = new URL(link)
169 | } catch (error) {
170 | return redirectToError(response, `Sorry, the link you provided (${link}) isn’t a valid URL.`)
171 | }
172 |
173 | // OK, let’s re-form the URL to keep only the protocol, hostname, pathname, and hash (if any).
174 | // In other words, a URL here really doesn’t need port, parameters, etc.
175 | link = `${url.protocol}//${url.hostname}${url.pathname}${url.hash}`
176 |
177 | // Now, finally, let’s make sure this URL is reachable.
178 | const linkIsReachable = await urlExists(link)
179 |
180 | if (!linkIsReachable) {
181 | return redirectToError(response, `Sorry, our sanitised version of the link you provided (${link}) isn’t loading for us.`)
182 | }
183 |
184 | // Start streaming the response so the person sees progress as we
185 | // attempt to send them the confirmation email.
186 | response.type('html')
187 | response.write(headerTemplate.replace('{{email_address}}', email))
188 |
189 | // Create a random hash for the validation URL
190 | // and map that to the pending signatory.
191 | const confirmationCode = crypto.randomBytes(16).toString('hex')
192 |
193 | // Email text.
194 | const text = `Hello ${name.split(' ')[0]},
195 |
196 | You (or someone who gave us your email address) has asked to sign the web0 manifesto on behalf of:
197 |
198 | ${signatory} (${link})
199 |
200 | If this wasn’t you, please ignore this email.
201 |
202 | Please use the following link to confirm your signature:
203 |
204 | https://web0.small-web.org/confirm/${confirmationCode}
205 |
206 | You can also cancel your request using the link below:
207 |
208 | https://web0.small-web.org/cancel/${confirmationCode}
209 |
210 | Thank you.
211 |
212 | Computer @ web0.small-web.org
213 | Sent on behalf of the humans at Small Technology Foundation.
214 | --
215 | Want to talk to a human being?
216 | Just hit reply and I’ll CC in the folks at hello@small-tech.org for you.
217 | https://small-tech.org`
218 |
219 | try {
220 | // Send email.
221 | const result = await sendMail(email, 'web0 manifesto signature confirmation request', text)
222 |
223 | // OK, email sent successfully, now persist the records in the database.
224 | db.confirmationCodesToSignatoryEmails[confirmationCode] = email
225 |
226 | // Create the signatory object and persist it in the database.
227 | db.pendingSignatories[email] = {
228 | id: confirmationCode,
229 | signatory,
230 | email,
231 | name,
232 | link,
233 | date: new Date()
234 | }
235 |
236 | // Start fading out the progress message and wait for it complete
237 | // before removing it from the DOM and fading in the result message.
238 | // Yes, we’re animating using the HTTP stream and CSS animations
239 | // without any client-side JavaScript ;)
240 | response.write(fadeOutProgressMessageTemplate)
241 | await delay(1000)
242 | response.write(hideProgressMessageTemplate)
243 | response.write(successTemplate)
244 | } catch (error) {
245 | // Start fading out the progress message and wait for it complete
246 | // before removing it from the DOM and fading in the error message.
247 | response.write(fadeOutProgressMessageTemplate)
248 | await delay(1000)
249 | response.write(hideProgressMessageTemplate)
250 | response.write(failureTemplate(error, signatory, link, name, email))
251 | }
252 | await delay(100)
253 | response.write(fadeInConfirmationEmailResultTemplate)
254 | response.write(footerTemplate)
255 | response.end()
256 | }
257 |
--------------------------------------------------------------------------------
/.dynamic/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "web0",
3 | "version": "1.0.0",
4 | "lockfileVersion": 1,
5 | "requires": true,
6 | "dependencies": {
7 | "@selderee/plugin-htmlparser2": {
8 | "version": "0.6.0",
9 | "resolved": "https://registry.npmjs.org/@selderee/plugin-htmlparser2/-/plugin-htmlparser2-0.6.0.tgz",
10 | "integrity": "sha512-J3jpy002TyBjd4N/p6s+s90eX42H2eRhK3SbsZuvTDv977/E8p2U3zikdiehyJja66do7FlxLomZLPlvl2/xaA==",
11 | "requires": {
12 | "domhandler": "^4.2.0",
13 | "selderee": "^0.6.0"
14 | }
15 | },
16 | "abort-controller": {
17 | "version": "3.0.0",
18 | "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
19 | "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==",
20 | "requires": {
21 | "event-target-shim": "^5.0.0"
22 | }
23 | },
24 | "base32.js": {
25 | "version": "0.1.0",
26 | "resolved": "https://registry.npmjs.org/base32.js/-/base32.js-0.1.0.tgz",
27 | "integrity": "sha1-tYLexpPC8R6JPPBk7mrFthMaIgI="
28 | },
29 | "commander": {
30 | "version": "2.20.3",
31 | "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
32 | "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="
33 | },
34 | "deepmerge": {
35 | "version": "4.2.2",
36 | "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz",
37 | "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg=="
38 | },
39 | "discontinuous-range": {
40 | "version": "1.0.0",
41 | "resolved": "https://registry.npmjs.org/discontinuous-range/-/discontinuous-range-1.0.0.tgz",
42 | "integrity": "sha1-44Mx8IRLukm5qctxx3FYWqsbxlo="
43 | },
44 | "dom-serializer": {
45 | "version": "1.3.2",
46 | "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.3.2.tgz",
47 | "integrity": "sha512-5c54Bk5Dw4qAxNOI1pFEizPSjVsx5+bpJKmL2kPn8JhBUq2q09tTCa3mjijun2NfK78NMouDYNMBkOrPZiS+ig==",
48 | "requires": {
49 | "domelementtype": "^2.0.1",
50 | "domhandler": "^4.2.0",
51 | "entities": "^2.0.0"
52 | }
53 | },
54 | "domelementtype": {
55 | "version": "2.2.0",
56 | "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.2.0.tgz",
57 | "integrity": "sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A=="
58 | },
59 | "domhandler": {
60 | "version": "4.3.0",
61 | "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.0.tgz",
62 | "integrity": "sha512-fC0aXNQXqKSFTr2wDNZDhsEYjCiYsDWl3D01kwt25hm1YIPyDGHvvi3rw+PLqHAl/m71MaiF7d5zvBr0p5UB2g==",
63 | "requires": {
64 | "domelementtype": "^2.2.0"
65 | }
66 | },
67 | "domutils": {
68 | "version": "2.8.0",
69 | "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz",
70 | "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==",
71 | "requires": {
72 | "dom-serializer": "^1.0.1",
73 | "domelementtype": "^2.2.0",
74 | "domhandler": "^4.2.0"
75 | }
76 | },
77 | "encoding-japanese": {
78 | "version": "1.0.30",
79 | "resolved": "https://registry.npmjs.org/encoding-japanese/-/encoding-japanese-1.0.30.tgz",
80 | "integrity": "sha512-bd/DFLAoJetvv7ar/KIpE3CNO8wEuyrt9Xuw6nSMiZ+Vrz/Q21BPsMHvARL2Wz6IKHKXgb+DWZqtRg1vql9cBg=="
81 | },
82 | "entities": {
83 | "version": "2.2.0",
84 | "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz",
85 | "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A=="
86 | },
87 | "event-target-shim": {
88 | "version": "5.0.1",
89 | "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz",
90 | "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ=="
91 | },
92 | "he": {
93 | "version": "1.2.0",
94 | "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
95 | "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw=="
96 | },
97 | "html-to-text": {
98 | "version": "8.0.0",
99 | "resolved": "https://registry.npmjs.org/html-to-text/-/html-to-text-8.0.0.tgz",
100 | "integrity": "sha512-fEtul1OerF2aMEV+Wpy+Ue20tug134jOY1GIudtdqZi7D0uTudB2tVJBKfVhTL03dtqeJoF8gk8EPX9SyMEvLg==",
101 | "requires": {
102 | "@selderee/plugin-htmlparser2": "^0.6.0",
103 | "deepmerge": "^4.2.2",
104 | "he": "^1.2.0",
105 | "htmlparser2": "^6.1.0",
106 | "minimist": "^1.2.5",
107 | "selderee": "^0.6.0"
108 | }
109 | },
110 | "htmlparser2": {
111 | "version": "6.1.0",
112 | "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz",
113 | "integrity": "sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==",
114 | "requires": {
115 | "domelementtype": "^2.0.1",
116 | "domhandler": "^4.0.0",
117 | "domutils": "^2.5.2",
118 | "entities": "^2.0.0"
119 | }
120 | },
121 | "iconv-lite": {
122 | "version": "0.6.3",
123 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
124 | "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
125 | "requires": {
126 | "safer-buffer": ">= 2.1.2 < 3.0.0"
127 | }
128 | },
129 | "ip-regex": {
130 | "version": "4.3.0",
131 | "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-4.3.0.tgz",
132 | "integrity": "sha512-B9ZWJxHHOHUhUjCPrMpLD4xEq35bUTClHM1S6CBU5ixQnkZmwipwgc96vAd7AAGM9TGHvJR+Uss+/Ak6UphK+Q=="
133 | },
134 | "ipv6-normalize": {
135 | "version": "1.0.1",
136 | "resolved": "https://registry.npmjs.org/ipv6-normalize/-/ipv6-normalize-1.0.1.tgz",
137 | "integrity": "sha1-GzJYKQ02X6gyOeiZB93kWS52IKg="
138 | },
139 | "is-url-superb": {
140 | "version": "3.0.0",
141 | "resolved": "https://registry.npmjs.org/is-url-superb/-/is-url-superb-3.0.0.tgz",
142 | "integrity": "sha512-3faQP+wHCGDQT1qReM5zCPx2mxoal6DzbzquFlCYJLWyy4WPTved33ea2xFbX37z4NoriEwZGIYhFtx8RUB5wQ==",
143 | "requires": {
144 | "url-regex": "^5.0.0"
145 | }
146 | },
147 | "ky": {
148 | "version": "0.19.1",
149 | "resolved": "https://registry.npmjs.org/ky/-/ky-0.19.1.tgz",
150 | "integrity": "sha512-ZwciYrfaWpDI72U2HAruuGYGFW3PCfGNdWWSANGGssg9BGm4rRJ9s/sApiiRpj+8Y245/hlZW9c60zudLr6iwA=="
151 | },
152 | "ky-universal": {
153 | "version": "0.5.0",
154 | "resolved": "https://registry.npmjs.org/ky-universal/-/ky-universal-0.5.0.tgz",
155 | "integrity": "sha512-O+0wjCua5i45lYBZrBy8TyRDRVodtsmzVC/MlE5FN7ZMFu/Icz7ekbZ85sdFw0F/JwGhXZTaKjXq9IgUGwGedQ==",
156 | "requires": {
157 | "abort-controller": "^3.0.0",
158 | "node-fetch": "^2.6.0"
159 | }
160 | },
161 | "libbase64": {
162 | "version": "1.2.1",
163 | "resolved": "https://registry.npmjs.org/libbase64/-/libbase64-1.2.1.tgz",
164 | "integrity": "sha512-l+nePcPbIG1fNlqMzrh68MLkX/gTxk/+vdvAb388Ssi7UuUN31MI44w4Yf33mM3Cm4xDfw48mdf3rkdHszLNew=="
165 | },
166 | "libmime": {
167 | "version": "5.0.0",
168 | "resolved": "https://registry.npmjs.org/libmime/-/libmime-5.0.0.tgz",
169 | "integrity": "sha512-2Bm96d5ktnE217Ib1FldvUaPAaOst6GtZrsxJCwnJgi9lnsoAKIHyU0sae8rNx6DNYbjdqqh8lv5/b9poD8qOg==",
170 | "requires": {
171 | "encoding-japanese": "1.0.30",
172 | "iconv-lite": "0.6.2",
173 | "libbase64": "1.2.1",
174 | "libqp": "1.1.0"
175 | },
176 | "dependencies": {
177 | "iconv-lite": {
178 | "version": "0.6.2",
179 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.2.tgz",
180 | "integrity": "sha512-2y91h5OpQlolefMPmUlivelittSWy0rP+oYVpn6A7GwVHNE8AWzoYOBNmlwks3LobaJxgHCYZAnyNo2GgpNRNQ==",
181 | "requires": {
182 | "safer-buffer": ">= 2.1.2 < 3.0.0"
183 | }
184 | }
185 | }
186 | },
187 | "libqp": {
188 | "version": "1.1.0",
189 | "resolved": "https://registry.npmjs.org/libqp/-/libqp-1.1.0.tgz",
190 | "integrity": "sha1-9ebgatdLeU+1tbZpiL9yjvHe2+g="
191 | },
192 | "linkify-it": {
193 | "version": "3.0.3",
194 | "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.3.tgz",
195 | "integrity": "sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==",
196 | "requires": {
197 | "uc.micro": "^1.0.1"
198 | }
199 | },
200 | "mailparser": {
201 | "version": "3.4.0",
202 | "resolved": "https://registry.npmjs.org/mailparser/-/mailparser-3.4.0.tgz",
203 | "integrity": "sha512-u2pfpLg+xr7m2FKDl+ohQhy2gMok1QZ+S9E5umS9ez5DSJWttrqSmBGswyj9F68pZMVTwbhLpBt7Kd04q/W4Vw==",
204 | "requires": {
205 | "encoding-japanese": "1.0.30",
206 | "he": "1.2.0",
207 | "html-to-text": "8.0.0",
208 | "iconv-lite": "0.6.3",
209 | "libmime": "5.0.0",
210 | "linkify-it": "3.0.3",
211 | "mailsplit": "5.3.1",
212 | "nodemailer": "6.7.0",
213 | "tlds": "1.224.0"
214 | },
215 | "dependencies": {
216 | "nodemailer": {
217 | "version": "6.7.0",
218 | "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.7.0.tgz",
219 | "integrity": "sha512-AtiTVUFHLiiDnMQ43zi0YgkzHOEWUkhDgPlBXrsDzJiJvB29Alo4OKxHQ0ugF3gRqRQIneCLtZU3yiUo7pItZw=="
220 | }
221 | }
222 | },
223 | "mailsplit": {
224 | "version": "5.3.1",
225 | "resolved": "https://registry.npmjs.org/mailsplit/-/mailsplit-5.3.1.tgz",
226 | "integrity": "sha512-o6R6HCzqWYmI2/IYlB+v2IMPgYqC2EynmagZQICAhR7zAq0CO6fPcsO6CrYmVuYT+SSwvLAEZR5WniohBELcAA==",
227 | "requires": {
228 | "libbase64": "1.2.1",
229 | "libmime": "5.0.0",
230 | "libqp": "1.1.0"
231 | }
232 | },
233 | "minimist": {
234 | "version": "1.2.5",
235 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
236 | "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
237 | },
238 | "moo": {
239 | "version": "0.5.1",
240 | "resolved": "https://registry.npmjs.org/moo/-/moo-0.5.1.tgz",
241 | "integrity": "sha512-I1mnb5xn4fO80BH9BLcF0yLypy2UKl+Cb01Fu0hJRkJjlCRtxZMWkTdAtDd5ZqCOxtCkhmRwyI57vWT+1iZ67w=="
242 | },
243 | "nearley": {
244 | "version": "2.20.1",
245 | "resolved": "https://registry.npmjs.org/nearley/-/nearley-2.20.1.tgz",
246 | "integrity": "sha512-+Mc8UaAebFzgV+KpI5n7DasuuQCHA89dmwm7JXw3TV43ukfNQ9DnBH3Mdb2g/I4Fdxc26pwimBWvjIw0UAILSQ==",
247 | "requires": {
248 | "commander": "^2.19.0",
249 | "moo": "^0.5.0",
250 | "railroad-diagrams": "^1.0.0",
251 | "randexp": "0.4.6"
252 | }
253 | },
254 | "node-fetch": {
255 | "version": "2.6.6",
256 | "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.6.tgz",
257 | "integrity": "sha512-Z8/6vRlTUChSdIgMa51jxQ4lrw/Jy5SOW10ObaA47/RElsAN2c5Pn8bTgFGWn/ibwzXTE8qwr1Yzx28vsecXEA==",
258 | "requires": {
259 | "whatwg-url": "^5.0.0"
260 | }
261 | },
262 | "nodemailer": {
263 | "version": "6.7.2",
264 | "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.7.2.tgz",
265 | "integrity": "sha512-Dz7zVwlef4k5R71fdmxwR8Q39fiboGbu3xgswkzGwczUfjp873rVxt1O46+Fh0j1ORnAC6L9+heI8uUpO6DT7Q=="
266 | },
267 | "parseley": {
268 | "version": "0.7.0",
269 | "resolved": "https://registry.npmjs.org/parseley/-/parseley-0.7.0.tgz",
270 | "integrity": "sha512-xyOytsdDu077M3/46Am+2cGXEKM9U9QclBDv7fimY7e+BBlxh2JcBp2mgNsmkyA9uvgyTjVzDi7cP1v4hcFxbw==",
271 | "requires": {
272 | "moo": "^0.5.1",
273 | "nearley": "^2.20.1"
274 | }
275 | },
276 | "railroad-diagrams": {
277 | "version": "1.0.0",
278 | "resolved": "https://registry.npmjs.org/railroad-diagrams/-/railroad-diagrams-1.0.0.tgz",
279 | "integrity": "sha1-635iZ1SN3t+4mcG5Dlc3RVnN234="
280 | },
281 | "randexp": {
282 | "version": "0.4.6",
283 | "resolved": "https://registry.npmjs.org/randexp/-/randexp-0.4.6.tgz",
284 | "integrity": "sha512-80WNmd9DA0tmZrw9qQa62GPPWfuXJknrmVmLcxvq4uZBdYqb1wYoKTmnlGUchvVWe0XiLupYkBoXVOxz3C8DYQ==",
285 | "requires": {
286 | "discontinuous-range": "1.0.0",
287 | "ret": "~0.1.10"
288 | }
289 | },
290 | "ret": {
291 | "version": "0.1.15",
292 | "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz",
293 | "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg=="
294 | },
295 | "safer-buffer": {
296 | "version": "2.1.2",
297 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
298 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
299 | },
300 | "selderee": {
301 | "version": "0.6.0",
302 | "resolved": "https://registry.npmjs.org/selderee/-/selderee-0.6.0.tgz",
303 | "integrity": "sha512-ibqWGV5aChDvfVdqNYuaJP/HnVBhlRGSRrlbttmlMpHcLuTqqbMH36QkSs9GEgj5M88JDYLI8eyP94JaQ8xRlg==",
304 | "requires": {
305 | "parseley": "^0.7.0"
306 | }
307 | },
308 | "slugify": {
309 | "version": "1.6.4",
310 | "resolved": "https://registry.npmjs.org/slugify/-/slugify-1.6.4.tgz",
311 | "integrity": "sha512-Pcz296CK0uGnTf4iNQId79Uv6/5G16t0g0x3OsxWS8qVSOW+JXNnNHKVcuDiMgEGTWyK6zjlWXo2dvzV/FLf9Q=="
312 | },
313 | "smtp-server": {
314 | "version": "3.9.0",
315 | "resolved": "https://registry.npmjs.org/smtp-server/-/smtp-server-3.9.0.tgz",
316 | "integrity": "sha512-CHws5GkHjfIikue6vSdp3uRnmW85l0JJQibVwDs7S5aIUppXJA9Y60XcdxcaCLXmAnd8V8wbtav5KY92nCZeag==",
317 | "requires": {
318 | "base32.js": "0.1.0",
319 | "ipv6-normalize": "1.0.1",
320 | "nodemailer": "6.6.1"
321 | },
322 | "dependencies": {
323 | "nodemailer": {
324 | "version": "6.6.1",
325 | "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.6.1.tgz",
326 | "integrity": "sha512-1xzFN3gqv+/qJ6YRyxBxfTYstLNt0FCtZaFRvf4Sg9wxNGWbwFmGXVpfSi6ThGK6aRxAo+KjHtYSW8NvCsNSAg=="
327 | }
328 | }
329 | },
330 | "tlds": {
331 | "version": "1.224.0",
332 | "resolved": "https://registry.npmjs.org/tlds/-/tlds-1.224.0.tgz",
333 | "integrity": "sha512-Jgdc8SEijbDFUsmCn6Wk/f7E6jBLFZOG3U1xK0amGSfEH55Xx97ItUS/d2NngsuApjn11UeWCWj8Um3VRhseZQ=="
334 | },
335 | "tr46": {
336 | "version": "0.0.3",
337 | "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
338 | "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o="
339 | },
340 | "uc.micro": {
341 | "version": "1.0.6",
342 | "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz",
343 | "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA=="
344 | },
345 | "url-exist": {
346 | "version": "2.0.2",
347 | "resolved": "https://registry.npmjs.org/url-exist/-/url-exist-2.0.2.tgz",
348 | "integrity": "sha512-JqLjYS8pU9xZtY3ro4c54CztoP5R8qRyMlg2Cxr4M9YD1NCe57MOsZHF1rP3y+qQcc7cqiZBBd4Cu5oehcJRlQ==",
349 | "requires": {
350 | "is-url-superb": "^3.0.0",
351 | "ky": "^0.19.0",
352 | "ky-universal": "^0.5.0"
353 | }
354 | },
355 | "url-regex": {
356 | "version": "5.0.0",
357 | "resolved": "https://registry.npmjs.org/url-regex/-/url-regex-5.0.0.tgz",
358 | "integrity": "sha512-O08GjTiAFNsSlrUWfqF1jH0H1W3m35ZyadHrGv5krdnmPPoxP27oDTqux/579PtaroiSGm5yma6KT1mHFH6Y/g==",
359 | "requires": {
360 | "ip-regex": "^4.1.0",
361 | "tlds": "^1.203.0"
362 | }
363 | },
364 | "webidl-conversions": {
365 | "version": "3.0.1",
366 | "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
367 | "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE="
368 | },
369 | "whatwg-url": {
370 | "version": "5.0.0",
371 | "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
372 | "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=",
373 | "requires": {
374 | "tr46": "~0.0.3",
375 | "webidl-conversions": "^3.0.0"
376 | }
377 | }
378 | }
379 | }
380 |
--------------------------------------------------------------------------------
/.dynamic/routes.js:
--------------------------------------------------------------------------------
1 | const os = require('os')
2 | const fs = require('fs')
3 | const path = require('path')
4 | const SMTPServer = require('smtp-server').SMTPServer
5 | const simpleParser = require('mailparser').simpleParser
6 | const sendMail = require('./sendMail')
7 |
8 | const redirectToError = require('./redirectToError')
9 |
10 | const crypto = require('crypto')
11 |
12 | // This little snippet hides secrets from the address bar and the browser
13 | // history so that they are not inadvertently revealed in screenshots.
14 | const hideSecretsFromAddressBarAndBrowserHistory = `
15 |
24 | `
25 |
26 | const header = require('./header-template')
27 | const footer = require('./footer-template')
28 |
29 | // Create a cryptographically-secure path for the admin route
30 | // and save it in a table called admin in the built-in JSDB database.
31 | if (db.admin === undefined) {
32 | db.admin = {}
33 | db.admin.route = crypto.randomBytes(16).toString('hex')
34 | }
35 |
36 | if (db.banned === undefined) {
37 | db.banned = []
38 | }
39 |
40 | module.exports = app => {
41 | // This is where we define the secret admin route and carry out one-time
42 | // global initialisation for the SMTP server.
43 |
44 | // Add admin route using cryptographically-secure secret path.
45 | app.get(`/admin/${db.admin.route}`, (request, response) => {
46 |
47 | const signatories = []
48 | let signatoryCount = 0
49 | db.confirmedSignatories.forEach(signatory => {
50 | signatoryCount++
51 | signatories.push(
52 | `
53 | ${signatoryCount}
54 | ${signatory.signatory}
55 | ${signatory.link}
56 | ${signatory.name}
57 | ${signatory.email}
58 | ✏️
59 | ❌
60 |
61 | `
62 | )
63 | })
64 |
65 | response.html(`
66 | ${header()}
67 |
68 | 📈 Site statistics
69 | Signatories (${signatories.length}) ↓
70 |
91 | ${footer(true, hideSecretsFromAddressBarAndBrowserHistory)}
92 | `)
93 | })
94 |
95 | function findSignatoryWithId (id) {
96 | let _index = null
97 | const signatory = db.confirmedSignatories.find((value, index) => {
98 | if (value) {
99 | if (value.id === id) {
100 | _index = index
101 | return true
102 | }
103 | }
104 | })
105 | return [signatory, _index]
106 | }
107 |
108 | // Add GET route to edit signatory.
109 | app.get(`/admin/${db.admin.route}/edit/:id`, (request, response) => {
110 |
111 | const id = request.params.id
112 | const [signatory] = findSignatoryWithId(id)
113 |
114 | response.html(`
115 | ${header()}
116 |
117 | 📈 Site statistics
118 | Signatories
119 | ✏️ Edit signatory
120 |
156 | ${footer(true, hideSecretsFromAddressBarAndBrowserHistory)}
157 | `)
158 | })
159 |
160 | // Add POST route to actually update edited signatory.
161 | app.post(`/admin/${db.admin.route}/edit/:id`, (request, response) => {
162 | const id = request.body.id
163 |
164 | // Note: we do not perform any server-side validation in this route
165 | // as we treat data sent to admin routes as trusted. (If the admin
166 | // route URL is compromised, we have bigger problems.)
167 | const signatory = request.body.signatory
168 | const link = request.body.link
169 | const name = request.body.name
170 | const email = request.body.email
171 |
172 | if (id == undefined) {
173 | return redirectToError(response, 'Nothing to update.')
174 | }
175 |
176 | const [currentSignatory, index] = findSignatoryWithId(id)
177 |
178 | if (index === null) {
179 | return redirectToError(response, 'Signatory not found.')
180 | }
181 |
182 | const updatedSignatory = { id, signatory, link, name, email }
183 |
184 | db.confirmedSignatories[index] = updatedSignatory
185 |
186 | response.html(`
187 | ${header()}
188 |
189 | 📈 Site statistics
190 | Signatories
191 | Signatory updated!
192 |
193 | Signatory: ${signatory}
194 | Link: ${link}
195 | Name: ${name}
196 | Email: ${email}
197 |
198 | Back to signatory list
199 | ${footer(true, hideSecretsFromAddressBarAndBrowserHistory)}
200 | `)
201 | })
202 |
203 | // Add GET route to confirm deletion of signatory.
204 | app.get(`/admin/${db.admin.route}/delete/:id`, (request, response) => {
205 |
206 | const id = request.params.id
207 | const [signatory] = findSignatoryWithId(id)
208 |
209 | response.html(`
210 | ${header()}
211 |
212 | 📈 Site statistics
213 | Signatories
214 | 💀 Do you really want to delete the following signatory?
215 |
225 | ${footer(true, hideSecretsFromAddressBarAndBrowserHistory)}
226 | `)
227 | })
228 |
229 | // Add POST route to actually delete signatory.
230 | app.post(`/admin/${db.admin.route}/delete/:id`, (request, response) => {
231 | const id = request.body.id
232 |
233 | if (id == undefined) {
234 | return redirectToError(response, 'Nothing to delete.')
235 | }
236 |
237 | const [signatory, index] = findSignatoryWithId(id)
238 |
239 | if (index === null) {
240 | return redirectToError(response, 'Signatory not found.')
241 | }
242 |
243 | delete db.confirmedSignatories[index]
244 |
245 | response.html(`
246 | ${header()}
247 |
248 | 📈 Site statistics
249 | Signatories
250 | Signatory ${signatory.signatory} (${signatory.link}) submitted by ${signatory.name} (${signatory.email}) has been deleted.
251 | Back to signatory list
252 | ${footer(true, hideSecretsFromAddressBarAndBrowserHistory)}
253 | `)
254 | })
255 |
256 | // Add POST route to ban signatories.
257 | //
258 | // (Banning adds email address to the banned table in the database in addition
259 | // to deleting the corresponding signature.)
260 | app.post(`/admin/${db.admin.route}/ban/`, (request, response) => {
261 | let ban = request.body.ban
262 |
263 | if (typeof ban === 'string') {
264 | ban = [ban]
265 | }
266 |
267 | console.log('Ban', ban)
268 |
269 | if (ban == undefined) {
270 | return redirectToError(response, 'Nothing to ban.')
271 | }
272 |
273 | const bannedEmails = []
274 |
275 | ban.forEach(id => {
276 | const [signatory, index] = findSignatoryWithId(id)
277 |
278 | if (index === null) {
279 | return redirectToError(response, `Signatory with id ${id} not found.`)
280 | }
281 |
282 | const emailToBan = db.confirmedSignatories[index].email
283 |
284 | if (emailToBan === null) {
285 | return redirectToError(response, `Cannot ban signatory with id ${id}: email is null.`)
286 | }
287 |
288 | console.log('Banning', emailToBan)
289 | db.banned.push(emailToBan)
290 |
291 | bannedEmails.push(emailToBan)
292 |
293 | console.log('Deleting signature of', emailToBan)
294 | delete db.confirmedSignatories[index]
295 | })
296 |
297 | const bannedEmailsListHtml = bannedEmails.reduce((html, emailAddress) => html += `${emailAddress} `, '')
298 |
299 | response.html(`
300 | ${header()}
301 |
302 | 📈 Site statistics
303 | Signatories
304 | The following spammers have been banned:
305 | 👋🤓
306 |
307 | ${bannedEmailsListHtml}
308 |
309 | Back to signatory list
310 | ${footer(true, hideSecretsFromAddressBarAndBrowserHistory)}
311 | `)
312 | })
313 |
314 | // Output admin path to logs so we know what it is.
315 | // (If someone has ssh access to our server to see this all is already lost anyway.)
316 | console.log(` 🔑️ ❨web0❩ Admin page is at /admin/${db.admin.route}`)
317 |
318 | console.log(' 📬 ❨web0❩ Starting SMTP server.')
319 |
320 | const tlsCertificatePath = path.join(os.homedir(), '.small-tech.org', 'site.js', 'tls', 'global', 'production', os.hostname())
321 | const keyPath = path.join(tlsCertificatePath, 'certificate-identity.pem') // Secret key path.
322 | const certPath = path.join(tlsCertificatePath, 'certificate.pem')
323 |
324 | //
325 | // SMTP Server configuration.
326 | //
327 |
328 | const key = fs.readFileSync(keyPath)
329 | const cert = fs.readFileSync(certPath)
330 | // const secure = true
331 | const size = 51200
332 | const banner = 'Welcome to the web0 SMTP Server'
333 | const disabledCommands = ['AUTH']
334 |
335 | const getNameFromAddressObject = addressObject => {
336 | let name = ''
337 | if (addressObject.name != undefined) {
338 | const names = addressObject.name.split(' ')
339 | name = ` ${names.length > 0 ? names[0] : addressObject.name}`
340 | }
341 | return name
342 | }
343 |
344 | const forwardEmailWithSessionIdToHumans = (message, envelope) => {
345 |
346 | // Sanity check. Ensure the mail envelope is correct before continuing.
347 | if (envelope == undefined || envelope.mailFrom == undefined || envelope.rcptTo == undefined || envelope.rcptTo.length === 0) {
348 | return console.error('Cannot forward email. Message envelope is wrong.', envelope)
349 | }
350 |
351 | const fromAddress = envelope.mailFrom.address
352 | const toAddress = envelope.rcptTo[0].address
353 |
354 | const fromName = message.from == undefined ? '' : getNameFromAddressObject(message.from)
355 | const toName = message.to == undefined ? '' : getNameFromAddressObject(message.to)
356 |
357 | const subject = message.subject == undefined ? '(no subject)' : message.subject
358 |
359 | const ccHeader = message.cc == undefined ? '' : `\n> CC: ${message.cc}`
360 | const messageDateHeader = message.date == undefined ? '' : `\n> Date: ${message.date}`
361 |
362 | const text = `Hello${fromName},
363 |
364 | Thanks for writing in.
365 |
366 | I’m CCing Laura and Aral at Small Technology Foundation so you can talk to a human being.
367 |
368 | Lots of love,
369 | Computer @ web0.small-web.org
370 |
371 | > From:${fromName} <${fromAddress}>
372 | > To:${toName} ${toAddress}${ccHeader}${messageDateHeader}
373 | > Subject: ${subject}
374 | >
375 | ${message.text.split('\n').map(line => `> ${line}`).join('\n')}
376 | `
377 | try {
378 | sendMail(/* to */ fromAddress, `FWD: ${subject}`, text, 'hello+web0@small-tech.org')
379 | } catch (error) {
380 | console.error(error)
381 | }
382 | }
383 |
384 | const onConnect = (session, callback) => {
385 | console.log(' 📬 ❨web0❩ Starting new session with email client.')
386 | console.log(session)
387 |
388 | // Always accept the connection.
389 | callback()
390 | }
391 |
392 | const onMailFrom = (address, session, callback) => {
393 | console.log(' 📬 ❨web0❩ Got mail from command.')
394 | console.log('address', address)
395 | console.log('session', session)
396 |
397 | // Accept all addresses that pass Nodemailer’s own cursory tests.
398 | callback ()
399 | }
400 |
401 | const onRcptTo = (address, session, callback) => {
402 | // First check size.
403 | // TODO.
404 | console.log(' 📬 ❨web0❩ Got rcpt to command.')
405 |
406 | // There’s only one account here.
407 | return address.address === 'computer@web0.small-web.org' ?
408 | callback() :
409 | callback(new Error("Address not found."))
410 | }
411 |
412 | // Called when a readable stream is available for the email.
413 | const onData = async (stream, session, callback) => {
414 |
415 | console.log('onData: session =', session)
416 |
417 | // Save the envelope here because it will have changed by the
418 | // time we get past the async await of the parser.
419 | const envelope = session.envelope
420 |
421 | // Persist session in local memory.
422 | let message
423 | try {
424 | message = await simpleParser(stream)
425 | } catch (error) {
426 | console.error(error)
427 | return callback(error)
428 | }
429 |
430 | console.log('message', message)
431 |
432 | // Acknowledge that we’ve received the message.
433 | callback()
434 |
435 | forwardEmailWithSessionIdToHumans(message, envelope)
436 | }
437 |
438 | const onClose = session => {
439 | console.log(' 📬 ❨web0❩ Email client closed (got quit command).')
440 | console.log('session', session)
441 | }
442 |
443 | const server = new SMTPServer({
444 | banner,
445 | disabledCommands,
446 | cert,
447 | key,
448 | size,
449 | onConnect,
450 | onMailFrom,
451 | onRcptTo,
452 | onData,
453 | onClose
454 | })
455 |
456 | server.on('error', error => {
457 | // TODO: Handle errors better.
458 | console.error('[SMTP Server Error] ', error.message)
459 | })
460 |
461 | // Clean up the mail server when the main server is shutting down.
462 | app.site.server.on('close', async () => {
463 | console.log(' 📬 ❨web0❩ Main server shutdown detected, asking mail server to close.')
464 | server.close(() => {
465 | console.log(' 📬 ❨web0❩ Mail server closed.')
466 | })
467 | })
468 |
469 | server.listen(25)
470 | }
471 |
--------------------------------------------------------------------------------
/styles.css:
--------------------------------------------------------------------------------
1 | * {
2 | box-sizing: border-box;
3 | }
4 |
5 | html {
6 | font-family:-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
7 | font-size: 24px;
8 | color: #111;
9 | }
10 |
11 | h1 {
12 | margin-top: 1rem;
13 | margin-bottom: 1.5rem;
14 | font-size: 3rem;
15 | line-height: 1;
16 | }
17 |
18 | h1 a, h2 a {
19 | color: #111;
20 | text-decoration: none;
21 | }
22 |
23 | h2 {
24 | margin-top: 1rem;
25 | margin-bottom: 1rem;
26 | font-size: 2rem;
27 | }
28 |
29 | body {
30 | max-width: 720px;
31 | margin-left: auto;
32 | margin-right: auto;
33 | padding-left: 1em;
34 | padding-right: 1em;
35 | }
36 |
37 | /* Signature submission form. */
38 |
39 | form:not(.admin) {
40 | background-color: lightgrey;
41 | padding: 1em;
42 | padding-right: 3em;
43 | transform: rotate(-1.5deg);
44 | font-size: 0.75em;
45 | box-shadow: .45em .5em 0 #666;
46 | }
47 |
48 | form:not(.admin) h2 {
49 | margin-top: 0;
50 | }
51 |
52 | form:not(.admin) ul li {
53 | display: flex;
54 | flex-direction: column;
55 | margin-bottom: 1em;
56 | }
57 |
58 | label {
59 | font-weight: 500;
60 | margin-bottom: 0.25em;
61 | }
62 |
63 | label small {
64 | display: block;
65 | font-style: italic;
66 | font-weight: normal;
67 | }
68 |
69 |
70 | input[type="text"], input[type="email"], input[type="url"] {
71 | padding: 1em;
72 | border: 0;
73 | font-size: 1em;
74 | padding-right: 3em;
75 | box-shadow: inset 0 0 10px #fff;
76 | }
77 |
78 | input[type="submit"]:not(.admin) {
79 | border: 0;
80 | padding: 1em;
81 | border-radius: 0.5em;
82 | font-size: 1em;
83 | color: #999;
84 | background-color: #eee;
85 | }
86 |
87 | input[type="submit"].admin {
88 | border: 0;
89 | padding: 1em;
90 | border-radius: 0.5em;
91 | font-size: 1em;
92 | color: #999;
93 | margin-top: 1em;
94 | background-color: #aa0000 !important;
95 | }
96 |
97 | /* We have a different validation animation on the submit button. */
98 | input[type="submit"] {
99 | transition: color, background-color 0.33s;
100 | }
101 |
102 | .inputWithCheckmark {
103 | position: relative;
104 | margin-top: 0;
105 | }
106 |
107 | .checkmark {
108 | position: absolute;
109 | text-align: right;
110 | top: calc(50% - 0.75em);
111 | right: 0.5em;
112 | opacity: 0;
113 | transition: opacity 0.33s;
114 | }
115 |
116 | input:valid + .checkmark {
117 | opacity: 1;
118 | }
119 |
120 | .checkmark img {
121 | width: 1.5em;
122 | }
123 |
124 | /* The submit button does not partake in browser form validation logic but we
125 | can still alter its appearance when the form itself is fully valid. */
126 | form:valid input[type="submit"] {
127 | background-color: rgb(31, 145, 31);
128 | color: #eee;
129 | }
130 |
131 | form:invalid input[type="submit"]:hover {
132 | color: #999;
133 | background-color: #eee;
134 | cursor: not-allowed;
135 | }
136 |
137 | form:valid input[type="submit"]:hover, form:valid input[type="submit"]:focus {
138 | background-color: rgb(35, 170, 35);;
139 | color: #fff;
140 | cursor: pointer;
141 | }
142 |
143 | input {
144 | width: 100%;
145 | }
146 |
147 | a {
148 | color: darkslateblue;
149 | }
150 |
151 | /* Used to display error messages */
152 | pre {
153 | white-space: pre-wrap;
154 | background-color: black;
155 | color: white;
156 | padding: 1em;
157 | font-size: 0.75em;
158 | }
159 |
160 | strong {
161 | font-weight: 800;
162 | }
163 |
164 | table {
165 | table-layout: fixed;
166 | width: 100%;
167 | font-size: 0.6em;
168 | border-collapse: collapse;
169 | }
170 |
171 | tr > * {
172 | padding: 0.5em;
173 | }
174 |
175 | thead tr {
176 | background-color: #333;
177 | color: white;
178 | }
179 |
180 | th {
181 | padding: 1em;
182 | padding-left: 0 ;
183 | font-size: 1em;
184 | text-align: left;
185 | }
186 |
187 | th:first-of-type, td:first-of-type {
188 | padding-left: 1em;
189 | }
190 |
191 | th:first-of-type {
192 | width: 4em;
193 | }
194 |
195 | td {
196 | /* border: 1px solid red; */
197 | word-break: break-word;
198 | /* text-align: center; */
199 | }
200 |
201 | tbody tr:nth-of-type(2n) {
202 | background-color: #eee;
203 | }
204 |
205 | footer {
206 | margin-top: 3em;
207 | margin-bottom: 2em;
208 | font-size: 0.75em;
209 | text-align: center;
210 | }
211 |
212 | footer p {
213 | margin-bottom: 1em;
214 | }
215 |
216 | .spammers {
217 | width: 100%;
218 | height: 50vh;
219 | font-size: 0.75em;
220 | font-family: monospace;
221 | color: brown;
222 | }
223 |
224 | .refreshLink {
225 | text-align: center;
226 | }
227 |
228 | .iconHeader {
229 | width: 2em;
230 | }
231 |
232 | .iconLink {
233 | text-decoration: none;
234 | }
235 |
236 | .trivia {
237 | font-style: italic;
238 | font-size: 0.8em;
239 | }
240 |
241 | .blackboard {
242 | list-style-type: none;
243 | background: black;
244 | color: white;
245 | border-radius: 0.25em;
246 | padding: 1em;
247 | font-family: 'Annie Use Your Telescope', cursive;
248 | font-size: 1.25em;
249 | margin-top: -0.25em;
250 | }
251 |
252 | .blackboard li {
253 | line-height: 1em;
254 | margin-bottom: 0.5em;
255 | }
256 |
257 | .blackboard li:last-of-type {
258 | margin-bottom: 0;
259 | }
260 |
261 | .spinner {
262 | width: 0.75em;
263 | height: 0.75em;
264 | margin-right: 0.33em;
265 | vertical-align: baseline;
266 | }
267 |
268 | .error {
269 | color: red;
270 | }
271 |
272 | .web0 {
273 | font-family: "Fira Code", monospace;
274 | }
275 |
276 | .authorisation {
277 | font-weight: 500;
278 | }
279 |
280 | .toWit {
281 | font-style: italic;
282 | }
283 |
284 | .andICantEmphasiseThisEnough {
285 | background-color: yellow;
286 | }
287 |
288 | .signatoryDetails {
289 | list-style: none;
290 | }
291 |
292 | /* Admin: delete confirmation form */
293 |
294 | form.deleteConfirmation, form.greylistError {
295 | padding-top: 0.25em;
296 | font-size: 1em;
297 | padding-right: 1em;
298 | }
299 |
300 | form.greylistError {
301 | margin-bottom: 2.5em;
302 | }
303 |
304 | form.deleteConfirmation strong {
305 | display: inline-block;
306 | font-size: 0.75em;
307 | margin-bottom: 0.25em;
308 | }
309 |
310 | form.deleteConfirmation input[type='submit'] {
311 | background-color: darkred;
312 | }
313 |
314 | form.deleteConfirmation input[type='submit']:hover, form.deleteConfirmation input[type='submit']:focus {
315 | background-color: red;
316 | }
317 |
318 | #updateButton {
319 | margin-top: 0.5em;
320 | }
321 |
322 | #progress {
323 | transition: opacity 1s;
324 | opacity: 100;
325 | }
326 |
327 | #confirmationEmailResult {
328 | transition: opacity 1s;
329 | opacity: 0;
330 | }
331 |
332 | #signatories {
333 | padding-left: 1em;
334 | }
335 |
336 | #signatories li {
337 | list-style-type: none;
338 | font-family: 'Reenie Beanie', cursive;
339 | font-size: 1.5em;
340 | line-height: 1;
341 | margin-bottom: 0.5em;
342 | border-bottom: 1px solid #ccc;
343 | }
344 |
345 | #signatories a {
346 | text-decoration: none;
347 | }
348 |
349 | #manifesto {
350 | margin-bottom: 2em;
351 | }
352 |
353 | #sign {
354 | margin-bottom: 2.5em;
355 | }
356 |
357 | @media only screen and (max-width: 640px) {
358 | /* Reduce form padding on narrower viewports to maximise usable space */
359 | form {
360 | padding-right: 1em;
361 | }
362 | form ul {
363 | padding-left: 0;
364 | }
365 | }
366 |
367 | @media only screen and (min-width: 720px) {
368 | #signatories {
369 | display: grid;
370 | column-gap: 3em;
371 | grid-template-columns: 1fr 1fr;
372 | /* columns: 2; */
373 | }
374 | }
375 |
376 | /* Web fonts */
377 |
378 | /* Fira Code subset, we only use the following glyphs: web0 */
379 | @font-face {
380 | font-family: 'Fira Code';
381 | font-weight: 600;
382 | font-style: normal;
383 | src: url(data:application/font-woff2;charset=utf-8;base64,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) format('woff');
384 | }
385 |
386 | /* Annie Use Your Telescope subset, we only use glyphs used in the blackboard section. */
387 | @font-face {
388 | font-family: 'Annie Use Your Telescope';
389 | font-weight: normal;
390 | font-style: normal;
391 | src: url(data:application/font-woff2;charset=utf-8;base64,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) format('woff');
392 | }
393 |
394 | /* This is the full Reenie Beenie font. We can’t really subset as this is used for the
395 | dynamically-generated list of signatory names. */
396 | @font-face {
397 | font-family: 'Reenie Beanie';
398 | font-weight: normal;
399 | font-style: normal;
400 | src: url(data:application/font-woff2;charset=utf-8;base64,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) format('woff2');
401 | }
402 |
--------------------------------------------------------------------------------