Compare commits
	
		
			159 Commits
		
	
	
		
			master
			...
			commercial
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 156c07a099 | |||
| 4d3be39043 | |||
| a4b39215ea | |||
| 6b619c8121 | |||
| dad775fcf6 | |||
| dbe3088908 | |||
| 285ee71486 | |||
| 04e399cf37 | |||
| 2bbfaeda2f | |||
| 7c5a451a9a | |||
| f0fda13b16 | |||
| 8046018dba | |||
| 7f2bd0a591 | |||
| 07468b41b2 | |||
| 40878066d1 | |||
| bba44656f7 | |||
| 408a049da2 | |||
| 137bf18c4e | |||
| 27ae2aeb48 | |||
| e77468106d | |||
| 947971dc3d | |||
| cde0525f63 | |||
| 2b79ca6b81 | |||
| f34f78cbfb | |||
| 682557dc86 | |||
| 6e1320fe9b | |||
| 883f8a55b1 | |||
| c2165578e1 | |||
| 00ad13efc7 | |||
| e6e8113cca | |||
| 374d6ee34b | |||
| 0fa68eef1e | |||
| 22e3730c4c | |||
| 2fc1ac7536 | |||
| eedf9fe896 | |||
| 34214e9afb | |||
| 7d11f15ebe | |||
| 739f86f1cc | |||
| e6c2282fcf | |||
| ec3f626560 | |||
| 168b2edfd6 | |||
| ec65fe31cc | |||
| bd15f45d1d | |||
| 2564b750e6 | |||
| d755b94bcd | |||
| 5e0b30f69e | |||
| 17d46bdcd5 | |||
| 2819117f10 | |||
| 222848cdd1 | |||
| d2df0d6ab3 | |||
| aad7c51834 | |||
| 616458a87a | |||
| b60658ee81 | |||
| f54c4dde7a | |||
| 8fe1f4d82a | |||
| 40ab6a4bb8 | |||
| acf8522195 | |||
| f828839b4d | |||
| 60ee3720e0 | |||
| 5b7f19e7a8 | |||
| 09660b0731 | |||
| 6972112782 | |||
| 004908e735 | |||
| 4e3e155460 | |||
| 5940d4bd28 | |||
| e4534d6076 | |||
| c3c4f8893f | |||
| 01c24d7eec | |||
| 4f2948d0f0 | |||
| a2b23b2509 | |||
| 64281e4c93 | |||
| 1099a75509 | |||
| 353329120f | |||
| c44608cbe7 | |||
| ae43b0859b | |||
| b386857d99 | |||
| d63b6cb370 | |||
| e611d945ed | |||
| de9aab8195 | |||
| 79e9728d19 | |||
| 5aed381963 | |||
| b5fd940429 | |||
| d12cddb1aa | |||
| f5126ad8ed | |||
| 36ab30c9f2 | |||
| 4e459ea617 | |||
| c045e4c712 | |||
| 16e30124e6 | |||
| d60b458f47 | |||
| 3773abdfdb | |||
| 6a1df2ee05 | |||
| 4bef0187f1 | |||
| db4e5c4f60 | |||
| 44cca52f50 | |||
| cda10951d8 | |||
| 5deefa9832 | |||
| 021629ea68 | |||
| 6b6ffd4647 | |||
| 57f1de5f2d | |||
| bbee698322 | |||
| cc58fcb98a | |||
| 5b90d5ef38 | |||
| 8aecbb1f56 | |||
| 40d54bcdad | |||
| a3b8cd6799 | |||
| dc67bee735 | |||
| 7fd28d55a1 | |||
| 589e4af90b | |||
| 6e60fc1750 | |||
| 6c2f039533 | |||
| cc2823a56a | |||
| cc5cbea486 | |||
| 9cd489f575 | |||
| 5d065c8bd0 | |||
| a9353a9a75 | |||
| 06b894a99d | |||
| d2fde5c38c | |||
| 4db9a8d53d | |||
| 809c14f91f | |||
| 79648af88c | |||
| 148cda8516 | |||
| 179256a88e | |||
| 95f9ca0844 | |||
| 33a565b19a | |||
| 114cc53dd4 | |||
| 09b1d5939e | |||
| 2603c322bd | |||
| 96631d1369 | |||
| 8a1e2f14e0 | |||
| 0013930dbd | |||
| 72986ccdc1 | |||
| ff52d1e4a8 | |||
| c1c7dfba02 | |||
| d6f517ed2b | |||
| f8ac7baa1a | |||
| f0df611886 | |||
| f8c7517b14 | |||
| f704a9fa19 | |||
| 2e36d903ae | |||
| 93c864d4d4 | |||
| 7de7cc651d | |||
| 97e5a847b8 | |||
| 20f59b6af7 | |||
| f2c983a129 | |||
| 06540352bf | |||
| 63065d6d65 | |||
| 5e1963d11f | |||
| 34b86bc7a3 | |||
| 08833123b2 | |||
| 4b44632f2e | |||
| d8e54c179d | |||
| a9b1c4204b | |||
| 46599c864c | |||
| ab35fdc40e | |||
| 114f298e8e | |||
| 2b9fadf4b4 | |||
| 2b2a0021aa | |||
| ee1f713bd3 | |||
| ad3b2e9167 | 
							
								
								
									
										14
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						| @ -1,3 +1,8 @@ | |||||||
|  | emails | ||||||
|  | lib/extensions/permissions.json | ||||||
|  | lib/extensions/permissions.json.bak | ||||||
|  | lib/extensions/admin/sclient/dist/ | ||||||
|  | lib/extensions/admin/optify/dist/ | ||||||
| node_modules.* | node_modules.* | ||||||
| include | include | ||||||
| bin/node | bin/node | ||||||
| @ -43,12 +48,3 @@ jspm_packages | |||||||
| 
 | 
 | ||||||
| # Optional REPL history | # Optional REPL history | ||||||
| .node_repl_history | .node_repl_history | ||||||
| 
 |  | ||||||
| # Snapcraft |  | ||||||
| /parts/ |  | ||||||
| /prime/ |  | ||||||
| /stage/ |  | ||||||
| .snapcraft |  | ||||||
| *.snap |  | ||||||
| *.tar.bz2 |  | ||||||
| 
 |  | ||||||
|  | |||||||
| @ -2,6 +2,10 @@ | |||||||
| (function () { | (function () { | ||||||
| 'use strict'; | 'use strict'; | ||||||
| 
 | 
 | ||||||
|  | var fs = require('fs'); | ||||||
|  | var path = require('path'); | ||||||
|  | var os = require('os'); | ||||||
|  | 
 | ||||||
| var pkg = require('../package.json'); | var pkg = require('../package.json'); | ||||||
| 
 | 
 | ||||||
| var argv = process.argv.slice(2); | var argv = process.argv.slice(2); | ||||||
| @ -47,13 +51,7 @@ function applyConfig(config) { | |||||||
|   } else { |   } else { | ||||||
|     state.Promise = require('bluebird'); |     state.Promise = require('bluebird'); | ||||||
|   } |   } | ||||||
|   state.tlsOptions = { |   state.tlsOptions = {}; // TODO just close the sockets that would use this early? or use the admin servername
 | ||||||
|     // Handles disconnected devices
 |  | ||||||
|     // TODO allow user to opt-in to wildcard hosting for a better error page?
 |  | ||||||
|     SNICallback: function (servername, cb) { |  | ||||||
|       return state.greenlock.tlsOptions.SNICallback(state.config.webminDomain || state.servernames[0], cb); |  | ||||||
|     } |  | ||||||
|   }; // TODO just close the sockets that would use this early? or use the admin servername
 |  | ||||||
|   state.config = config; |   state.config = config; | ||||||
|   state.servernames = config.servernames || []; |   state.servernames = config.servernames || []; | ||||||
|   state.secret = state.config.secret; |   state.secret = state.config.secret; | ||||||
| @ -73,54 +71,59 @@ function applyConfig(config) { | |||||||
|     state.config.greenlock.configDir = require('os').homedir() + require('path').sep + 'acme'; |     state.config.greenlock.configDir = require('os').homedir() + require('path').sep + 'acme'; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   function approveDomains(opts, certs, cb) { |  | ||||||
|     if (state.debug) { console.log('[debug] approveDomains', opts.domains); } |  | ||||||
|     // This is where you check your database and associated
 |  | ||||||
|     // email addresses with domains and agreements and such
 |  | ||||||
| 
 |  | ||||||
|   // The domains being approved for the first time are listed in opts.domains
 |   // The domains being approved for the first time are listed in opts.domains
 | ||||||
|   // Certs being renewed are listed in certs.altnames
 |   // Certs being renewed are listed in certs.altnames
 | ||||||
|     if (certs) { |   function approveDomains(opts, certs, cb) { | ||||||
|       opts.domains = certs.altnames; |     if (state.debug) { console.log('[debug] approveDomains', opts.domains); } | ||||||
|       cb(null, { options: opts, certs: certs }); |  | ||||||
|       return; |  | ||||||
|     } |  | ||||||
| 
 | 
 | ||||||
|     if (!state.validHosts) { state.validHosts = {}; } |     function allow() { | ||||||
|     if (!state.validHosts[opts.domains[0]] && state.config.vhost) { |  | ||||||
|       if (state.debug) { console.log('[sni] vhost checking is turned on'); } |  | ||||||
|       var vhost = state.config.vhost.replace(/:hostname/, opts.domains[0]); |  | ||||||
|       require('fs').readdir(vhost, function (err, nodes) { |  | ||||||
|         if (state.debug) { console.log('[sni] checking fs vhost', opts.domains[0], !err); } |  | ||||||
|         if (err) { check(); return; } |  | ||||||
|         if (nodes) { approve(); } |  | ||||||
|       }); |  | ||||||
|       return; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     function approve() { |  | ||||||
|       state.validHosts[opts.domains[0]] = true; |       state.validHosts[opts.domains[0]] = true; | ||||||
|       opts.email = state.config.email; |       opts.email = state.config.email; | ||||||
|       opts.agreeTos = state.config.agreeTos; |       opts.agreeTos = state.config.agreeTos; | ||||||
|       opts.communityMember = state.config.communityMember || state.config.greenlock.communityMember; |       opts.communityMember = state.config.communityMember || state.config.greenlock.communityMember; | ||||||
|       opts.challenges = { |       opts.challenges = { | ||||||
|         // TODO dns-01
 |         // TODO dns-01
 | ||||||
|         'http-01': require('le-challenge-fs').create({ webrootPath: '/tmp/acme-challenges' }) |         'http-01': require('le-challenge-fs').create({ webrootPath: path.join(os.tmpdir(), 'acme-challenges') }) | ||||||
|       }; |       }; | ||||||
|       opts.communityMember = state.config.communityMember; |       opts.communityMember = state.config.communityMember; | ||||||
|       cb(null, { options: opts, certs: certs }); |       cb(null, { options: opts, certs: certs }); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     function check() { |     function deny() { | ||||||
|       if (state.debug) { console.log('[sni] checking servername'); } |       cb(new Error("[bin/telebit-relay.js] failed the approval chain '" + opts.domains[0] + "'")); | ||||||
|       if (-1 !== state.servernames.indexOf(opts.domain) || -1 !== (state._servernames||[]).indexOf(opts.domain)) { |       return; | ||||||
|         approve(); |  | ||||||
|       } else { |  | ||||||
|         cb(new Error("failed the approval chain '" + opts.domains[0] + "'")); |  | ||||||
|       } |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     check(); |     // 1) If the host was already allowed => allow
 | ||||||
|  |     if (!state.validHosts) { state.validHosts = {}; } | ||||||
|  |     if (state.validHosts[opts.domains[0]]) { | ||||||
|  |       allow(); | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // 2) If the host is in the config => allow
 | ||||||
|  |     if (state.debug) { console.log('[sni] checking servername'); } | ||||||
|  |     if (-1 !== state.servernames.indexOf(opts.domain) | ||||||
|  |       || -1 !== (state._servernames||[]).indexOf(opts.domain)) { | ||||||
|  |       allow(); | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // 3) If dynamic vhosting is allowed
 | ||||||
|  |     //    & a vhost folder exist for this domain => allow
 | ||||||
|  |     if (state.config.vhost) { | ||||||
|  |       if (state.debug) { console.log('[sni] vhost checking is turned on'); } | ||||||
|  |       var vhost = state.config.vhost.replace(/:hostname/, opts.domains[0]); | ||||||
|  |       require('fs').readdir(vhost, function (err, nodes) { | ||||||
|  |         if (state.debug) { console.log('[sni] checking fs vhost', opts.domains[0], !err); } | ||||||
|  |         if (err) { deny(); return; } | ||||||
|  |         if (nodes) { allow(); } | ||||||
|  |       }); | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // 4) fallback => fail
 | ||||||
|  |     deny(); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   state.greenlock = Greenlock.create({ |   state.greenlock = Greenlock.create({ | ||||||
| @ -202,7 +205,7 @@ function applyConfig(config) { | |||||||
|   //});
 |   //});
 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| require('fs').readFile(confpath, 'utf8', function (err, text) { | fs.readFile(confpath, 'utf8', function (err, text) { | ||||||
|   var config; |   var config; | ||||||
| 
 | 
 | ||||||
|   var recase = require('recase').create({}); |   var recase = require('recase').create({}); | ||||||
|  | |||||||
							
								
								
									
										27
									
								
								etc/telebit-relay.sample.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,27 @@ | |||||||
|  | email: jon@example.com         # must be valid (for certificate recovery and security alerts) | ||||||
|  | agree_tos: true                # agree to the Telebit, Greenlock, and Let's Encrypt TOSes | ||||||
|  | community_member: true         # receive infrequent relevant updates | ||||||
|  | telemetry: true                # contribute to project telemetric data | ||||||
|  | webmin_domain: telebit.example.com | ||||||
|  | api_domain: api.telebit.example.com | ||||||
|  | shared_domain: telebit.example.com | ||||||
|  | shared_domains: | ||||||
|  |   - telebit.example.com | ||||||
|  | servernames:                   # hostnames that direct to the Telebit Relay admin console | ||||||
|  |   - telebit.example.com | ||||||
|  |   - www.telebit.example.com | ||||||
|  |   - api.telebit.example.com | ||||||
|  | vhost: /srv/www/:hostname      # load secure websites at this path (uses template string, i.e. /var/www/:hostname/public) | ||||||
|  | trusted_issuers: | ||||||
|  |   - oauth3.org | ||||||
|  | mailer: | ||||||
|  |   url: 'https://api.mailgun.net/v3/example.com/messages' | ||||||
|  |   api_key: 'key-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' | ||||||
|  |   from: 'Telebit Wizard <wizard@example.com>' | ||||||
|  | greenlock: | ||||||
|  |   version: 'draft-11' | ||||||
|  |   server: 'https://acme-v02.api.letsencrypt.org/directory' | ||||||
|  |   store: | ||||||
|  |     strategy: le-store-certbot # certificate storage plugin | ||||||
|  |   config_dir: /opt/telebit-relay/etc/acme        # directory for ssl certificates | ||||||
|  | secret: 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'       # generate with node -e "console.log(crypto.randomBytes(16).toString('hex'))" | ||||||
| @ -1,17 +1,24 @@ | |||||||
| email: 'jon@example.com'       # must be valid (for certificate recovery and security alerts) | email: coolaj86@gmail.com | ||||||
| agree_tos: true                # agree to the Telebit, Greenlock, and Let's Encrypt TOSes | agree_tos: true | ||||||
| community_member: true         # receive infrequent relevant updates | community_member: true | ||||||
| telemetry: true                # contribute to project telemetric data | telemetry: true | ||||||
| webmin_domain: example.com | webmin_domain: example.com | ||||||
| shared_domain: xm.pl | api_domain: example.com | ||||||
| servernames:                   # hostnames that direct to the Telebit Relay admin console | shared_domain: example.com | ||||||
|   - telebit.example.com | servernames: | ||||||
|   - telebit.example.net |   - www.example.com | ||||||
| vhost: /srv/www/:hostname      # load secure websites at this path (uses template string, i.e. /var/www/:hostname/public) |   - example.com | ||||||
|  |   - api.example.com | ||||||
|  | vhost: /srv/www/:hostname | ||||||
| greenlock: | greenlock: | ||||||
|   version: 'draft-11' |   version: 'draft-11' | ||||||
|   server: 'https://acme-v02.api.letsencrypt.org/directory' |   server: 'https://acme-v02.api.letsencrypt.org/directory' | ||||||
|   store: |   store: | ||||||
|     strategy: le-store-certbot # certificate storage plugin |     strategy: le-store-certbot | ||||||
|   config_dir: /etc/acme        # directory for ssl certificates |   config_dir: /opt/telebit-relay/etc/acme | ||||||
| secret: ''                     # generate with node -e "console.log(crypto.randomBytes(16).toString('hex'))" | secret: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx | ||||||
|  | mailer: | ||||||
|  |   url: 'https://api.mailgun.net/v3/EXAMPLE.COM/messages' | ||||||
|  |   api_key: 'key-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' | ||||||
|  |   from: 'Example Mailer <MALIER@EXAMPLE.COM>' | ||||||
|  | debug: true | ||||||
|  | |||||||
							
								
								
									
										5
									
								
								examples/telebitd.real.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,5 @@ | |||||||
|  | servernames: [ 'telebit.cloud' ] | ||||||
|  | email: 'coolaj86@gmail.com' | ||||||
|  | agree_tos: true | ||||||
|  | community_member: false | ||||||
|  | vhost: /srv/www/:hostname | ||||||
| @ -179,6 +179,9 @@ if [ ! -f "$TELEBIT_RELAY_PATH/etc/$my_app.yml" ]; then | |||||||
|   sudo bash -c "echo 'email: $my_email' >> $TELEBIT_RELAY_PATH/etc/$my_app.yml" |   sudo bash -c "echo 'email: $my_email' >> $TELEBIT_RELAY_PATH/etc/$my_app.yml" | ||||||
|   sudo bash -c "echo 'secret: $my_secret' >> $TELEBIT_RELAY_PATH/etc/$my_app.yml" |   sudo bash -c "echo 'secret: $my_secret' >> $TELEBIT_RELAY_PATH/etc/$my_app.yml" | ||||||
|   sudo bash -c "echo 'servernames: [ $my_servername ]' >> $TELEBIT_RELAY_PATH/etc/$my_app.yml" |   sudo bash -c "echo 'servernames: [ $my_servername ]' >> $TELEBIT_RELAY_PATH/etc/$my_app.yml" | ||||||
|  |   sudo bash -c "echo 'webmin_domain: $my_servername' >> $TELEBIT_RELAY_PATH/etc/$my_app.yml" | ||||||
|  |   sudo bash -c "echo 'api_domain: $my_servername' >> $TELEBIT_RELAY_PATH/etc/$my_app.yml" | ||||||
|  |   sudo bash -c "echo 'shared_domain: $my_servername' >> $TELEBIT_RELAY_PATH/etc/$my_app.yml" | ||||||
|   sudo bash -c "cat $TELEBIT_RELAY_PATH/examples/$my_app.yml.tpl >> $TELEBIT_RELAY_PATH/etc/$my_app.yml" |   sudo bash -c "cat $TELEBIT_RELAY_PATH/examples/$my_app.yml.tpl >> $TELEBIT_RELAY_PATH/etc/$my_app.yml" | ||||||
| fi | fi | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -1,35 +0,0 @@ | |||||||
| 'use strict'; |  | ||||||
| 
 |  | ||||||
| var timeago = require('./ago.js').AGO; |  | ||||||
| 
 |  | ||||||
| function test() { |  | ||||||
|   [ 1.5 * 1000 // a moment ago
 |  | ||||||
|   , 4.5 * 1000 // moments ago
 |  | ||||||
|   , 10  * 1000 // 10 seconds ago
 |  | ||||||
|   , 59  * 1000 // a minute ago
 |  | ||||||
|   , 60  * 1000 // a minute ago
 |  | ||||||
|   , 61  * 1000 // a minute ago
 |  | ||||||
|   , 119  * 1000 // a minute ago
 |  | ||||||
|   , 120  * 1000 // 2 minutes ago
 |  | ||||||
|   , 121 * 1000 // 2 minutes ago
 |  | ||||||
|   , (60 * 60 * 1000) - 1000 // 59 minutes ago
 |  | ||||||
|   , 1 * 60 * 60 * 1000 // an hour ago
 |  | ||||||
|   , 1.5 * 60 * 60 * 1000 // an hour ago
 |  | ||||||
|   , 2.5 * 60 * 60 * 1000 // 2 hours ago
 |  | ||||||
|   , 1.5 * 24 * 60 * 60 * 1000 // a day ago
 |  | ||||||
|   , 2.5 * 24 * 60 * 60 * 1000 // 2 days ago
 |  | ||||||
|   , 7 * 24 * 60 * 60 * 1000 // a week ago
 |  | ||||||
|   , 14 * 24 * 60 * 60 * 1000 // 2 weeks ago
 |  | ||||||
|   , 27 * 24 * 60 * 60 * 1000 // 3 weeks ago
 |  | ||||||
|   , 28 * 24 * 60 * 60 * 1000 // 4 weeks ago
 |  | ||||||
|   , 29 * 24 * 60 * 60 * 1000 // 4 weeks ago
 |  | ||||||
|   , 1.5 * 30 * 24 * 60 * 60 * 1000 // a month ago
 |  | ||||||
|   , 2.5 * 30 * 24 * 60 * 60 * 1000 // 2 months ago
 |  | ||||||
|   , (12 * 30 * 24 * 60 * 60 * 1000) + 1000 // 12 months ago
 |  | ||||||
|   , 13 * 30 * 24 * 60 * 60 * 1000 // over a year ago
 |  | ||||||
|   ].forEach(function (d) { |  | ||||||
|     console.log(d, '=', timeago(d)); |  | ||||||
|   }); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| test(); |  | ||||||
							
								
								
									
										50
									
								
								lib/ago.js
									
									
									
									
									
								
							
							
						
						| @ -1,50 +0,0 @@ | |||||||
| ;(function (exports) { |  | ||||||
| 'use strict'; |  | ||||||
| 
 |  | ||||||
| exports.AGO = function timeago(ms) { |  | ||||||
|   var ago = Math.floor(ms / 1000); |  | ||||||
|   var part = 0; |  | ||||||
| 
 |  | ||||||
|   if (ago < 2) { return "a moment ago"; } |  | ||||||
|   if (ago < 5) { return "moments ago"; } |  | ||||||
|   if (ago < 60) { return ago + " seconds ago"; } |  | ||||||
| 
 |  | ||||||
|   if (ago < 120) { return "a minute ago"; } |  | ||||||
|   if (ago < 3600) { |  | ||||||
|     while (ago >= 60) { ago -= 60; part += 1; } |  | ||||||
|     return part + " minutes ago"; |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   if (ago < 7200) { return "an hour ago"; } |  | ||||||
|   if (ago < 86400) { |  | ||||||
|     while (ago >= 3600) { ago -= 3600; part += 1; } |  | ||||||
|     return part + " hours ago"; |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   if (ago < 172800) { return "a day ago"; } |  | ||||||
|   if (ago < 604800) { |  | ||||||
|     while (ago >= 172800) { ago -= 172800; part += 1; } |  | ||||||
|     return part + " days ago"; |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   if (ago < 1209600) { return "a week ago"; } |  | ||||||
|   if (ago < 2592000) { |  | ||||||
|     while (ago >= 604800) { ago -= 604800; part += 1; } |  | ||||||
|     return part + " weeks ago"; |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   if (ago < 5184000) { return "a month ago"; } |  | ||||||
|   if (ago < 31536001) { |  | ||||||
|     while (ago >= 2592000) { ago -= 2592000; part += 1; } |  | ||||||
|     return part + " months ago"; |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   if (ago < 315360000) { // 10 years
 |  | ||||||
|     return "more than year ago"; |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   // TODO never
 |  | ||||||
|   return ""; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| }('undefined' !== typeof module ? module.exports : window)); |  | ||||||
| @ -1,7 +1,6 @@ | |||||||
| 'use strict'; | 'use strict'; | ||||||
| 
 | 
 | ||||||
| var Devices = module.exports; | var Devices = module.exports; | ||||||
| // TODO enumerate store's keys and device's keys for documentation
 |  | ||||||
| Devices.addPort = function (store, serverport, newDevice) { | Devices.addPort = function (store, serverport, newDevice) { | ||||||
|   // TODO make special
 |   // TODO make special
 | ||||||
|   return Devices.add(store, serverport, newDevice, true); |   return Devices.add(store, serverport, newDevice, true); | ||||||
| @ -15,7 +14,6 @@ Devices.add = function (store, servername, newDevice, isPort) { | |||||||
|   if (!store._domains) { store._domains = {}; } |   if (!store._domains) { store._domains = {}; } | ||||||
|   if (!store._domains[servername]) { store._domains[servername] = []; } |   if (!store._domains[servername]) { store._domains[servername] = []; } | ||||||
|   store._domains[servername].push(newDevice); |   store._domains[servername].push(newDevice); | ||||||
|   Devices.touch(store, servername); |  | ||||||
| 
 | 
 | ||||||
|   // add device
 |   // add device
 | ||||||
|   // TODO only use a device id 
 |   // TODO only use a device id 
 | ||||||
| @ -97,6 +95,7 @@ Devices.list = function (store, servername) { | |||||||
|     // aliases have ._primary which is the name of the original
 |     // aliases have ._primary which is the name of the original
 | ||||||
|     return store._domains[servername]._primary && store._domains[store._domains[servername]._primary] || store._domains[servername]; |     return store._domains[servername]._primary && store._domains[store._domains[servername]._primary] || store._domains[servername]; | ||||||
|   } |   } | ||||||
|  | 
 | ||||||
|   // There wasn't an exact match so check any of the wildcard domains, sorted longest
 |   // There wasn't an exact match so check any of the wildcard domains, sorted longest
 | ||||||
|   // first so the one with the biggest natural match with be found first.
 |   // first so the one with the biggest natural match with be found first.
 | ||||||
|   var deviceList = []; |   var deviceList = []; | ||||||
| @ -105,7 +104,10 @@ Devices.list = function (store, servername) { | |||||||
|   }).sort(function (a, b) { |   }).sort(function (a, b) { | ||||||
|     return b.length - a.length; |     return b.length - a.length; | ||||||
|   }).some(function (pattern) { |   }).some(function (pattern) { | ||||||
|  |     // '.example.com' = '*.example.com'.split(1)
 | ||||||
|     var subPiece = pattern.slice(1); |     var subPiece = pattern.slice(1); | ||||||
|  |     // '.com' = 'sub.example.com'.slice(-4)
 | ||||||
|  |     // '.example.com' = 'sub.example.com'.slice(-12)
 | ||||||
|     if (subPiece === servername.slice(-subPiece.length)) { |     if (subPiece === servername.slice(-subPiece.length)) { | ||||||
|       console.log('[Devices.list] "'+servername+'" matches "'+pattern+'"'); |       console.log('[Devices.list] "'+servername+'" matches "'+pattern+'"'); | ||||||
|       deviceList = store._domains[pattern]; |       deviceList = store._domains[pattern]; | ||||||
| @ -128,11 +130,7 @@ Devices.active = function (store, id) { | |||||||
| }; | }; | ||||||
| */ | */ | ||||||
| Devices.exist = function (store, servername) { | Devices.exist = function (store, servername) { | ||||||
|   if (Devices.list(store, servername).length) { |   return !!(Devices.list(store, servername).length); | ||||||
|     Devices.touch(store, servername); |  | ||||||
|     return true; |  | ||||||
|   } |  | ||||||
|   return false; |  | ||||||
| }; | }; | ||||||
| Devices.next = function (store, servername) { | Devices.next = function (store, servername) { | ||||||
|   var devices = Devices.list(store, servername); |   var devices = Devices.list(store, servername); | ||||||
| @ -144,20 +142,5 @@ Devices.next = function (store, servername) { | |||||||
|   device = devices[devices._index || 0]; |   device = devices[devices._index || 0]; | ||||||
|   devices._index = (devices._index || 0) + 1; |   devices._index = (devices._index || 0) + 1; | ||||||
| 
 | 
 | ||||||
|   if (device) { Devices.touch(store, servername); } |  | ||||||
|   return device; |   return device; | ||||||
| }; | }; | ||||||
| Devices.touchDevice = function (store, device) { |  | ||||||
|   // TODO use device.id (which will be pubkey thumbprint) and store._devices[id].domainsMap
 |  | ||||||
|   Object.keys(device.domainsMap).forEach(function (servername) { |  | ||||||
|     Devices.touch(store, servername); |  | ||||||
|   }); |  | ||||||
| }; |  | ||||||
| Devices.touch = function (store, servername) { |  | ||||||
|   if (!store._recency) { store._recency = {}; } |  | ||||||
|   store._recency[servername] = Date.now(); |  | ||||||
| }; |  | ||||||
| Devices.lastSeen = function (store, servername) { |  | ||||||
|   if (!store._recency) { store._recency = {}; } |  | ||||||
|   return store._recency[servername] || 0; |  | ||||||
| }; |  | ||||||
|  | |||||||
							
								
								
									
										3
									
								
								lib/extensions/LICENSE
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,3 @@ | |||||||
|  | commercial | ||||||
|  | 
 | ||||||
|  | Copyright ppl 2018 | ||||||
							
								
								
									
										1
									
								
								lib/extensions/admin/.well-known
									
									
									
									
									
										Symbolic link
									
								
							
							
						
						| @ -0,0 +1 @@ | |||||||
|  | _apis | ||||||
							
								
								
									
										1
									
								
								lib/extensions/admin/_apis/oauth3
									
									
									
									
									
										Symbolic link
									
								
							
							
						
						| @ -0,0 +1 @@ | |||||||
|  | oauth3.org | ||||||
							
								
								
									
										1
									
								
								lib/extensions/admin/_apis/oauth3.org
									
									
									
									
									
										Symbolic link
									
								
							
							
						
						| @ -0,0 +1 @@ | |||||||
|  | ../assets/oauth3.org/_apis/oauth3.org | ||||||
							
								
								
									
										11
									
								
								lib/extensions/admin/_apis/telebit.cloud/index.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,11 @@ | |||||||
|  | { "terms_of_service": ":hostname/tos/" | ||||||
|  | , "api_host": "api.:hostname" | ||||||
|  | , "pair_request": { | ||||||
|  |     "method": "POST" | ||||||
|  |   , "pathname": "api/telebit.cloud/pair_request" | ||||||
|  |   } | ||||||
|  | , "tunnel": { | ||||||
|  |     "method": "wss" | ||||||
|  |   , "pathname": "" | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										90
									
								
								lib/extensions/admin/account.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,90 @@ | |||||||
|  | <html> | ||||||
|  |   <head> | ||||||
|  |     <title>Telebit Account</title> | ||||||
|  |   </head> | ||||||
|  |   <body> | ||||||
|  | 
 | ||||||
|  |     <div class="v-app"> | ||||||
|  |       <div v-if="spinner" style="position: absolute; width: 100%; height: 100%; background-color: #ddd;">Loading... </div> | ||||||
|  | 
 | ||||||
|  |       <div v-if="!hasAccount"> | ||||||
|  |         <h1>Login</h1> | ||||||
|  |         <form class="js-auth-form" v-on:submit.prevent="login()"> | ||||||
|  |           <input class="js-auth-subject" v-model="newEmail" placeholder="email" type="email" required/> | ||||||
|  |           <button class="js-auth-submit" type="submit">Login</button> | ||||||
|  |         </form> | ||||||
|  |       </div> | ||||||
|  | 
 | ||||||
|  |       <div v-if="hasAccount"> | ||||||
|  |         <h1>Account</h1> | ||||||
|  |         <button v-on:click.prevent.stop="logout()" type="click">Logout</button> | ||||||
|  |         <!-- not yet --> | ||||||
|  |         <!--form v-on:submit.prevent="challengeEmail()"> | ||||||
|  |           Authorize another email: | ||||||
|  |           <input v-model="newEmail" placeholder="jon@example.com" type="email" required/> | ||||||
|  |           <button type="submit">Next</button> | ||||||
|  |         </form--> | ||||||
|  | 
 | ||||||
|  |         <div v-if="claims.length"> | ||||||
|  |           <h3>Pending Claims</h3> | ||||||
|  |           <p>If your DNS host supports ANAME records, please use those instead of CNAMEs.</p> | ||||||
|  |           <p>If CNAMEs are not supported, set an A record to {{ site.deviceDomainA }}.</p> | ||||||
|  |           <ol> | ||||||
|  |             <li v-for="claim in claims"> | ||||||
|  |               <span>{{ claim.value }}</span> | ||||||
|  |               <br> | ||||||
|  |               <span v-if="'dns' === claim.type">CNAME <span v-if="claim.wildcard">*.</span>{{ claim.value }}: {{ site.deviceDomain }}</span> | ||||||
|  |               <br> | ||||||
|  |               <span v-if="'dns' === claim.type">TXT _claim-challenge.{{ claim.value }}: {{ claim.challenge }}</span> | ||||||
|  |               <br> | ||||||
|  |               <button v-on:click.prevent="checkDns(claim)">Check</button> | ||||||
|  |             </li> | ||||||
|  |           </ol> | ||||||
|  |         </div> | ||||||
|  | 
 | ||||||
|  |         <h3>Devices</h3> | ||||||
|  |         <div v-if="!devices.length"> | ||||||
|  |           You can add up to 5 devices: | ||||||
|  |           <pre><code>curl -sf https://get.telebit.io/ | bash</code></pre> | ||||||
|  |         </div> | ||||||
|  |         <div v-if="devices.length"> | ||||||
|  |           <ol> | ||||||
|  |             <li v-for="device in devices"> | ||||||
|  |               <span v-if="device.id">{{ device.id }}</span> {{ device.socketId }} | ||||||
|  |               <ul> | ||||||
|  |                 <li><form v-on:submit.stop.prevent="pushDomain(device)"> | ||||||
|  |                   <input type="text" v-model="device.newDomain" placeholder="ex: jon.telebit.com"></input><button type="submit">Push</button> | ||||||
|  |                 </form></li> | ||||||
|  |                 <li v-for="name in device.names">{{ name }}</li> | ||||||
|  |               </ul> | ||||||
|  |             </li> | ||||||
|  |           </ol> | ||||||
|  |         </div> | ||||||
|  | 
 | ||||||
|  |         <h3>Domains</h3> | ||||||
|  |         <form v-on:submit.prevent="challengeDns()"> | ||||||
|  |           Add a custom domain: | ||||||
|  |           <input v-model="newDomain" placeholder="example.com" type="text" required/> | ||||||
|  |           <button type="submit">Next</button> | ||||||
|  |         </form> | ||||||
|  |         <div v-if="domains.length"> | ||||||
|  |           <ol> | ||||||
|  |             <li v-for="domain in domains"> | ||||||
|  |               <span v-if="domain.wildcard">*.</span>{{ domain.name }} <span v-if="domain.hostname">- {{domain.hostname}} ({{domain.os}} {{domain.arch}})</span> | ||||||
|  |             </li> | ||||||
|  |           </ol> | ||||||
|  |         </div> | ||||||
|  | 
 | ||||||
|  |         <h3>Debug: Token</h3> | ||||||
|  |         <pre><code v-text="token"></code></pre> | ||||||
|  |       </div> | ||||||
|  | 
 | ||||||
|  |     </div> | ||||||
|  | 
 | ||||||
|  |     <!-- development version, includes helpful console warnings --> | ||||||
|  |     <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> | ||||||
|  | 
 | ||||||
|  |     <script src="assets/oauth3.org/oauth3.core.js"></script> | ||||||
|  |     <script src="js/account.js"></script> | ||||||
|  |   </body> | ||||||
|  | </html> | ||||||
							
								
								
									
										1
									
								
								lib/extensions/admin/assets/oauth3.org
									
									
									
									
									
										Submodule
									
								
							
							
								
								
								
								
								
								
									
									
								
							
						
						| @ -0,0 +1 @@ | |||||||
|  | Subproject commit 8e2e09f5823ae919c615c9c3b21114e01096b1ee | ||||||
							
								
								
									
										4
									
								
								lib/extensions/admin/dist/index.tab
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,4 @@ | |||||||
|  | channel	version	date | ||||||
|  | prod	v0.15.4	2018-06-14T09:37:22Z | ||||||
|  | beta	v0.15.3	2018-06-14T07:07:21Z | ||||||
|  | prod	v0.12.0	2018-06-07T07:43:21Z | ||||||
							
								
								
									
										86
									
								
								lib/extensions/admin/dist/notes.txt
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,86 @@ | |||||||
|  | Release Notes | ||||||
|  | ============= | ||||||
|  | 
 | ||||||
|  | Table of Contents | ||||||
|  | 
 | ||||||
|  | * v0.20.6 - protocol upgrade | ||||||
|  | 
 | ||||||
|  | Re: v0.20.6 | ||||||
|  | =========== | ||||||
|  | 
 | ||||||
|  | Saturday, Sept 29, 2018  | ||||||
|  | 
 | ||||||
|  | This version is a required update. I had to make some changes to the network | ||||||
|  | protocol that were easy enough to make backwards-compatible in the client, but | ||||||
|  | not worth the effort to do so on the server. | ||||||
|  | 
 | ||||||
|  | Mac, Linux, Raspberry Pi Users: | ||||||
|  | ------------------------------- | ||||||
|  | 
 | ||||||
|  |     curl -fsSL https://get.telebit.io | bash | ||||||
|  | 
 | ||||||
|  | That should be quick and easy, but you may need to reboot your computer. | ||||||
|  | 
 | ||||||
|  | Windows & npm users | ||||||
|  | ------------------- | ||||||
|  | 
 | ||||||
|  |     npm install -g npm | ||||||
|  | 
 | ||||||
|  | Note that on Windows the upgrade will **NOT** work while Telebit is | ||||||
|  | running. `telebit restart` should kill it but, on Windows, won't actually | ||||||
|  | restart it. | ||||||
|  | 
 | ||||||
|  | This is not well tested, so please contact me (aj@ppl.family) if you have any | ||||||
|  | trouble. | ||||||
|  | 
 | ||||||
|  | Upgrading *really* old versions | ||||||
|  | --------------------- | ||||||
|  | 
 | ||||||
|  | If you have a version of telebit prior to v0.18.1 (which may not even list its | ||||||
|  | version in `telebit help` yet), it'll probably be easiest to manually remove | ||||||
|  | the old telebit files first: | ||||||
|  | 
 | ||||||
|  |     sudo rm -rf ~/Applications/telebit* ~/.config/telebit* | ||||||
|  |     sudo rm -rf /opt/telebit* /etc/telebit* /etc/systemd/system/telebit* | ||||||
|  | 
 | ||||||
|  | You'll lose your current domain. If that's an issue, contact me and we can work | ||||||
|  | it out. | ||||||
|  | 
 | ||||||
|  | Rationale | ||||||
|  | --------- | ||||||
|  | 
 | ||||||
|  | > "If it ain't broke, don't fix it" - Ancient Redneck Proverb | ||||||
|  | 
 | ||||||
|  | > "When is broke, is most right time to fix" -  Ageless Chinese Adage | ||||||
|  | 
 | ||||||
|  | There's a delicate balance between the two and in my infinite wisdom I've | ||||||
|  | decided that now is the right time to fix. | ||||||
|  | There are some rather disruptive bugs in the network protocol and fixing them | ||||||
|  | means breaking most existing clients. | ||||||
|  | 
 | ||||||
|  | If you've been using telebit on a daily basis, especially with ssh, I believe | ||||||
|  | that'll you see benefit immediately and even moreso once the server is updated. | ||||||
|  | It's worth it. | ||||||
|  | 
 | ||||||
|  | Additional Notes | ||||||
|  | ---------------- | ||||||
|  | 
 | ||||||
|  | A number of good fixes are in here: | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | ### `telebit help` | ||||||
|  | 
 | ||||||
|  | The in-app cli help is now correctly documented. Not everything _works_ as | ||||||
|  | documented, however. Feel free to poke around and give me feedback. | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | ### `telebit ssh none` | ||||||
|  | 
 | ||||||
|  | Previously `telebit ssh none` behaved identically to `telebit ssh auto`. | ||||||
|  | 
 | ||||||
|  | The output correctly showed the actual behavior, but it didn't make sense. | ||||||
|  | 
 | ||||||
|  | Bascially this was happening: `telebit.ssh = telebit.ssh || 22`. So when it | ||||||
|  | it was `false` it became `true` | ||||||
|  | 
 | ||||||
|  | It was changed to this `if (!('ssh' in telebit)) { telebit.ssh = 22; }`. | ||||||
							
								
								
									
										12
									
								
								lib/extensions/admin/dist/upgrade.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,12 @@ | |||||||
|  | 'use strict'; | ||||||
|  | module.exports = function (opts, cb) { | ||||||
|  |   var pkg = opts.package; | ||||||
|  |   var root = opts.root; | ||||||
|  | 
 | ||||||
|  |   //console.log('DEBUG pkg', pkg);
 | ||||||
|  |   //console.log('DEBUG root', root);
 | ||||||
|  |   process.nextTick(function () { | ||||||
|  |     cb(null, { message: "upgrade complete" }); | ||||||
|  |   }); | ||||||
|  |   return { message: "placeholder upgrade: nothing to do yet" }; | ||||||
|  | }; | ||||||
							
								
								
									
										357
									
								
								lib/extensions/admin/index.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,357 @@ | |||||||
|  | <!DOCTYPE html> | ||||||
|  | <html> | ||||||
|  | 	<head> | ||||||
|  |     <meta charset="utf-8" /> | ||||||
|  |     <meta name="viewport" content="width=900"> | ||||||
|  | 		<title>Telebit™ Cloud</title> | ||||||
|  |     <link href="static-site-assets/styles/main.css" rel="stylesheet"> | ||||||
|  |     <link href="static-site-assets/styles/vertical-slide.css" rel="stylesheet"> | ||||||
|  |     <link href="static-site-assets/styles/1200.css" rel="stylesheet" media="(max-width:1075px)"> | ||||||
|  |     <style> | ||||||
|  |       @font-face { | ||||||
|  |         font-family: 'Source Sans Pro'; | ||||||
|  |         font-style: normal; | ||||||
|  |         font-display: block; | ||||||
|  |         font-weight: 400; | ||||||
|  |         src: local('Source Sans Pro Regular'), local('SourceSansPro-Regular'), url(/static-site-assets/fonts/6xK3dSBYKcSV-LCoeQqfX1RYOo3qOK7l.woff2) format('woff2'); | ||||||
|  |         unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; | ||||||
|  |       } | ||||||
|  |       @font-face { | ||||||
|  |         font-family: 'Source Sans Pro'; | ||||||
|  |         font-style: normal; | ||||||
|  |         font-weight: 700; | ||||||
|  |         font-display: block; | ||||||
|  |         src: local('Source Sans Pro Bold'), local('SourceSansPro-Bold'), url(/static-site-assets/fonts/6xKydSBYKcSV-LCoeQqfX1RYOo3ig4vwlxdu.woff2) format('woff2'); | ||||||
|  |         unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; | ||||||
|  |       } | ||||||
|  |       @font-face { | ||||||
|  |         font-family: 'Source Code Pro'; | ||||||
|  |         font-style: normal; | ||||||
|  |         font-weight: 400; | ||||||
|  |         src: local('Source Code Pro'), local('SourceCodePro-Regular'), url(/static-site-assets/fonts/HI_SiYsKILxRpg3hIP6sJ7fM7PqlPevW.woff2) format('woff2'); | ||||||
|  |         unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; | ||||||
|  |       } | ||||||
|  |     </style> | ||||||
|  | 	</head> | ||||||
|  | 	<body> | ||||||
|  |     <header> | ||||||
|  |       <div class="container"> | ||||||
|  |         <div class="logo">Telebit</div> | ||||||
|  |         <ul class="navigation-menu">  | ||||||
|  |           <li> | ||||||
|  |             <a class="nav-link" target="_blank" href="https://git.coolaj86.com/coolaj86/telebit.js">Docs</a> | ||||||
|  |           </li> | ||||||
|  |           <li> | ||||||
|  |             <a class="nav-link" target="_blank" href="https://www.patreon.com/coolaj86">Donate</a> | ||||||
|  |           </li> | ||||||
|  |           <li> | ||||||
|  |             <a class="link-button nav-link" href="#download-section">Download</a> | ||||||
|  |           </li> | ||||||
|  |         </ul> | ||||||
|  |       </div> | ||||||
|  |     </header><div class="hero"> | ||||||
|  |       <div class="container"> | ||||||
|  |         <div class="spiel"> | ||||||
|  |           <h1>Access your devices | ||||||
|  |             <br>Share your stuff | ||||||
|  |           </h1> | ||||||
|  |         </div> | ||||||
|  |         <div aria-hidden="true" class="demo-row"> | ||||||
|  |           <div class="demo-container"> | ||||||
|  |             <div class="demo-browser"> | ||||||
|  |               <div class="demo-browser-header"> | ||||||
|  |                 <div class="demo-browser-buttons"> | ||||||
|  |                   <div></div><div></div><div></div> | ||||||
|  |                 </div> | ||||||
|  |                 <div class="demo-browser-address-bar"> | ||||||
|  |                   <img src="static-site-assets/images/green-secure.png"> | ||||||
|  |                   <div class="demo-browser-url"> | ||||||
|  |                     https://jondoe.telebit.io | ||||||
|  |                   </div> | ||||||
|  |                 </div> | ||||||
|  |               </div> | ||||||
|  |               <div class="demo-browser-body"> | ||||||
|  |                 Hello world! | ||||||
|  |               </div> | ||||||
|  |             </div> | ||||||
|  |            | ||||||
|  |             <div class="demo-terminal"> | ||||||
|  |               <div class="demo-terminal-input"> | ||||||
|  |                 telebit http 3000 | ||||||
|  |               </div> | ||||||
|  |               <div class="demo-terminal-line">  | ||||||
|  |               </div> | ||||||
|  |               <div class="demo-terminal-output"> | ||||||
|  |                 Forwarding https://jondoe.telebit.io => localhost:3000 | ||||||
|  |               </div> | ||||||
|  |             </div> | ||||||
|  |           </div> | ||||||
|  |         </div> | ||||||
|  |       </div> | ||||||
|  |     </div> | ||||||
|  |     <div class="content"> | ||||||
|  |       <div class="container quickstart-container"> | ||||||
|  |         <h2 class="use-it">Use it <div class="sliding-vertical"> | ||||||
|  |           <!-- to add more of or remove some of these, you will also need to update | ||||||
|  |               ./static-site-assets/styles/vertical-slide.css | ||||||
|  |             to allow for the correct number of values.  Formulas for calculating | ||||||
|  |             new values are included in the style comments. | ||||||
|  |           --> | ||||||
|  |           <span class="accent-color">to test your webhooks.</span> | ||||||
|  |           <span class="accent-color">to show your project to Mom.</span> | ||||||
|  |           <span class="accent-color">to test your site on mobile.</span> | ||||||
|  |           <span class="accent-color">to work from 127.0.0.1.</span> | ||||||
|  |           <span class="accent-color">to access your raspberry pi.</span> | ||||||
|  |           <span class="accent-color">to build peer-to-peer apps.</span> | ||||||
|  |         </div></h2> | ||||||
|  |         <h2 id="download-section">Quickstart with bash</h2> | ||||||
|  |         <div class="quickstart-step"> | ||||||
|  |           <div class="quickstart-step-text"> | ||||||
|  |             <div class="quickstart-step-number">1</div> | ||||||
|  |             <div class="quickstart-step-name">Install Telebit</div> | ||||||
|  |           </div> | ||||||
|  |           <pre class="quickstart-terminal qickstart-terminal-prompt">curl https://get.telebit.io/ | bash</pre> | ||||||
|  |         </div> | ||||||
|  |         <div class="quickstart-step"> | ||||||
|  |           <div class="quickstart-step-text"> | ||||||
|  |             <div class="quickstart-step-number">2</div> | ||||||
|  |             <div class="quickstart-step-name">Claim your device via Email</div> | ||||||
|  |           </div> | ||||||
|  |           <pre class="quickstart-terminal">Hello! | ||||||
|  | 
 | ||||||
|  | Want to use 'Jon's Macbook Pro' with Telebit? | ||||||
|  | Just confirm your email address: | ||||||
|  | 
 | ||||||
|  |     <u>Confirm Email Address</u></pre> | ||||||
|  |         </div> | ||||||
|  |         <div class="quickstart-step"> | ||||||
|  |           <div class="quickstart-step-text"> | ||||||
|  |             <div class="quickstart-step-number">3</div> | ||||||
|  |             <div class="quickstart-step-name">Enjoy Anytime, Anywhere Access</div> | ||||||
|  |           </div> | ||||||
|  |           <pre class="quickstart-terminal"><strong>For Local Development</strong> | ||||||
|  | 
 | ||||||
|  |   <code class="quickstart-input">~/telebit http 3000</code> | ||||||
|  |   <code class="quickstart-output">Forwarding https://jondoe.telebit.io => localhost:3000</code> | ||||||
|  | 
 | ||||||
|  |   <code class="quickstart-input">curl -fsSL https://jondoe.telebit.io/</code> | ||||||
|  | 
 | ||||||
|  | <strong>For Sharing Files</strong> | ||||||
|  | 
 | ||||||
|  |   <code class="quickstart-input">~/telebit http ./project.zip</code> | ||||||
|  |   <code class="quickstart-output">Serving ./project.zip as https://jondoe.telebit.io</code> | ||||||
|  | 
 | ||||||
|  |   <code class="quickstart-input">curl -fsSL https://jondoe.telebit.io/</code> | ||||||
|  | 
 | ||||||
|  | <strong>For Access with SSH</strong> | ||||||
|  | 
 | ||||||
|  |   <code class="quickstart-input">~/telebit ssh auto</code> | ||||||
|  |   <code class="quickstart-output">Forwarding jondoe.telebit.io -p 5050 => localhost:22</code> | ||||||
|  |   <code class="quickstart-output">Forwarding ssh+https (openssl proxy) => localhost:22</code> | ||||||
|  | 
 | ||||||
|  |   <code class="quickstart-input">ssh -p 5050 jondoe.telebit.io</code> | ||||||
|  |   <code class="quickstart-input">ssh -o ProxyCommand="<a href="sclient/">sclient</a> %h" jondoe.telebit.io</code> | ||||||
|  | 
 | ||||||
|  | <strong>For Debugging with TCP</strong> | ||||||
|  | 
 | ||||||
|  |   <code class="quickstart-input">~/telebit tcp 9000</code> | ||||||
|  |   <code class="quickstart-output">Forwarding jondoe.telebit.io -p 5050 => localhost:9000</code> | ||||||
|  | 
 | ||||||
|  |   <code class="quickstart-input">netcat jondoe.telebit.io 5050</code></pre> | ||||||
|  |         </div> | ||||||
|  |       </div> | ||||||
|  |     </div> | ||||||
|  |     <div class="install-for"> | ||||||
|  |       <div class="container"> | ||||||
|  |         <h3>Install For</h3> | ||||||
|  |         <div class="install-badges"> | ||||||
|  |           <a class="install-badge" target="_blank" | ||||||
|  |               href="https://git.coolaj86.com/coolaj86/telebit.js#windows--nodejs"> | ||||||
|  |             <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"> | ||||||
|  |                 <g fill="none" fill-rule="evenodd"> | ||||||
|  |                     <path fill="#000" fill-rule="nonzero" d="M2 | ||||||
|  |                     4.819l8.11-1.105.004 7.823-8.107.047L2 4.819zm8.107 | ||||||
|  |                     7.62l.006 7.83-8.107-1.114v-6.769l8.1.053zm.983-8.87L21.844 | ||||||
|  |                     2v9.438l-10.754.085V3.57zm10.757 8.944l-.003 9.395L11.09 | ||||||
|  |                     20.39l-.015-7.895 10.772.018z"/> | ||||||
|  |                 </g> | ||||||
|  |             </svg> | ||||||
|  | 		        <span>Windows</span> | ||||||
|  | 					</a> | ||||||
|  |           <a class="install-badge"  target="_blank" | ||||||
|  |               href="https://git.coolaj86.com/coolaj86/telebit.js#mac--linux"> | ||||||
|  |             <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"> | ||||||
|  |                 <g fill="none" fill-rule="evenodd"> | ||||||
|  |                     <path fill="#1A1A1A" d="M21.41 8.22c-1.667 1.179-2.5 | ||||||
|  |                     2.597-2.5 4.254 0 1.986 1.03 3.509 3.09 4.57-.553 1.6-1.354 | ||||||
|  |                     2.993-2.402 4.178C18.549 22.407 17.592 23 16.726 23c-.408 | ||||||
|  |                     0-.965-.135-1.67-.404l-.34-.13c-.69-.27-1.302-.404-1.834-.404-.502 | ||||||
|  |                     0-1.052.105-1.649.316l-.426.153-.535.218c-.422.167-.848.251-1.277.251-1.012 | ||||||
|  |                     0-2.13-.833-3.352-2.498C3.88 18.117 3 15.518 3 12.704c0-2 | ||||||
|  |                     .55-3.61 1.649-4.832 1.1-1.222 2.555-1.833 4.368-1.833.677 | ||||||
|  |                     0 1.31.124 | ||||||
|  |                     1.9.371l.404.164.426.174c.378.16.684.24.917.24.298 0 | ||||||
|  |                     .63-.069.993-.207l.557-.218.415-.153c.663-.24 1.394-.36 | ||||||
|  |                     2.195-.36 1.9 0 3.429.724 4.586 2.17zM16.911 | ||||||
|  |                     1c.022.255.033.45.033.589 0 1.258-.458 2.361-1.376 | ||||||
|  |                     3.31-.917.95-1.983 1.424-3.199 1.424a5.474 5.474 0 0 | ||||||
|  |                     1-.055-.611c0-1.069.426-2.072 1.278-3.01.852-.938 | ||||||
|  |                     1.838-1.487 2.96-1.647.08-.015.2-.033.36-.055z"/> | ||||||
|  |                 </g> | ||||||
|  |             </svg> | ||||||
|  | 						<span>Mac</span> | ||||||
|  | 					</a> | ||||||
|  |           <a class="install-badge" target="_blank" | ||||||
|  |               href="https://git.coolaj86.com/coolaj86/telebit.js#mac--linux"> | ||||||
|  |             <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"> | ||||||
|  |                 <g fill="none" fill-rule="evenodd"> | ||||||
|  |                     <g transform="translate(1 2)"> | ||||||
|  |                         <rect width="22" height="20" fill="#000" rx="1"/> | ||||||
|  |                         <path fill="#FFF" d="M6.495 3.942v1.125l-4.12 | ||||||
|  |                         1.566V5.551l2.882-1.047-2.882-1.056V2.375l4.12 | ||||||
|  |                         1.567zm.32 3.592h4.327v.779H6.814v-.78z"/> | ||||||
|  |                     </g> | ||||||
|  |                 </g> | ||||||
|  |             </svg> | ||||||
|  | 						<span>Linux</span> | ||||||
|  | 					</a> | ||||||
|  |           <a class="install-badge" target="_blank" | ||||||
|  |               href="https://git.coolaj86.com/coolaj86/telebit.js#mac--linux"> | ||||||
|  |             <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"> | ||||||
|  |                 <g fill="none" fill-rule="evenodd"> | ||||||
|  |                     <g fill="#000" fill-rule="nonzero"> | ||||||
|  |                         <path d="M6.502 3.124c2.152 1.11 3.403 2.007 4.088 | ||||||
|  |                         2.771-.35 1.407-2.182 1.471-2.851 | ||||||
|  |                         1.432.137-.064.251-.14.292-.258-.168-.12-.764-.012-1.18-.246.16-.033.235-.065.31-.183-.394-.125-.817-.233-1.066-.441.134.002.26.03.435-.092-.352-.19-.727-.34-1.019-.63.182-.004.378-.001.435-.068a3.924 | ||||||
|  |                         3.924 0 0 | ||||||
|  |                         1-.819-.665c.255.031.363.005.424-.04-.243-.25-.552-.46-.698-.767.189.065.362.09.486-.006-.082-.186-.437-.296-.641-.733.199.02.41.044.452 | ||||||
|  |                         0-.092-.376-.25-.588-.406-.807.426-.006 1.071.002 | ||||||
|  |                         1.042-.034l-.263-.27c.416-.112.841.018 | ||||||
|  |                         1.15.115.14-.11-.002-.248-.171-.39.353.048.673.129.962.241.154-.14-.1-.279-.224-.418.547.104.778.25 | ||||||
|  |                         1.008.395.167-.16.01-.296-.103-.435.412.153.624.35.848.544.075-.102.192-.177.051-.424.293.169.513.367.676.59.18-.115.108-.273.109-.418.304.247.497.51.733.767.047-.034.089-.152.126-.338.725.704 | ||||||
|  |                         1.75 2.476.263 | ||||||
|  |                         3.179-1.264-1.044-2.775-1.802-4.45-2.371zM17.921 | ||||||
|  |                         3.124c-2.152 1.11-3.403 2.007-4.089 2.771.351 1.407 | ||||||
|  |                         2.183 1.471 2.852 | ||||||
|  |                         1.432-.137-.064-.251-.14-.292-.258.168-.12.764-.012 | ||||||
|  |                         1.18-.246-.16-.033-.235-.065-.31-.183.393-.125.817-.233 | ||||||
|  |                         1.066-.441-.135.002-.26.03-.436-.092.352-.19.728-.34 | ||||||
|  |                         1.02-.63-.182-.004-.379-.001-.436-.068.323-.2.594-.422.82-.665-.255.031-.363.005-.424-.04.243-.25.551-.46.698-.767-.189.065-.362.09-.487-.006.083-.186.438-.296.642-.733-.2.02-.41.044-.453 | ||||||
|  |                         0 | ||||||
|  |                         .093-.376.251-.588.407-.807-.426-.006-1.071.002-1.042-.034l.263-.27c-.416-.112-.842.018-1.15.115-.14-.11.002-.248.171-.39a4.182 | ||||||
|  |                         4.182 0 0 | ||||||
|  |                         0-.962.241c-.154-.14.1-.279.223-.418-.546.104-.778.25-1.008.395-.166-.16-.01-.296.103-.435-.411.153-.624.35-.847.544-.076-.102-.192-.177-.052-.424a2.149 | ||||||
|  |                         2.149 0 0 | ||||||
|  |                         0-.675.59c-.181-.115-.108-.273-.109-.418-.304.247-.497.51-.733.767-.048-.034-.09-.152-.126-.338-.725.704-1.75 | ||||||
|  |                         2.476-.263 3.179 1.264-1.044 2.775-1.802 | ||||||
|  |                         4.449-2.371zM14.818 17.45c0 1.313-1.154 2.377-2.578 | ||||||
|  |                         2.377s-2.578-1.064-2.578-2.377c0-1.313 1.154-2.377 | ||||||
|  |                         2.578-2.377s2.578 1.064 2.578 2.377zM10.153 | ||||||
|  |                         10.363c1.204.426 1.773 1.922 1.27 3.343-.501 1.42-1.884 | ||||||
|  |                         2.227-3.088 | ||||||
|  |                         1.801-1.204-.426-1.773-1.922-1.271-3.343.502-1.42 | ||||||
|  |                         1.885-2.227 3.09-1.801zM14.226 10.236c-1.204.426-1.773 | ||||||
|  |                         1.922-1.27 3.343.501 1.42 1.884 2.227 3.088 1.801 | ||||||
|  |                         1.204-.425 1.773-1.922 | ||||||
|  |                         1.271-3.342-.502-1.42-1.885-2.227-3.089-1.802zM5.41 | ||||||
|  |                         11.803c1.153-.309.389 4.771-.55 | ||||||
|  |                         4.355-1.032-.83-1.364-3.262.55-4.355zM18.737 | ||||||
|  |                         11.74c-1.154-.309-.39 4.771.549 4.354 1.032-.83 | ||||||
|  |                         1.364-3.261-.55-4.354zM14.818 7.957c1.99-.336 3.647.847 | ||||||
|  |                         3.58 3.005-.066.827-4.313-2.882-3.58-3.005zM9.32 | ||||||
|  |                         7.894c-1.99-.336-3.646.846-3.58 3.004.066.828 | ||||||
|  |                         4.313-2.881 3.58-3.004zM12.178 | ||||||
|  |                         7.39c-1.187-.03-2.327.882-2.33 1.411-.003.643.939 1.302 | ||||||
|  |                         2.339 1.318 1.429.01 2.34-.527 | ||||||
|  |                         2.345-1.19.006-.752-1.3-1.55-2.354-1.539zM12.251 | ||||||
|  |                         20.578c1.036-.045 2.425.333 2.428.836.017.488-1.26 | ||||||
|  |                         1.59-2.497 | ||||||
|  |                         1.569-1.28.055-2.536-1.05-2.52-1.432-.019-.56 1.56-.999 | ||||||
|  |                         2.589-.973zM8.426 17.6c.737.888 1.073 2.449.458 | ||||||
|  |                         2.909-.582.351-1.996.207-3-1.237-.678-1.211-.591-2.444-.115-2.806.711-.433 | ||||||
|  |                         1.81.152 2.657 1.134zM15.929 17.318c-.798.935-1.242 | ||||||
|  |                         2.64-.66 3.188.556.427 2.05.367 | ||||||
|  |                         3.153-1.164.801-1.028.533-2.746.075-3.201-.68-.526-1.656.147-2.568 | ||||||
|  |                         1.177z"/> | ||||||
|  |         </g> | ||||||
|  |     </g> | ||||||
|  | </svg> | ||||||
|  | 						<span>Raspberry Pi</span> | ||||||
|  | 					</a> | ||||||
|  |         </div> | ||||||
|  |       </div> | ||||||
|  |     </div> | ||||||
|  |     <div class="feature-list"> | ||||||
|  |       <div class="container"> | ||||||
|  |         <h2>Features</h2> | ||||||
|  |         <div class="feature-badges"> | ||||||
|  |           <div class="feature-badge"> | ||||||
|  | 						<img src="static-site-assets/images/lock.svg" /> | ||||||
|  | 						<div> | ||||||
|  | 							Secure https for all tunnels | ||||||
|  | 						</div> | ||||||
|  |           </div> | ||||||
|  |           <div class="feature-badge"> | ||||||
|  | 						<img src="static-site-assets/images/computer.svg" /> | ||||||
|  | 						<div> | ||||||
|  |           		Show your work to anyone | ||||||
|  | 						</div> | ||||||
|  | 					</div> | ||||||
|  |           <div class="feature-badge"> | ||||||
|  | 						<img src="static-site-assets/images/language.svg" /> | ||||||
|  | 						<div> | ||||||
|  | 							Test API Webhooks | ||||||
|  | 						</div> | ||||||
|  |           </div> | ||||||
|  |           <div class="feature-badge"> | ||||||
|  | 						<img src="static-site-assets/images/cloud.svg" /> | ||||||
|  | 						<div> | ||||||
|  | 							Test your UI in cloud browsers | ||||||
|  | 						</div> | ||||||
|  |           </div> | ||||||
|  |         </div> | ||||||
|  |       </div> | ||||||
|  |     </div> | ||||||
|  |     <div class="donate-section"> | ||||||
|  |       <div class="container"> | ||||||
|  |         <h2>Donate and become a sponsor of a more open web</h2> | ||||||
|  |         <p>We're on a mission to build a more open web. Telebit is still in it's | ||||||
|  |           early days and the development is supported by generous sponsors like | ||||||
|  |           you. Make a recurring or one-time donation today. | ||||||
|  |         </p> | ||||||
|  |         <a class="link-button" target="_blank" href="https://www.patreon.com/coolaj86">Make a donation</a> | ||||||
|  |       </div> | ||||||
|  |     </div> | ||||||
|  | 		<div class="mailing-list-form"> | ||||||
|  |       <div class="container"> | ||||||
|  | 				<h2>Join our mailing list</h2> | ||||||
|  | 				<form class="js-inline-email-form email-signup-form" novalidate> | ||||||
|  |           <div class="form-error js-inactive"></div> | ||||||
|  |           <div class="success-message js-inactive">Thank you for joining!</div> | ||||||
|  |           <span class="input-container email"> | ||||||
|  |             <div class="input-error email js-inactive"></div> | ||||||
|  |             <input type="email" name="email" id="email" placeholder="Email"> | ||||||
|  |           </span> | ||||||
|  | 					<input class="link-button" type="submit" value="Join"> | ||||||
|  | 				</form> | ||||||
|  | 				<ul> | ||||||
|  |           <li><img src="static-site-assets/images/done.svg" />Get exclusive invites to try new features</li> | ||||||
|  | 					<li><img src="static-site-assets/images/done.svg" />Get updates on our progress</li> | ||||||
|  | 					<li><img src="static-site-assets/images/done.svg" />We'll never spam you</li> | ||||||
|  | 				</ul> | ||||||
|  | 			</div> | ||||||
|  | 		</div> | ||||||
|  | 		<footer> | ||||||
|  |       <div class="container"> | ||||||
|  | 				<div class="logo"> | ||||||
|  | 					Telebit | ||||||
|  | 				</div> | ||||||
|  | 				<ul clss="footer-links"> | ||||||
|  | 					<li><a href="">Privacy</a></li> | ||||||
|  | 					<li><a href="">Terms</a></li> | ||||||
|  | 				</ul> | ||||||
|  | 			</div> | ||||||
|  | 		</footer> | ||||||
|  | 	</body> | ||||||
|  |   <script src="./static-site-assets/scripts/form-processing.js"></script> | ||||||
|  | </html> | ||||||
							
								
								
									
										215
									
								
								lib/extensions/admin/js/account.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,215 @@ | |||||||
|  | /*global Vue*/ | ||||||
|  | (function () { | ||||||
|  |   'use strict'; | ||||||
|  |   var OAUTH3 = window.OAUTH3; | ||||||
|  |   var oauth3 = OAUTH3.create({ | ||||||
|  |     host: window.location.host | ||||||
|  |   , pathname: window.location.pathname.replace(/\/[^\/]*$/, '/') | ||||||
|  |   }); | ||||||
|  |   //var $ = function () { return document.querySelector.apply(document, arguments); };
 | ||||||
|  |   var sessionStr = localStorage.getItem('session'); | ||||||
|  |   var session; | ||||||
|  |   if (sessionStr) { | ||||||
|  |     try { | ||||||
|  |       session = JSON.parse(sessionStr); | ||||||
|  |     } catch(e) { | ||||||
|  |       // ignore
 | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   var dnsRecords = { | ||||||
|  |     "telebit.ppl.family": "178.128.3.196" | ||||||
|  |   , "telebit.cloud": "46.101.97.218" | ||||||
|  |   }; | ||||||
|  |   var vueData = { | ||||||
|  |     claims: [] | ||||||
|  |   , domains: [] | ||||||
|  |   , devices: [] | ||||||
|  |   , newDomain: null | ||||||
|  |   , newDomainWildcard: false | ||||||
|  |   , newEmail: null | ||||||
|  |   , hasAccount: false | ||||||
|  |   , token: null | ||||||
|  |   , spinner: false | ||||||
|  |   , site: { deviceDomain: document.location.hostname, deviceDomainA: dnsRecords[document.location.hostname] } | ||||||
|  |   }; | ||||||
|  |   var vueMethods = { | ||||||
|  |     challengeDns: function () { | ||||||
|  |       // we could do a checkbox
 | ||||||
|  |       var wildcard = vueData.newDomainWildcard || false; | ||||||
|  |       if (!/(\*\.)?[a-z0-9][a-z0-9\.\-_]\.[a-z0-9]{2,}/.test(vueData.newDomain)) { | ||||||
|  |         window.alert("invalid domain name '" + vueData.newDomain + "'"); | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|  |       // we can just detect by text
 | ||||||
|  |       if ('*.' === vueData.newDomain.slice(0, 2)) { | ||||||
|  |         vueData.newDomain = vueData.newDomain.slice(2); | ||||||
|  |         wildcard = true; | ||||||
|  |       } | ||||||
|  |       return oauth3.request({ | ||||||
|  |         url: 'https://api.' + location.hostname + '/api/telebit.cloud/account/authorizations/new' | ||||||
|  |       , method: 'POST' | ||||||
|  |       , session: session | ||||||
|  |       , data: { type: 'dns', value: vueData.newDomain, wildcard: wildcard } | ||||||
|  |       }).then(function (resp) { | ||||||
|  |         vueData.claims.unshift(resp.data.claim); | ||||||
|  |       }); | ||||||
|  |     } | ||||||
|  |   , checkDns: function (claim) { | ||||||
|  |       return oauth3.request({ | ||||||
|  |         url: 'https://api.' + location.hostname + '/api/telebit.cloud/account/authorizations/new/:value/:challenge' | ||||||
|  |           .replace(/:value/g, claim.value) | ||||||
|  |           .replace(/:challenge/g, claim.challenge) | ||||||
|  |       , method: 'POST' | ||||||
|  |       , session: session | ||||||
|  |       }); | ||||||
|  |     } | ||||||
|  |   , challengeEmail: function () { | ||||||
|  |       console.log("A new (Email) challenger!", vueData); | ||||||
|  |     } | ||||||
|  |   , pushDomain: function (dev) { | ||||||
|  |       // TODO do some local validation too
 | ||||||
|  |       console.log('pushDomain', dev); | ||||||
|  |       vueData.spinner = true; | ||||||
|  |       return oauth3.request({ | ||||||
|  |         url: 'https://api.' + location.hostname + '/api/telebit.cloud/devices/:dev/:name' | ||||||
|  |           .replace(/:dev/g, dev.socketId) | ||||||
|  |           .replace(/:name/g, dev.newDomain) | ||||||
|  |       , method: 'POST' | ||||||
|  |       , session: session | ||||||
|  |       }).catch(function (err) { | ||||||
|  |         console.error(err); | ||||||
|  |         window.alert(err.toString()); | ||||||
|  |       }).then(function () { | ||||||
|  |         dev.newDomain = ''; | ||||||
|  |         vueData.spinner = false; | ||||||
|  |       }); | ||||||
|  |     } | ||||||
|  |   , login: function () { | ||||||
|  |       var email = vueData.newEmail; | ||||||
|  |       vueMethods.logout(); | ||||||
|  |       onClickLogin(email); | ||||||
|  |     } | ||||||
|  |   , logout: function () { | ||||||
|  |       sessionStorage.clear(); | ||||||
|  |       vueData.hasAccount = false; | ||||||
|  |       // TODO OAUTH3._logoutHelper(directives, opts)
 | ||||||
|  |     } | ||||||
|  |   }; | ||||||
|  |   var app = new Vue({ | ||||||
|  |     el: '.v-app' | ||||||
|  |   , data: vueData | ||||||
|  |   , methods: vueMethods | ||||||
|  |   }); | ||||||
|  |   app = null; | ||||||
|  | 
 | ||||||
|  |   function listStuff(data) { | ||||||
|  |     //window.alert("TODO: show authorized devices, domains, and connectivity information");
 | ||||||
|  |     vueData.hasAccount = true; | ||||||
|  |     vueData.domains = data.domains; | ||||||
|  |     vueData.devices = data.devices; | ||||||
|  |     vueData.claims = data.claims; | ||||||
|  |   } | ||||||
|  |   function loadAccount(session) { | ||||||
|  |     return oauth3.request({ | ||||||
|  |       url: 'https://api.' + location.hostname + '/api/telebit.cloud/account' | ||||||
|  |     , session: session | ||||||
|  |     }).then(function (resp) { | ||||||
|  | 
 | ||||||
|  |       console.info("Telebit Account:"); | ||||||
|  |       console.log(resp.data); | ||||||
|  | 
 | ||||||
|  |       if (resp.data && resp.data.domains) { | ||||||
|  |         listStuff(resp.data); | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       if (1 === resp.data.accounts.length) { | ||||||
|  |         listStuff(resp); | ||||||
|  |       } else if (0 === resp.data.accounts.length) { | ||||||
|  |         return oauth3.request({ | ||||||
|  |           url: 'https://api.' + location.hostname + 'api/telebit.cloud/account' | ||||||
|  |         , method: 'POST' | ||||||
|  |         , session: session | ||||||
|  |         , body: { | ||||||
|  |             email: vueData.newEmail | ||||||
|  |           } | ||||||
|  |         }).then(function (resp) { | ||||||
|  |           listStuff(resp); | ||||||
|  |         }); | ||||||
|  |       } if (resp.data.accounts.length > 2) { | ||||||
|  |         window.alert("Multiple accounts."); | ||||||
|  |       } else { | ||||||
|  |         window.alert("Bad response."); | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   function onChangeProvider(providerUri) { | ||||||
|  |     // example https://oauth3.org
 | ||||||
|  |     return oauth3.setIdentityProvider(providerUri); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   // This opens up the login window for the specified provider
 | ||||||
|  |   //
 | ||||||
|  |   function onClickLogin(email) { | ||||||
|  |     // TODO check subject for provider viability
 | ||||||
|  |     vueData.spinner = true; | ||||||
|  |     return oauth3.authenticate({ | ||||||
|  |       subject: email | ||||||
|  |     , scope: 'email@oauth3.org' | ||||||
|  |     }).then(function (session) { | ||||||
|  | 
 | ||||||
|  |       console.info('Authentication was Successful:'); | ||||||
|  |       console.log(session); | ||||||
|  | 
 | ||||||
|  |       // You can use the PPID (or preferably a hash of it) as the login for your app
 | ||||||
|  |       // (it securely functions as both username and password which is known only by your app)
 | ||||||
|  |       // If you use a hash of it as an ID, you can also use the PPID itself as a decryption key
 | ||||||
|  |       //
 | ||||||
|  |       console.info('Secure PPID (aka subject):', session.token.sub); | ||||||
|  | 
 | ||||||
|  |       return oauth3.request({ | ||||||
|  |         url: 'https://api.oauth3.org/api/issuer@oauth3.org/jwks/:sub/:kid.json' | ||||||
|  |           .replace(/:sub/g, session.token.sub) | ||||||
|  |           .replace(/:kid/g, session.token.iss) | ||||||
|  |       , session: session | ||||||
|  |       }).then(function (resp) { | ||||||
|  |         console.info("Public Key:"); | ||||||
|  |         console.log(resp.data); | ||||||
|  | 
 | ||||||
|  |         return oauth3.request({ | ||||||
|  |           url: 'https://api.oauth3.org/api/issuer@oauth3.org/acl/profile' | ||||||
|  |         , session: session | ||||||
|  |         }).then(function (resp) { | ||||||
|  | 
 | ||||||
|  |           console.info("Inspect Token:"); | ||||||
|  |           console.log(resp.data); | ||||||
|  | 
 | ||||||
|  |           localStorage.setItem('session', JSON.stringify(session)); | ||||||
|  |           loadAccount(session); | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |       }); | ||||||
|  | 
 | ||||||
|  |     }, function (err) { | ||||||
|  |       console.error('Authentication Failed:'); | ||||||
|  |       console.log(err); | ||||||
|  |     }).then(function () { | ||||||
|  |       vueData.spinner = false; | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   //$('body form.js-auth-form').addEventListener('submit', function onClickLoginHelper(ev) {
 | ||||||
|  |   //  ev.preventDefault();
 | ||||||
|  |   //  ev.stopPropagation();
 | ||||||
|  |   //  var email = $('.js-auth-subject').value;
 | ||||||
|  |   //  onClickLogin(email);
 | ||||||
|  |   //});
 | ||||||
|  |   onChangeProvider('oauth3.org'); | ||||||
|  |   if (session) { | ||||||
|  |     vueData.token = session.access_token; | ||||||
|  |     loadAccount(session); | ||||||
|  |   } | ||||||
|  | }()); | ||||||
							
								
								
									
										65
									
								
								lib/extensions/admin/js/app.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,65 @@ | |||||||
|  | (function () { | ||||||
|  | 'use strict'; | ||||||
|  | 
 | ||||||
|  | document.body.hidden = false; | ||||||
|  | 
 | ||||||
|  | function formSubmit() { | ||||||
|  |   // to be used for good, not evil
 | ||||||
|  |   var msg = { | ||||||
|  |     name: document.querySelector('.js-list-comment').value | ||||||
|  |   , address: document.querySelector('.js-list-address').value | ||||||
|  |   , list: 'telebit@ppl.family' | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   window.fetch('https://api.ppl.family/api/ppl.family/public/list', { | ||||||
|  |     method: 'POST' | ||||||
|  |   , cors: true | ||||||
|  |   , headers: new Headers({ 'Content-Type': 'application/json' }) | ||||||
|  |   , body: JSON.stringify(msg) | ||||||
|  |   }).then(function (resp) { | ||||||
|  |     return resp.json().then(function (data) { | ||||||
|  |       if (data.error) { | ||||||
|  |         window.alert("Couldn't save your message. Email coolaj86@gmail.com instead."); | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|  |       document.querySelector('.js-list-form').hidden = true; | ||||||
|  |       document.querySelector('.js-list-form').className += ' hidden'; | ||||||
|  |       document.querySelector('.js-list-thanks').hidden = false; | ||||||
|  |       document.querySelector('.js-list-thanks').className = document.querySelector('.js-list-thanks').className.replace(/\s*hidden\b/, ''); | ||||||
|  |     }, function () { | ||||||
|  |       window.alert("Couldn't save your message. Email coolaj86@gmail.com instead."); | ||||||
|  |     }); | ||||||
|  |   }, function () { | ||||||
|  |     window.alert("Didn't get your message. Bad network connection? Email coolaj86@gmail.com instead."); | ||||||
|  |   }); | ||||||
|  | } | ||||||
|  | document.body.addEventListener('submit', function (ev) { | ||||||
|  |   if (ev.target.matches('.js-list-form')) { | ||||||
|  |     ev.preventDefault(); | ||||||
|  |     ev.stopPropagation(); | ||||||
|  |     formSubmit(); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  | }); | ||||||
|  | document.body.addEventListener('click', function (ev) { | ||||||
|  |   if (ev.target.matches('.js-list-submit')) { | ||||||
|  |     ev.preventDefault(); | ||||||
|  |     ev.stopPropagation(); | ||||||
|  |     formSubmit(); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |   /* | ||||||
|  |   if (ev.target.closest('.js-navbar-toggle')) { | ||||||
|  |     ev.preventDefault(); | ||||||
|  |     ev.stopPropagation(); | ||||||
|  |     if (/show/.test(document.querySelector('.js-navbar-collapse').className)) { | ||||||
|  |       document.querySelector('.js-navbar-collapse').className = document.querySelector('.js-navbar-collapse').className.replace(/\s+show\b/, ''); | ||||||
|  |     } else { | ||||||
|  |       document.querySelector('.js-navbar-collapse').className += ' show'; | ||||||
|  |     } | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |   */ | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | }()); | ||||||
							
								
								
									
										28
									
								
								lib/extensions/admin/legal/index.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,28 @@ | |||||||
|  | <head> | ||||||
|  |   <style> | ||||||
|  |     body { | ||||||
|  |       margin: 1em; | ||||||
|  |     } | ||||||
|  |   </style> | ||||||
|  | </head> | ||||||
|  | 
 | ||||||
|  | <body> | ||||||
|  |   <h1>Terms of Service</h1> | ||||||
|  |   <p>To be used for good, not evil.</p> | ||||||
|  | 
 | ||||||
|  |   <h1>Privacy</h1> | ||||||
|  |   <p>We'll keep your info to ourselves.</p> | ||||||
|  | 
 | ||||||
|  |   <h1>License</h1> | ||||||
|  |   <p>There are Commercial and Open Source versions of Telebit<br>(kinda like how Google has Chrome and Chromium). | ||||||
|  |   </p> | ||||||
|  |   <p> | ||||||
|  |   The Open Source versions are available as | ||||||
|  |   <li><a href="https://git.coolaj86.com/coolaj86/telebit.js" target="_blank">Telebit Remote</a> (the "client" daemon)</li> | ||||||
|  |   <li><a href="https://git.coolaj86.com/coolaj86/telebit.js" target="_blank">Telebit Relay</a> (the service daemon)</li> | ||||||
|  |   </p> | ||||||
|  | 
 | ||||||
|  |   <h1>Trademark</h1> | ||||||
|  |   <p>Telebit is a trademark of AJ ONeal | ||||||
|  |   </p> | ||||||
|  | </body> | ||||||
							
								
								
									
										228
									
								
								lib/extensions/admin/login/css/main.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,228 @@ | |||||||
|  | body { | ||||||
|  |     font-family: Source Sans Pro, sans-serif; | ||||||
|  |     font-size: 18px; | ||||||
|  |     color: #1a1a1a; | ||||||
|  |     letter-spacing: -0.022222222em; | ||||||
|  |     line-height: 1.33; | ||||||
|  |     margin: 0; | ||||||
|  |     padding-bottom: 4em; | ||||||
|  |     box-sizing: border-box; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | p { | ||||||
|  |     margin: 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | h2 { | ||||||
|  |     font-size: 1.777777778em; | ||||||
|  |     margin: 0 0 1em 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | svg { | ||||||
|  |     width: 1.333333333em; | ||||||
|  |     height: 1.333333333em; | ||||||
|  |     fill: #1a1a1a; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | svg.icon-computer {width: 4em;height: 4em;} | ||||||
|  | 
 | ||||||
|  | button { | ||||||
|  |     width: 100%; | ||||||
|  |     background-color: #1a1a1a; | ||||||
|  |     border: none; | ||||||
|  |     font-size: 1em; | ||||||
|  |     color: white; | ||||||
|  |     padding: 0.44444em; | ||||||
|  |     margin: 1em 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | button:disabled { | ||||||
|  |     background-color: #d9d9d9; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | input[type=text] { | ||||||
|  |     font-size: 1em; | ||||||
|  |     padding: 0.444444444em 0.888889em; | ||||||
|  |     width: 100%; | ||||||
|  |     border: solid 1px #d9d9d9; | ||||||
|  |     border-radius: 2px; | ||||||
|  |     box-sizing: border-box; | ||||||
|  |     margin: 0.888888889em 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .container { | ||||||
|  |     text-align: center; | ||||||
|  |     width: 17.777777778em; | ||||||
|  |     margin: auto; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .checkbox-array { | ||||||
|  |     display: flex; | ||||||
|  |     flex-direction: column; | ||||||
|  |     padding: 1em 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .checkbox-array input[type=checkbox] { | ||||||
|  |     opacity: 0; | ||||||
|  |     position: absolute; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .checkbox-array input[type=checkbox] ~ .icon-checked-box { | ||||||
|  |     display: none; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .checkbox-array input[type=checkbox] ~ .icon-unchecked-box { | ||||||
|  |     display: initial; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .checkbox-array input[type=checkbox]:checked ~ .icon-checked-box { | ||||||
|  |     display: initial; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .checkbox-array input[type=checkbox]:checked ~ .icon-unchecked-box { | ||||||
|  |     display: none; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .checkbox-array input[type=checkbox]:focus ~ .icon-checked-box, .checkbox-array input[type=checkbox]:focus ~ .icon-unchecked-box { | ||||||
|  |     background: #DDDDDD; | ||||||
|  | } | ||||||
|  | .checkbox-array .icon-checked-box, .checkbox-array .icon-unchecked-box { | ||||||
|  |     margin-right: 0.666666667em; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .checkbox-array label { | ||||||
|  |     display: flex; | ||||||
|  |     height: 1.333333333em; | ||||||
|  |     font-size: 0.833333333em; | ||||||
|  |     margin: 0.4em 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | h1.logo { | ||||||
|  |     font-size: 1.555555556em; | ||||||
|  |     margin-bottom: 1.777777778em; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | svg.authorized-check { | ||||||
|  |     fill: #63f794; | ||||||
|  |     margin-right: 0.666666667em; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .progress .row { | ||||||
|  |     display:  flex; | ||||||
|  |     justify-content: left; | ||||||
|  |     margin: 0 0 0.6666em 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .spinner-ball { | ||||||
|  |     width: 4px; | ||||||
|  |     height: 4px; | ||||||
|  |     border-radius: 5px; | ||||||
|  |     background: #1a1a1a; | ||||||
|  |     margin: 2px; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | span.spinner { | ||||||
|  |     display: flex; | ||||||
|  |     align-items: center; | ||||||
|  |     margin-right: 0.666666667em; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .important-text { | ||||||
|  |     font-weight: bold; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .progress { | ||||||
|  |     display: inline-block; | ||||||
|  |     margin-bottom: 1.111177778em; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .debugging-info-container { | ||||||
|  |     text-align: center; | ||||||
|  |     position: fixed; | ||||||
|  |     bottom: 0; | ||||||
|  |     width: 100%; | ||||||
|  |     /* overflow: hidden; */ | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .debugging-info-container pre { | ||||||
|  |     word-break: break-all; | ||||||
|  |     white-space: pre-wrap; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .debugging-info { | ||||||
|  |     max-width: 65em; | ||||||
|  |     margin: 0 auto; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | span.debugging.button { | ||||||
|  |     display: inline-flex; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | span.js-debugging-button.debugging-button { | ||||||
|  |     display: inline-flex; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .debugging-button { | ||||||
|  |     display: inline-flex; | ||||||
|  |     padding: 0.3em; | ||||||
|  |     position: absolute; | ||||||
|  |     bottom: 100%; | ||||||
|  |     transform: translateX(-50%); | ||||||
|  |     background:  white; | ||||||
|  |     border: solid #eee 1px; | ||||||
|  |     border-radius: 5px 5px 0 0; | ||||||
|  |     border-bottom: none; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .debugging-info-container.visible .debugging-button svg { | ||||||
|  |     transform: rotate(180deg); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .debugging-button svg {transition: transform 0.3s;} | ||||||
|  | 
 | ||||||
|  | .debug-drawer { | ||||||
|  |     /* position: relative; */ | ||||||
|  |     transform: translateY(100%); | ||||||
|  |     transition: transform 0.3s; | ||||||
|  |     padding: 0.1em 0; | ||||||
|  |     background: white; | ||||||
|  |     pointer-events: initial; | ||||||
|  |     border-top: solid #eee 1px; | ||||||
|  |     padding-top: 1em; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | .debugging-info-container.visible .debug-drawer { | ||||||
|  |     transform: translateY(0); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .debugging-info-container { | ||||||
|  |     padding-top: 3em; | ||||||
|  |     overflow: hidden; | ||||||
|  |     pointer-events: none; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .spinner .spinner-ball { | ||||||
|  |   animation: pulsing 2s ease infinite; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .spinner .spinner-ball:nth-child(2) { | ||||||
|  |   animation-delay: 0.2s; | ||||||
|  | } | ||||||
|  | .spinner .spinner-ball:nth-child(3) { | ||||||
|  |   animation-delay: 0.4s; | ||||||
|  | } | ||||||
|  | @keyframes pulsing { | ||||||
|  |   0% {transform: scale(1);} | ||||||
|  |   35% {transform: scale(1);} | ||||||
|  |   60% {transform: scale(1.3);} | ||||||
|  |   75% {transform: scale(1.3);} | ||||||
|  |   100% {transform: scale(1);} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | .finish-button { | ||||||
|  |     margin-top: 2.222222222em; | ||||||
|  | } | ||||||
							
								
								
									
										161
									
								
								lib/extensions/admin/login/index.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,161 @@ | |||||||
|  | <!DOCTYPE html> | ||||||
|  | <html> | ||||||
|  | <head> | ||||||
|  |   <title>Telebit - Pair Device</title> | ||||||
|  |   <link href="./css/main.css" rel="stylesheet"> | ||||||
|  |   <style> | ||||||
|  |     @font-face { | ||||||
|  |       font-family: 'Source Sans Pro'; | ||||||
|  |       font-style: normal; | ||||||
|  |       font-display: block; | ||||||
|  |       font-weight: 400; | ||||||
|  |       src: local('Source Sans Pro Regular'), local('SourceSansPro-Regular'), url(/static-site-assets/fonts/6xK3dSBYKcSV-LCoeQqfX1RYOo3qOK7l.woff2) format('woff2'); | ||||||
|  |       unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; | ||||||
|  |     } | ||||||
|  |     @font-face { | ||||||
|  |       font-family: 'Source Sans Pro'; | ||||||
|  |       font-style: normal; | ||||||
|  |       font-weight: 700; | ||||||
|  |       font-display: block; | ||||||
|  |       src: local('Source Sans Pro Bold'), local('SourceSansPro-Bold'), url(/static-site-assets/fonts/6xKydSBYKcSV-LCoeQqfX1RYOo3ig4vwlxdu.woff2) format('woff2'); | ||||||
|  |       unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; | ||||||
|  |     } | ||||||
|  |   </style> | ||||||
|  |   <link rel="preload" href="/static-site-assets/fonts/6xKydSBYKcSV-LCoeQqfX1RYOo3ig4vwlxdu.woff2" as="font" crossorigin="anonymous"> | ||||||
|  |   <link rel="preload" href="/static-site-assets/fonts/6xK3dSBYKcSV-LCoeQqfX1RYOo3qOK7l.woff2" as="font" crossorigin="anonymous"> | ||||||
|  | 
 | ||||||
|  | </head> | ||||||
|  | <body> | ||||||
|  |   <script>document.body.hidden = true;</script> | ||||||
|  |   <!-- let's define our SVG that we will use later --> | ||||||
|  |   <svg width="0" height="0" viewBox="0 0 24 24"> | ||||||
|  |     <defs> | ||||||
|  |       <g id="svg-check"> | ||||||
|  |         <path fill="none" d="M0 0h24v24H0z"/> | ||||||
|  |         <path d="M9 16.2L4.8 12l-1.4 1.4L9 19 21 7l-1.4-1.4L9 16.2z"/> | ||||||
|  |       </g> | ||||||
|  |       <g id="svg-checked"> | ||||||
|  |         <path d="M0 0h24v24H0z" fill="none"/> | ||||||
|  |         <path d="M19 3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.11 0 2-.9 2-2V5c0-1.1-.89-2-2-2zm-9 14l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"/> | ||||||
|  |       </g> | ||||||
|  |       <g id="svg-unchecked"> | ||||||
|  |         <path d="M19 5v14H5V5h14m0-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2z"/> | ||||||
|  |         <path d="M0 0h24v24H0z" fill="none"/> | ||||||
|  |       </g> | ||||||
|  |       <g id="svg-download"> | ||||||
|  |         <path d="M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z"/> | ||||||
|  |         <path d="M0 0h24v24H0z" fill="none"/> | ||||||
|  |       </g> | ||||||
|  |       <g id="svg-computer"> | ||||||
|  |         <path d="M0 0h24v24H0z" fill="none"/> | ||||||
|  |         <path d="M20 18c1.1 0 1.99-.9 1.99-2L22 6c0-1.1-.9-2-2-2H4c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2H0v2h24v-2h-4zM4 6h16v10H4V6z"/> | ||||||
|  |       </g> | ||||||
|  |       <g id="svg-circle-check"> | ||||||
|  |         <path d="M0 0h24v24H0z" fill="none"/> | ||||||
|  |         <path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"/> | ||||||
|  |       </g> | ||||||
|  |       <g id="svg-arrow-down"> | ||||||
|  |         <path d="M7.41,8.59L12,13.17l4.59-4.58L18,10l-6,6l-6-6L7.41,8.59z"/> | ||||||
|  |         <path fill="none" d="M0,0h24v24H0V0z"/> | ||||||
|  |       </g> | ||||||
|  |     </defs> | ||||||
|  |   </svg> | ||||||
|  | 
 | ||||||
|  |   <div class="js-error" hidden> | ||||||
|  |     <h1>Invalid Pairing Link</h1> | ||||||
|  |     <div class="js-magic-link">'{{magic_link}}' isn't a valid pairing link code. | ||||||
|  |       <br>Links are only valid for a limited time, so you gotta act fast. | ||||||
|  |     </div> | ||||||
|  |   </div> | ||||||
|  |   <div class="container js-magic" hidden><form class="js-submit"> | ||||||
|  |     <h1 class="logo">Telebit</h1> | ||||||
|  |     <svg class="icon-computer" viewBox="0 0 24 24"> | ||||||
|  |       <use xlink:href="#svg-computer"></use> | ||||||
|  |     </svg> | ||||||
|  |     <h2>Pair <span class="js-hostname">Device</span></h1> | ||||||
|  |     <label><span class="important-text">Enter your device pairing code</span> | ||||||
|  |       <input type="text" name="pair-code" placeholder="ex: 0000" autofocus> | ||||||
|  |     </label> | ||||||
|  |     <div class="checkbox-array"> | ||||||
|  |       <label> | ||||||
|  |         <input name="telebit-agree" type="checkbox" required> | ||||||
|  |         <svg class="icon-checked-box" viewBox="0 0 24 24"> | ||||||
|  |           <use xlink:href="#svg-checked"></use> | ||||||
|  |         </svg> | ||||||
|  |         <svg class="icon-unchecked-box"  viewBox="0 0 24 24"> | ||||||
|  |           <use xlink:href="#svg-unchecked"></use> | ||||||
|  |         </svg> | ||||||
|  |         <div>Agree to <a target="_blank" href="/legal/">Telebit™ Terms of Service</a></div> | ||||||
|  |       </label> | ||||||
|  |       <label> | ||||||
|  |         <input name="letsencrypt-agree" type="checkbox" required> | ||||||
|  |         <svg class="icon-checked-box" viewBox="0 0 24 24"> | ||||||
|  |           <use xlink:href="#svg-checked"></use> | ||||||
|  |         </svg> | ||||||
|  |         <svg class="icon-unchecked-box" viewBox="0 0 24 24"> | ||||||
|  |           <use xlink:href="#svg-unchecked"></use> | ||||||
|  |         </svg> | ||||||
|  |         <div>Agree to <a target="_blank" href="https://letsencrypt.org/documents/LE-SA-v1.2-November-15-2017.pdf"> Let's Encrypt™ Terms of Service</a></div> | ||||||
|  |       </label> | ||||||
|  |     </div> | ||||||
|  |     <div> | ||||||
|  |       <button type="submit" disabled>Claim Device</button> | ||||||
|  |     </div> | ||||||
|  |   </form></div> | ||||||
|  |   <div class="container js-authz" hidden> | ||||||
|  |     <h1 class="logo">Telebit</h1> | ||||||
|  |     <svg class="icon-computer" viewBox="0 0 24 24"> | ||||||
|  |       <use xlink:href="#svg-computer"></use> | ||||||
|  |     </svg> | ||||||
|  |     <h2>Pair <span class="js-hostname">Device</span></h1> | ||||||
|  |     <div> | ||||||
|  |       <div class="progress"> | ||||||
|  |         <div class="row"> | ||||||
|  |           <svg class="authorized-check" viewBox="0 0 24 24"> | ||||||
|  |             <use xlink:href="#svg-circle-check"></use> | ||||||
|  |           </svg> | ||||||
|  |           Authorized | ||||||
|  |         </div> | ||||||
|  |         <div class="row"> | ||||||
|  |           <span class="spinner"> | ||||||
|  |             <div class="spinner-ball ball-1"></div> | ||||||
|  |             <div class="spinner-ball ball-1"></div> | ||||||
|  |             <div class="spinner-ball ball-1"></div> | ||||||
|  |           </span> | ||||||
|  |           Waiting for device to pair | ||||||
|  |         </div> | ||||||
|  |       </div> | ||||||
|  |     </div> | ||||||
|  |     <div class="important-text"> | ||||||
|  |       Check the command line on your device to finish pairing. | ||||||
|  |     </div> | ||||||
|  |   </div> | ||||||
|  |   <div class="js-debug-container debugging-info-container" hidden> | ||||||
|  |     <div class="debug-drawer"> | ||||||
|  |       <span class="js-debug-button debugging-button"> | ||||||
|  |         Debugging info <svg class="debugging-arrow" viewBox="0 0 24 24"> | ||||||
|  |           <use xlink:href="#svg-arrow-down"></use> | ||||||
|  |         </svg> | ||||||
|  |       </span> | ||||||
|  |       <div class="js-debug-info debugging-info"> | ||||||
|  |         <p><a class="js-new-href">{{js-new-href}}</a></p> | ||||||
|  |         <p class="js-serviceport">xxxxx</p> | ||||||
|  |         <p><small>Authorization Token: | ||||||
|  |           <pre><code class="js-token">{{js-token}}</code></pre></small></p> | ||||||
|  |       </div> | ||||||
|  |     </div> | ||||||
|  |   </div> | ||||||
|  |   <div class="container js-finish" hidden> | ||||||
|  |     <h1 class="logo">Telebit</h1> | ||||||
|  |     <svg class="icon-computer" viewBox="0 0 24 24"> | ||||||
|  |       <use xlink:href="#svg-computer"></use> | ||||||
|  |     </svg> | ||||||
|  |     <h2>Success!</h1> | ||||||
|  |     <div> | ||||||
|  |       <span class="important-text js-new-domain">______</span> is paired and ready to use for accessing your device and sharing your stuff. | ||||||
|  |     </div> | ||||||
|  |     <button class="js-finish-button finish-button">Take Me There</button> | ||||||
|  |     <script src="js/app.js"></script> | ||||||
|  |   </div> | ||||||
|  | </body> | ||||||
|  | </html> | ||||||
							
								
								
									
										180
									
								
								lib/extensions/admin/login/js/app.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,180 @@ | |||||||
|  | (function () { | ||||||
|  | 'use strict'; | ||||||
|  | 
 | ||||||
|  | var meta = {}; | ||||||
|  | var magic; | ||||||
|  | var domainname; | ||||||
|  | var port; | ||||||
|  | 
 | ||||||
|  | function checkStatus() { | ||||||
|  |   // TODO use Location or Link
 | ||||||
|  |   window.fetch(meta.baseUrl + 'api/telebit.cloud/pair_state/' + magic, { | ||||||
|  |     method: 'GET' | ||||||
|  |   , cors: true | ||||||
|  |   }).then(function (resp) { | ||||||
|  |     return resp.json().then(function (data) { | ||||||
|  |       console.log(data); | ||||||
|  |       if ('invalid' === data.status) { | ||||||
|  |         window.alert("something went wrong"); | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|  |       if ('complete' === data.status) { | ||||||
|  |         successScreen(); | ||||||
|  |         setTimeout(function () { | ||||||
|  |           //window.document.body.innerHTML += ('<img src="https://' + domainname + '/_apis/telebit.cloud/clear.gif">');
 | ||||||
|  |           // TODO once this is loaded (even error) Let's Encrypt is done,
 | ||||||
|  |           // then it's time to redirect to the domain. Yay!
 | ||||||
|  |         }, 1 * 1000); | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|  |       setTimeout(checkStatus, 2 * 1000); | ||||||
|  |     }, function (err) { | ||||||
|  |       console.error(err); | ||||||
|  |       setTimeout(checkStatus, 2 * 1000); | ||||||
|  |     }); | ||||||
|  |   }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function successScreen() { | ||||||
|  |   document.querySelector('.js-authz').hidden = true; | ||||||
|  |   document.querySelector('.js-finish-button').addEventListener('click', function(e) { | ||||||
|  |     window.location.href='https://' + domainname + "/#/serviceport=" + port; | ||||||
|  |   }); | ||||||
|  |   document.querySelectorAll('.js-new-domain').forEach(function(ele) { | ||||||
|  |     ele.innerHTML = domainname; | ||||||
|  |   }); | ||||||
|  |   document.querySelector('.js-finish').hidden = false; | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function submitCode(pair) { | ||||||
|  |   // TODO use Location or Link
 | ||||||
|  |   document.querySelector('.js-magic').hidden = true; | ||||||
|  |   window.fetch(meta.baseUrl + 'api/telebit.cloud/pair_code/', { | ||||||
|  |     method: 'POST' | ||||||
|  |   , headers: { | ||||||
|  |       'Content-Type': 'application/json' | ||||||
|  |     } | ||||||
|  |   , body: JSON.stringify({ | ||||||
|  |       magic: pair.magic | ||||||
|  |     , pin: pair.pin || pair.code | ||||||
|  |     , agree_tos: pair.agreeTos | ||||||
|  |     }) | ||||||
|  |   , cors: true | ||||||
|  |   }).then(function (resp) { | ||||||
|  |     return resp.json().then(function (data) { | ||||||
|  |       // TODO check for error (i.e. bad Pair Code / PIN)
 | ||||||
|  |       // shouldn't be pending (because we get here by being ready)
 | ||||||
|  |       // should poll over 'ready'
 | ||||||
|  | 
 | ||||||
|  |       console.log('Submit Code Response:'); | ||||||
|  |       console.log(data); | ||||||
|  |       if (data.error) { | ||||||
|  |         document.querySelector('.js-error').hidden = false; | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       setTimeout(checkStatus, 0); | ||||||
|  | 
 | ||||||
|  |       document.querySelector('.js-authz').hidden = false; | ||||||
|  |       document.querySelector('.js-debug-container').hidden = false; | ||||||
|  | 
 | ||||||
|  |       /* | ||||||
|  |       document.querySelectorAll('.js-token-data').forEach(function ($el) { | ||||||
|  |         $el.innerText = JSON.stringify(data, null, 2); | ||||||
|  |       }); | ||||||
|  |       */ | ||||||
|  |       document.querySelectorAll('.js-new-href').forEach(function ($el) { | ||||||
|  |         domainname = data.domains[0]; | ||||||
|  |         port = data.port; | ||||||
|  |         $el.href = 'https://' + data.domains[0] + '/'; | ||||||
|  |         $el.innerText = '🔐 https://' + data.domains[0]; | ||||||
|  |       }); | ||||||
|  |       document.querySelectorAll('.js-domainname').forEach(function ($el) { | ||||||
|  |         $el.innerText = data.domains.join(','); | ||||||
|  |       }); | ||||||
|  |       document.querySelectorAll('.js-serviceport').forEach(function ($el) { | ||||||
|  |         $el.innerText = data.ports.join(','); | ||||||
|  |       }); | ||||||
|  |       document.querySelectorAll('.js-token').forEach(function ($el) { | ||||||
|  |         $el.innerText = data.jwt; | ||||||
|  |       }); | ||||||
|  |     }, function (err) { | ||||||
|  |       console.error(err); | ||||||
|  |       document.querySelector('.js-error').hidden = false; | ||||||
|  |     }); | ||||||
|  |   }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function init() { | ||||||
|  |   magic = (window.location.hash || '').substr(2).replace(/magic=/, ''); | ||||||
|  | 
 | ||||||
|  |   if (!magic) { | ||||||
|  |     document.querySelector('body').hidden = false; | ||||||
|  |     document.querySelector('.js-error').hidden = false; | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   window.fetch(meta.baseUrl + meta.pair_request.pathname + '/' + magic, { | ||||||
|  |     method: 'GET' | ||||||
|  |   , cors: true | ||||||
|  |   }).then(function (resp) { | ||||||
|  |     return resp.json().then(function (data) { | ||||||
|  |       console.log('pair request data:'); | ||||||
|  |       console.log(data); | ||||||
|  |       document.querySelector('body').hidden = false; | ||||||
|  |       if (data.error) { | ||||||
|  |         document.querySelector('.js-error').hidden = false; | ||||||
|  |         document.querySelector('.js-magic-link').innerText = "Something went wrong. Perhaps an bad or expired link."; | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|  |       document.querySelector('.js-magic').hidden = false; | ||||||
|  |       document.querySelectorAll('.js-hostname').forEach(function(ele) { | ||||||
|  |         ele.innerText = data.hostname || 'Device'; | ||||||
|  |       }); | ||||||
|  |       //document.querySelector('.js-token-data').innerText = JSON.stringify(data, null, 2);
 | ||||||
|  |     }); | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   document.querySelector('.js-submit').addEventListener('submit', function (ev) { | ||||||
|  |     ev.preventDefault(); | ||||||
|  |     var pair = {}; | ||||||
|  |     pair.magic = magic; | ||||||
|  |     pair.code = document.querySelector('[name=pair-code]').value; | ||||||
|  |     pair.agreeTos = document.querySelector('[name=letsencrypt-agree]').checked | ||||||
|  |       && document.querySelector('[name=telebit-agree]').checked; | ||||||
|  |     console.log('Pair Form:'); | ||||||
|  |     console.log(pair); | ||||||
|  |     submitCode(pair); | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   var formElements = document.querySelector('.js-submit').elements; | ||||||
|  |   for(var i = 0; i < formElements.length; ++i) { | ||||||
|  |     var tosCheck = document.querySelector('[name=telebit-agree]'); | ||||||
|  |     var leCheck = document.querySelector('[name=letsencrypt-agree]'); | ||||||
|  |     var pairCodeInput = document.querySelector('[name=pair-code]'); | ||||||
|  |     formElements[i].addEventListener('input', function(ev) { | ||||||
|  |       if(tosCheck.checked && leCheck.checked && pairCodeInput.value.length) { | ||||||
|  |         document.querySelector('.js-submit button').disabled = false; | ||||||
|  |       } else { | ||||||
|  |         document.querySelector('.js-submit button').disabled = true; | ||||||
|  |       } | ||||||
|  |     }); | ||||||
|  |   }; | ||||||
|  |   document.querySelector('.js-debug-button').addEventListener("click", function(e) { | ||||||
|  |     document.querySelector('.js-debug-container').classList.toggle("visible"); | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | window.fetch('https://' + location.hostname + '/_apis/telebit.cloud/index.json', { | ||||||
|  |   method: 'GET' | ||||||
|  | , cors: true | ||||||
|  | }).then(function (resp) { | ||||||
|  |   return resp.json().then(function (_json) { | ||||||
|  |     meta = _json; | ||||||
|  |     meta.baseUrl = 'https://' + meta.api_host.replace(/:hostname/g, location.hostname) + '/'; | ||||||
|  |     init(); | ||||||
|  |   }); | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | }()); | ||||||
							
								
								
									
										18
									
								
								lib/extensions/admin/optify/index.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,18 @@ | |||||||
|  | <!DOCTYPE html> | ||||||
|  | <html> | ||||||
|  |   <head> | ||||||
|  |   <title>Optify - Containerize without the container</title> | ||||||
|  |   </head> | ||||||
|  | 
 | ||||||
|  |   <body> | ||||||
|  | 
 | ||||||
|  |   <h1>Optify</h1>  | ||||||
|  |   <p>containerize without the container</p> | ||||||
|  | 
 | ||||||
|  |   <p>Holds your hand without being so hands on. | ||||||
|  |   Like brew and Docker had a baby that <em>did</em> care about versions and <em>didn't</em> care about port numbers. | ||||||
|  |   Only for the masters of their domain. | ||||||
|  |   Be wise and be bold.</p> | ||||||
|  | 
 | ||||||
|  |   </body> | ||||||
|  | </html> | ||||||
							
								
								
									
										58
									
								
								lib/extensions/admin/optify/install
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,58 @@ | |||||||
|  | #!/bin/bash | ||||||
|  | #<pre><code> | ||||||
|  | set -e | ||||||
|  | set -u | ||||||
|  | 
 | ||||||
|  | # minimal os detection | ||||||
|  | my_os="$(uname -s | tr '[:upper:]' '[:lower:]')" | ||||||
|  | 
 | ||||||
|  | # https://github.com/golang/go/wiki/GoArm#supported-architectures | ||||||
|  | # minimal cpu arch detection | ||||||
|  | my_arch="$(uname -m)" | ||||||
|  | if [ "x86_64" == "$my_arch" ]; then | ||||||
|  |   my_arch="amd64" | ||||||
|  | elif [ "i386" == "$my_arch" ]; then | ||||||
|  |   my_arch="386" | ||||||
|  | elif [ -n "$($my_arch | grep arm)" ]; then | ||||||
|  | 	if [ -n "$(uname -a | grep aarch64)" ]; then | ||||||
|  | 		my_arch="arm64" | ||||||
|  | 	elif [ -n "$(uname -a | grep armv8l)" ]; then | ||||||
|  | 		my_arch="arm64" | ||||||
|  | 	elif [ -n "$(uname -a | grep armv7l)" ]; then | ||||||
|  | 		my_arch="armv7l" | ||||||
|  | 	elif [ -n "$(uname -a | grep armv6l)" ]; then | ||||||
|  | 		my_arch="armv6l" | ||||||
|  | 	else | ||||||
|  | 		echo "could not determine arm cpu architecture" >&2 | ||||||
|  | 		exit 1 | ||||||
|  | 	fi | ||||||
|  | else | ||||||
|  | 	echo "could not determine cpu architecture" >&2 | ||||||
|  | 	exit 1 | ||||||
|  | fi | ||||||
|  | 
 | ||||||
|  | # get optify for this cpu and arch | ||||||
|  | if [ -z "${OPTIFY_VERSION:-}" ]; then | ||||||
|  |   latest_version="$(curl -fsSL https://telebit.cloud/optify/latest)" | ||||||
|  |   OPTIFY_VERSION=$latest_version | ||||||
|  |   echo "Installing optify-$OPTIFY_VERSION (latest)" | ||||||
|  | else | ||||||
|  |   echo "Installing optify OPTIFY_VERSION=$OPTIFY_VERSION" | ||||||
|  | fi | ||||||
|  | 
 | ||||||
|  | # download to a tmp folder | ||||||
|  | #my_tmpdir="$(mktemp -d /tmp/optify.XXXXXXXX)" | ||||||
|  | my_tmpdir="$(mktemp -d -t optify.XXXXXXXX)" | ||||||
|  | my_url="https://telebit.cloud/optify/dist/${my_os}/${my_arch}/optify-${OPTIFY_VERSION}" | ||||||
|  | if [ -n "$(type -p curl || true)" ]; then | ||||||
|  |   my_out="$(curl -fsSL -o "$my_tmpdir/optify-${OPTIFY_VERSION}" "$my_url" || true)" | ||||||
|  | elif [ -n "$(type -p wget || true)" ]; then | ||||||
|  |   my_out="$(wget -q -c "$my_url" -O "$my_tmpdir/optify-${OPTIFY_VERSION}" || true)" | ||||||
|  | else | ||||||
|  |   echo "found neither wget nor curl" >&2 | ||||||
|  |   exit 1 | ||||||
|  | fi | ||||||
|  | 
 | ||||||
|  | # check for downloader success | ||||||
|  | chmod a+x "$my_tmpdir/optify-${OPTIFY_VERSION}" | ||||||
|  | "$my_tmpdir/optify-${OPTIFY_VERSION}" --install | ||||||
							
								
								
									
										1
									
								
								lib/extensions/admin/optify/latest
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1 @@ | |||||||
|  | v0.0.5 | ||||||
							
								
								
									
										102
									
								
								lib/extensions/admin/sclient/index.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,102 @@ | |||||||
|  | <!DOCTYPE html> | ||||||
|  | <html> | ||||||
|  |   <head> | ||||||
|  |   <title>sclient - tls unwrapper for Windows, Mac, and Linux</title> | ||||||
|  |   </head> | ||||||
|  | 
 | ||||||
|  |   <body> | ||||||
|  | 
 | ||||||
|  |   <h1>sclient</h1>  | ||||||
|  |   <p>ssl unwrapper for Windows, Mac, and Linux</p> | ||||||
|  |   <p>a poor man's alternative to <code>openssl s_client</code>, <code>stunnel</code>, <code>socat</code> | ||||||
|  |     for the simple use case of connecting a client application that doesn't support tls+sni | ||||||
|  |     through a secure connection, https proxy, or sni multiplexer | ||||||
|  |     (think <code>telnet</code>, <code>netcat</code>, <code>ssh</code>, <code>openvpn</code>, etc).</p> | ||||||
|  | 
 | ||||||
|  |   <h2>Usage</h2> | ||||||
|  |   <pre><code>$ sclient [flags] <remote> <local></code></pre> | ||||||
|  |   <pre><code>$ sclient example.com:443 localhost:3000</code></pre> | ||||||
|  |   <h3>Flags</h3> | ||||||
|  |   <ul> | ||||||
|  |     <li><kbd>-k, --insecure</kbd> ignore invalid tls certificates</li> | ||||||
|  |     <li><kbd>--servername <string></kbd> spoof SNI | ||||||
|  |       (to disable use IP as <remote> and do not use this option)</li> | ||||||
|  |   </ul> | ||||||
|  |   <h3>Arguments</h3> | ||||||
|  |   <ul> | ||||||
|  |     <li><kbd><remote></kbd> the servername and port of the tls-enabled server (default port is 443)</li> | ||||||
|  |     <li><kbd><local></kbd> the local address and port to bind to (default bind address is 127.0.0.1 or ::1) | ||||||
|  |       <ul> | ||||||
|  |         <li><code>-</code> may be used to read from stdin (like netcat)</li> | ||||||
|  |         <li>may be omitted when piping (see pipe example below)</li> | ||||||
|  |       </ul> | ||||||
|  |     </li> | ||||||
|  |   </ul> | ||||||
|  | 
 | ||||||
|  |   <h2>Examples</h2> | ||||||
|  |   <h3>SSH</h3> | ||||||
|  |   <pre><code>$ ssh -o ProxyCommand="sclient %h" jon.telebit.io</code></pre> | ||||||
|  | 
 | ||||||
|  |   <p>This is useful to be able to connect to SSH even from behind a corporate packet-inspection firewall. | ||||||
|  |   It can also be used to multiplex and relay multiple ssh connections through a single host. | ||||||
|  |   </p> | ||||||
|  | 
 | ||||||
|  |   <h3>Telnet </h3> | ||||||
|  |   <pre><code>$ sclient example.com:443 localhost:3000 | ||||||
|  | > [listening] example.com:443 <= localhost:3000</code></pre> | ||||||
|  |   <pre><code>$ telnet localhost 3000</code></pre> | ||||||
|  |   <h3>stdin/stdout</h3> | ||||||
|  |   <pre><code>$ sclient whatever.com - | ||||||
|  | > (connected to whatever.com:443 and reading from stdin)</code></pre> | ||||||
|  |   Use just like netcat or telnet. A manual HTTP request, for example: | ||||||
|  |   <pre><code>> GET / HTTP/1.1 | ||||||
|  | > Host: whatever.com | ||||||
|  | > Connection: close | ||||||
|  | >   | ||||||
|  | </code></pre> | ||||||
|  | 
 | ||||||
|  |   <h3>pipe</h3> | ||||||
|  |   <pre><code>$ printf "GET / HTTP/1.1\r\nHost: telebit.cloud\r\n\r\n" | sclient telebit.cloud</code></pre> | ||||||
|  | 
 | ||||||
|  |   <h2>Downloads (standalone) <small>v1.2</small></h2> | ||||||
|  |   <ul> | ||||||
|  |     <li>Windows 7/8/10 | ||||||
|  |       <a href="dist/windows/amd64/sclient.exe">Download</a> | ||||||
|  |       | <a href="dist/windows/386/sclient.exe">(x86)</a> | ||||||
|  |     </li> | ||||||
|  |     <li>macOS / OS X / Darwin | ||||||
|  |       <a href="dist/darwin/amd64/sclient">Download</a> | ||||||
|  |     </li> | ||||||
|  |     <li>Linux Ubuntu/Arch/etc | ||||||
|  |       <a href="dist/linux/amd64/sclient">Download</a> | ||||||
|  |       | <a href="dist/linux/386/sclient">(386)</a> | ||||||
|  |     </li> | ||||||
|  |     <li>Raspberry Pi | ||||||
|  |       <a href="dist/linux/armv7/sclient">Download</a> | ||||||
|  |       | <a href="dist/linux/armv5/sclient">(Pi Zero)</a> | ||||||
|  |     </li> | ||||||
|  |     <li>Source Code | ||||||
|  |       <a href="https://git.coolaj86.com/coolaj86/sclient.go">Go (golang)</a> | ||||||
|  |       | <a href="https://git.coolaj86.com/coolaj86/sclient.js">node.js</a> | ||||||
|  |     </li> | ||||||
|  |   </ul> | ||||||
|  | 
 | ||||||
|  |   <h2>Source Code</h2> | ||||||
|  |   <p><code>sclient</code> is maintained simultaneously in go and node.js</p> | ||||||
|  |   <ul> | ||||||
|  |     <li>Go | ||||||
|  |       <ul> | ||||||
|  |         <li><a href="https://git.coolaj86.com/coolaj86/sclient.go">git</a></li> | ||||||
|  |         <li><a href="https://git.coolaj86.com/coolaj86/sclient.go/archive/master.zip">zip</a></li> | ||||||
|  |       </ul> | ||||||
|  |     </li> | ||||||
|  |     <br> | ||||||
|  |     <li>node.js | ||||||
|  |     <ul> | ||||||
|  |         <li><a href="https://git.coolaj86.com/coolaj86/sclient.js">git</a></li> | ||||||
|  |         <li><a href="https://git.coolaj86.com/coolaj86/sclient.js/archive/master.zip">zip</a></li> | ||||||
|  |         <li><kbd>npm install -g sclient</kbd></li> | ||||||
|  |     </ul> | ||||||
|  | 
 | ||||||
|  |   </body> | ||||||
|  | </html> | ||||||
							
								
								
									
										16
									
								
								lib/extensions/admin/setup/index.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,16 @@ | |||||||
|  | <!DOCTYPE html> | ||||||
|  | <html> | ||||||
|  |   <head> | ||||||
|  |     <title>Telebit Relay</title> | ||||||
|  |   </head> | ||||||
|  |   <body> | ||||||
|  |     [TODO: Setup Interface] | ||||||
|  |     <br> | ||||||
|  |     <ul> | ||||||
|  |       <li>Admin Server Name</li> | ||||||
|  |       <li>Administrator Email</li> | ||||||
|  |       <li>SSL ToS Agree</li> | ||||||
|  |       <li>Community Member</li> | ||||||
|  |     </ul> | ||||||
|  |   </body> | ||||||
|  | </html> | ||||||
| @ -0,0 +1,4 @@ | |||||||
|  | <?xml version="1.0" encoding="UTF-8" standalone="no"?> | ||||||
|  | <svg width="170px" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 170 170" version="1.1" height="170px"> | ||||||
|  |  <path d="m150.37 130.25c-2.45 5.66-5.35 10.87-8.71 15.66-4.58 6.53-8.33 11.05-11.22 13.56-4.48 4.12-9.28 6.23-14.42 6.35-3.69 0-8.14-1.05-13.32-3.18-5.197-2.12-9.973-3.17-14.34-3.17-4.58 0-9.492 1.05-14.746 3.17-5.262 2.13-9.501 3.24-12.742 3.35-4.929 0.21-9.842-1.96-14.746-6.52-3.13-2.73-7.045-7.41-11.735-14.04-5.032-7.08-9.169-15.29-12.41-24.65-3.471-10.11-5.211-19.9-5.211-29.378 0-10.857 2.346-20.221 7.045-28.068 3.693-6.303 8.606-11.275 14.755-14.925s12.793-5.51 19.948-5.629c3.915 0 9.049 1.211 15.429 3.591 6.362 2.388 10.447 3.599 12.238 3.599 1.339 0 5.877-1.416 13.57-4.239 7.275-2.618 13.415-3.702 18.445-3.275 13.63 1.1 23.87 6.473 30.68 16.153-12.19 7.386-18.22 17.731-18.1 31.002 0.11 10.337 3.86 18.939 11.23 25.769 3.34 3.17 7.07 5.62 11.22 7.36-0.9 2.61-1.85 5.11-2.86 7.51zm-31.26-123.01c0 8.1021-2.96 15.667-8.86 22.669-7.12 8.324-15.732 13.134-25.071 12.375-0.119-0.972-0.188-1.995-0.188-3.07 0-7.778 3.386-16.102 9.399-22.908 3.002-3.446 6.82-6.3113 11.45-8.597 4.62-2.2516 8.99-3.4968 13.1-3.71 0.12 1.0831 0.17 2.1663 0.17 3.2409z"/> | ||||||
|  | </svg> | ||||||
| After Width: | Height: | Size: 1.2 KiB | 
							
								
								
									
										4
									
								
								lib/extensions/admin/static-site-assets/images/cloud.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,4 @@ | |||||||
|  | <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"> | ||||||
|  |     <path d="M0 0h24v24H0z" fill="none"/> | ||||||
|  |     <path d="M19.35 10.04C18.67 6.59 15.64 4 12 4 9.11 4 6.6 5.64 5.35 8.04 2.34 8.36 0 10.91 0 14c0 3.31 2.69 6 6 6h13c2.76 0 5-2.24 5-5 0-2.64-2.05-4.78-4.65-4.96z"/> | ||||||
|  | </svg> | ||||||
| After Width: | Height: | Size: 302 B | 
| @ -0,0 +1,4 @@ | |||||||
|  | <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"> | ||||||
|  |     <path d="M0 0h24v24H0z" fill="none"/> | ||||||
|  |     <path d="M20 18c1.1 0 1.99-.9 1.99-2L22 6c0-1.1-.9-2-2-2H4c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2H0v2h24v-2h-4zM4 6h16v10H4V6z"/> | ||||||
|  | </svg> | ||||||
| After Width: | Height: | Size: 261 B | 
							
								
								
									
										4
									
								
								lib/extensions/admin/static-site-assets/images/done.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,4 @@ | |||||||
|  | <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"> | ||||||
|  |     <path fill="none" d="M0 0h24v24H0z"/> | ||||||
|  |     <path d="M9 16.2L4.8 12l-1.4 1.4L9 19 21 7l-1.4-1.4L9 16.2z"/> | ||||||
|  | </svg> | ||||||
| After Width: | Height: | Size: 200 B | 
| After Width: | Height: | Size: 2.1 KiB | 
							
								
								
									
										
											BIN
										
									
								
								lib/extensions/admin/static-site-assets/images/green-secure.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 954 B | 
| @ -0,0 +1,4 @@ | |||||||
|  | <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"> | ||||||
|  |     <path d="M0 0h24v24H0z" fill="none"/> | ||||||
|  |     <path d="M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zm6.93 6h-2.95c-.32-1.25-.78-2.45-1.38-3.56 1.84.63 3.37 1.91 4.33 3.56zM12 4.04c.83 1.2 1.48 2.53 1.91 3.96h-3.82c.43-1.43 1.08-2.76 1.91-3.96zM4.26 14C4.1 13.36 4 12.69 4 12s.1-1.36.26-2h3.38c-.08.66-.14 1.32-.14 2 0 .68.06 1.34.14 2H4.26zm.82 2h2.95c.32 1.25.78 2.45 1.38 3.56-1.84-.63-3.37-1.9-4.33-3.56zm2.95-8H5.08c.96-1.66 2.49-2.93 4.33-3.56C8.81 5.55 8.35 6.75 8.03 8zM12 19.96c-.83-1.2-1.48-2.53-1.91-3.96h3.82c-.43 1.43-1.08 2.76-1.91 3.96zM14.34 14H9.66c-.09-.66-.16-1.32-.16-2 0-.68.07-1.35.16-2h4.68c.09.65.16 1.32.16 2 0 .68-.07 1.34-.16 2zm.25 5.56c.6-1.11 1.06-2.31 1.38-3.56h2.95c-.96 1.65-2.49 2.93-4.33 3.56zM16.36 14c.08-.66.14-1.32.14-2 0-.68-.06-1.34-.14-2h3.38c.16.64.26 1.31.26 2s-.1 1.36-.26 2h-3.38z"/> | ||||||
|  | </svg> | ||||||
| After Width: | Height: | Size: 959 B | 
| After Width: | Height: | Size: 5.6 KiB | 
							
								
								
									
										9
									
								
								lib/extensions/admin/static-site-assets/images/linux.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,9 @@ | |||||||
|  | <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"> | ||||||
|  |     <g fill="none" fill-rule="evenodd"> | ||||||
|  |         <path d="M0 0h24v24H0z"/> | ||||||
|  |         <g transform="translate(1 2)"> | ||||||
|  |             <rect width="22" height="20" fill="#000" rx="1"/> | ||||||
|  |             <path fill="#FFF" d="M6.495 3.942v1.125l-4.12 1.566V5.551l2.882-1.047-2.882-1.056V2.375l4.12 1.567zm.32 3.592h4.327v.779H6.814v-.78z"/> | ||||||
|  |         </g> | ||||||
|  |     </g> | ||||||
|  | </svg> | ||||||
| After Width: | Height: | Size: 436 B | 
							
								
								
									
										4
									
								
								lib/extensions/admin/static-site-assets/images/lock.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,4 @@ | |||||||
|  | <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"> | ||||||
|  |     <path d="M0 0h24v24H0z" fill="none"/> | ||||||
|  |     <path d="M18 8h-1V6c0-2.76-2.24-5-5-5S7 3.24 7 6v2H6c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V10c0-1.1-.9-2-2-2zm-6 9c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2zm3.1-9H8.9V6c0-1.71 1.39-3.1 3.1-3.1 1.71 0 3.1 1.39 3.1 3.1v2z"/> | ||||||
|  | </svg> | ||||||
| After Width: | Height: | Size: 375 B | 
							
								
								
									
										6
									
								
								lib/extensions/admin/static-site-assets/images/mac.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,6 @@ | |||||||
|  | <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"> | ||||||
|  |     <g fill="none" fill-rule="evenodd"> | ||||||
|  |         <path d="M0 0h24v24H0z"/> | ||||||
|  |         <path fill="#1A1A1A" d="M21.41 8.22c-1.667 1.179-2.5 2.597-2.5 4.254 0 1.986 1.03 3.509 3.09 4.57-.553 1.6-1.354 2.993-2.402 4.178C18.549 22.407 17.592 23 16.726 23c-.408 0-.965-.135-1.67-.404l-.34-.13c-.69-.27-1.302-.404-1.834-.404-.502 0-1.052.105-1.649.316l-.426.153-.535.218c-.422.167-.848.251-1.277.251-1.012 0-2.13-.833-3.352-2.498C3.88 18.117 3 15.518 3 12.704c0-2 .55-3.61 1.649-4.832 1.1-1.222 2.555-1.833 4.368-1.833.677 0 1.31.124 1.9.371l.404.164.426.174c.378.16.684.24.917.24.298 0 .63-.069.993-.207l.557-.218.415-.153c.663-.24 1.394-.36 2.195-.36 1.9 0 3.429.724 4.586 2.17zM16.911 1c.022.255.033.45.033.589 0 1.258-.458 2.361-1.376 3.31-.917.95-1.983 1.424-3.199 1.424a5.474 5.474 0 0 1-.055-.611c0-1.069.426-2.072 1.278-3.01.852-.938 1.838-1.487 2.96-1.647.08-.015.2-.033.36-.055z"/> | ||||||
|  |     </g> | ||||||
|  | </svg> | ||||||
| After Width: | Height: | Size: 982 B | 
| @ -0,0 +1,8 @@ | |||||||
|  | <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"> | ||||||
|  |     <g fill="none" fill-rule="evenodd"> | ||||||
|  |         <path d="M0 0h24v24H0z"/> | ||||||
|  |         <g fill="#000" fill-rule="nonzero"> | ||||||
|  |             <path d="M6.502 3.124c2.152 1.11 3.403 2.007 4.088 2.771-.35 1.407-2.182 1.471-2.851 1.432.137-.064.251-.14.292-.258-.168-.12-.764-.012-1.18-.246.16-.033.235-.065.31-.183-.394-.125-.817-.233-1.066-.441.134.002.26.03.435-.092-.352-.19-.727-.34-1.019-.63.182-.004.378-.001.435-.068a3.924 3.924 0 0 1-.819-.665c.255.031.363.005.424-.04-.243-.25-.552-.46-.698-.767.189.065.362.09.486-.006-.082-.186-.437-.296-.641-.733.199.02.41.044.452 0-.092-.376-.25-.588-.406-.807.426-.006 1.071.002 1.042-.034l-.263-.27c.416-.112.841.018 1.15.115.14-.11-.002-.248-.171-.39.353.048.673.129.962.241.154-.14-.1-.279-.224-.418.547.104.778.25 1.008.395.167-.16.01-.296-.103-.435.412.153.624.35.848.544.075-.102.192-.177.051-.424.293.169.513.367.676.59.18-.115.108-.273.109-.418.304.247.497.51.733.767.047-.034.089-.152.126-.338.725.704 1.75 2.476.263 3.179-1.264-1.044-2.775-1.802-4.45-2.371zM17.921 3.124c-2.152 1.11-3.403 2.007-4.089 2.771.351 1.407 2.183 1.471 2.852 1.432-.137-.064-.251-.14-.292-.258.168-.12.764-.012 1.18-.246-.16-.033-.235-.065-.31-.183.393-.125.817-.233 1.066-.441-.135.002-.26.03-.436-.092.352-.19.728-.34 1.02-.63-.182-.004-.379-.001-.436-.068.323-.2.594-.422.82-.665-.255.031-.363.005-.424-.04.243-.25.551-.46.698-.767-.189.065-.362.09-.487-.006.083-.186.438-.296.642-.733-.2.02-.41.044-.453 0 .093-.376.251-.588.407-.807-.426-.006-1.071.002-1.042-.034l.263-.27c-.416-.112-.842.018-1.15.115-.14-.11.002-.248.171-.39a4.182 4.182 0 0 0-.962.241c-.154-.14.1-.279.223-.418-.546.104-.778.25-1.008.395-.166-.16-.01-.296.103-.435-.411.153-.624.35-.847.544-.076-.102-.192-.177-.052-.424a2.149 2.149 0 0 0-.675.59c-.181-.115-.108-.273-.109-.418-.304.247-.497.51-.733.767-.048-.034-.09-.152-.126-.338-.725.704-1.75 2.476-.263 3.179 1.264-1.044 2.775-1.802 4.449-2.371zM14.818 17.45c0 1.313-1.154 2.377-2.578 2.377s-2.578-1.064-2.578-2.377c0-1.313 1.154-2.377 2.578-2.377s2.578 1.064 2.578 2.377zM10.153 10.363c1.204.426 1.773 1.922 1.27 3.343-.501 1.42-1.884 2.227-3.088 1.801-1.204-.426-1.773-1.922-1.271-3.343.502-1.42 1.885-2.227 3.09-1.801zM14.226 10.236c-1.204.426-1.773 1.922-1.27 3.343.501 1.42 1.884 2.227 3.088 1.801 1.204-.425 1.773-1.922 1.271-3.342-.502-1.42-1.885-2.227-3.089-1.802zM5.41 11.803c1.153-.309.389 4.771-.55 4.355-1.032-.83-1.364-3.262.55-4.355zM18.737 11.74c-1.154-.309-.39 4.771.549 4.354 1.032-.83 1.364-3.261-.55-4.354zM14.818 7.957c1.99-.336 3.647.847 3.58 3.005-.066.827-4.313-2.882-3.58-3.005zM9.32 7.894c-1.99-.336-3.646.846-3.58 3.004.066.828 4.313-2.881 3.58-3.004zM12.178 7.39c-1.187-.03-2.327.882-2.33 1.411-.003.643.939 1.302 2.339 1.318 1.429.01 2.34-.527 2.345-1.19.006-.752-1.3-1.55-2.354-1.539zM12.251 20.578c1.036-.045 2.425.333 2.428.836.017.488-1.26 1.59-2.497 1.569-1.28.055-2.536-1.05-2.52-1.432-.019-.56 1.56-.999 2.589-.973zM8.426 17.6c.737.888 1.073 2.449.458 2.909-.582.351-1.996.207-3-1.237-.678-1.211-.591-2.444-.115-2.806.711-.433 1.81.152 2.657 1.134zM15.929 17.318c-.798.935-1.242 2.64-.66 3.188.556.427 2.05.367 3.153-1.164.801-1.028.533-2.746.075-3.201-.68-.526-1.656.147-2.568 1.177z"/> | ||||||
|  |         </g> | ||||||
|  |     </g> | ||||||
|  | </svg> | ||||||
| After Width: | Height: | Size: 3.2 KiB | 
| @ -0,0 +1 @@ | |||||||
|  | <svg aria-labelledby="simpleicons-raspberrypi-icon" role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title id="simpleicons-raspberrypi-icon">Raspberry Pi icon</title><path d="M16.111 17.338c-.857.989-1.334 2.79-.709 3.371.596.449 2.201.391 3.385-1.23.86-1.08.569-2.893.081-3.372-.73-.555-1.778.164-2.757 1.243v-.012zm-8.057.3c-.908-1.04-2.088-1.658-2.851-1.199-.51.382-.605 1.685.123 2.967 1.078 1.524 2.596 1.679 3.221 1.307.659-.488.3-2.137-.493-3.075zm4.105 3.145c-1.103-.026-2.798.439-2.776 1.032-.018.403 1.331 1.572 2.705 1.513 1.326.03 2.699-1.139 2.682-1.649-.004-.523-1.498-.927-2.607-.884l-.004-.012zm-.075-13.944c-1.275-.032-2.502.933-2.502 1.493-.004.68 1.008 1.376 2.51 1.394 1.543.01 2.518-.559 2.532-1.26.016-.794-1.394-1.639-2.518-1.627h-.022zm-3.071.532c-2.135-.345-3.913.9-3.842 3.192.07.884 4.63-3.041 3.843-3.177l-.001-.015zm9.749 3.251c.071-2.277-1.709-3.521-3.844-3.176-.787.135 3.772 4.061 3.844 3.176zm.364.824c-1.239-.329-.42 5.049.588 4.615 1.109-.869 1.466-3.446-.588-4.6v-.015zM4.228 16.121c1.007.45 1.827-4.929.589-4.6-2.053 1.153-1.698 3.73-.589 4.615v-.015zm9.415-5.948c-1.146.75-1.354 2.428-.461 3.746.891 1.318 2.543 1.813 3.691 1.078 1.146-.733 1.353-2.412.462-3.746-.892-1.333-2.545-1.813-3.692-1.063v-.015zm-3.096.135c-1.146-.734-2.799-.254-3.689 1.064-.892 1.334-.686 3.012.461 3.761s2.799.269 3.691-1.064c.885-1.318.675-2.997-.465-3.745l.002-.016zm4.369 7.162c-.009-1.393-1.252-2.518-2.781-2.502-1.527.016-2.761 1.139-2.754 2.532v.029c.01 1.394 1.254 2.517 2.783 2.502 1.527 0 2.756-1.138 2.742-2.517v-.029l.01-.015zm3.209-15.133c-2.307 1.184-3.652 2.128-4.389 2.938.377 1.498 2.344 1.558 3.063 1.512-.147-.06-.271-.149-.315-.269.18-.12.821-.016 1.268-.255-.171-.03-.252-.061-.329-.195.419-.135.875-.24 1.141-.465-.143 0-.278.03-.467-.09.377-.194.778-.359 1.095-.658-.196 0-.406 0-.466-.075.346-.21.635-.435.877-.704-.272.045-.39.016-.454-.03.261-.255.593-.479.749-.81-.203.076-.391.09-.522 0 .091-.194.47-.314.69-.779-.215.03-.441.046-.486 0 .098-.389.269-.613.435-.854-.457 0-1.15 0-1.117-.029l.283-.285c-.448-.12-.904.015-1.236.12-.149-.105 0-.255.185-.405-.39.061-.733.135-1.034.256-.164-.15.105-.285.24-.436-.599.12-.839.27-1.094.42-.18-.165-.015-.314.104-.449-.449.164-.674.374-.914.568-.09-.104-.209-.179-.06-.449-.314.18-.554.39-.734.629-.194-.134-.119-.299-.119-.449-.33.27-.54.54-.794.811-.061-.031-.105-.15-.135-.346-.779.75-1.889 2.623-.285 3.356 1.349-1.094 2.981-1.903 4.779-2.503l.041-.075zm-12.259 0c1.798.6 3.419 1.408 4.777 2.518 1.596-.75.493-2.623-.282-3.356-.041.194-.085.329-.135.359-.255-.27-.462-.54-.788-.81 0 .15.077.33-.117.45-.175-.239-.41-.45-.725-.63.149.256.025.33-.056.449-.24-.225-.465-.434-.899-.599.12.149.3.3.12.465-.239-.149-.494-.3-1.078-.42.135.149.404.3.238.45-.315-.122-.66-.212-1.035-.258.181.15.342.289.192.405-.345-.12-.806-.255-1.255-.135l.284.284c.03.037-.659.03-1.121.035.165.225.337.449.435.854-.045.045-.27.016-.483 0 .225.449.599.57.688.765-.135.096-.314.075-.523 0 .164.314.494.539.748.81-.074.044-.18.074-.464.037.239.26.524.494.869.704-.06.07-.271.069-.479.075.314.304.719.464 1.094.663-.195.136-.33.105-.465.105.255.225.72.329 1.139.464-.09.135-.164.165-.344.195.449.254 1.078.135 1.258.27-.045.119-.164.209-.314.27.719.045 2.697-.015 3.072-1.514-.736-.807-2.084-1.752-4.391-2.921l.04.016zM7.6.103c.236-.007.436.135.652.201.529-.17.65.063.91.159.577-.12.752.141 1.029.419l.322-.009c.869.507 1.305 1.536 1.457 2.065.152-.529.584-1.559 1.457-2.065l.321.007c.277-.283.453-.539 1.029-.418.261-.105.38-.33.911-.166.33-.104.62-.375 1.057-.045.368-.149.726-.195 1.045.09.495-.06.653.061.774.21.108 0 .809-.104 1.132.36.81-.09 1.064.464.774.988.165.255.337.494-.05.975.15.269.062.553-.27.913.091.374-.074.63-.374.839.06.51-.48.81-.629.914-.061.3-.181.584-.795.734-.089.449-.464.523-.824.614 1.185.675 2.188 1.558 2.188 3.731l.181.299c1.349.809 2.562 3.402.674 5.514-.119.659-.329 1.124-.511 1.648-.269 2.113-2.082 3.101-2.561 3.221-.689.525-1.438 1.02-2.442 1.363-.942.961-1.976 1.336-2.994 1.336h-.092c-1.033 0-2.063-.375-3.012-1.335-1.007-.344-1.754-.838-2.447-1.363-.479-.12-2.283-1.107-2.562-3.221-.187-.524-.394-1.004-.518-1.662-1.894-2.113-.681-4.705.666-5.515l.172-.3c0-2.172 1.005-3.057 2.188-3.73-.359-.09-.72-.165-.823-.615-.615-.15-.735-.434-.795-.734-.15-.105-.689-.404-.629-.928-.3-.211-.465-.465-.375-.854-.314-.346-.404-.645-.27-.915-.39-.479-.209-.733-.045-.974C3.236 1.329 3.491.76 4.3.85 4.614.385 5.32.491 5.423.491c.121-.15.285-.285.779-.225.314-.285.675-.24 1.049-.102.151-.12.286-.164.406-.164L7.6.103z"/></svg> | ||||||
| After Width: | Height: | Size: 4.4 KiB | 
| @ -0,0 +1,18 @@ | |||||||
|  | <?xml version="1.0" encoding="UTF-8" standalone="no"?> | ||||||
|  | <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> | ||||||
|  | <!-- Created with Inkscape (http://www.inkscape.org/) by Marsupilami --> | ||||||
|  | <svg | ||||||
|  |    xmlns:svg="http://www.w3.org/2000/svg" | ||||||
|  |    xmlns="http://www.w3.org/2000/svg" | ||||||
|  |    version="1.1" | ||||||
|  |    width="766" | ||||||
|  |    height="768" | ||||||
|  |    viewBox="-2.61977004 -2.61977004 92.56520808 92.83416708" | ||||||
|  |    id="svg8375"> | ||||||
|  |   <defs | ||||||
|  |      id="defs8377" /> | ||||||
|  |   <path | ||||||
|  |      d="M 0,12.40183 35.68737,7.5416 35.70297,41.96435 0.03321,42.16748 z m 35.67037,33.52906 0.0277,34.45332 -35.66989,-4.9041 -0.002,-29.77972 z M 39.99644,6.90595 87.31462,0 l 0,41.527 -47.31818,0.37565 z M 87.32567,46.25471 87.31457,87.59463 39.9964,80.91625 39.9301,46.17767 z" | ||||||
|  |      id="path13" /> | ||||||
|  | </svg> | ||||||
|  | <!-- version: 20110311, original size: 87.325668 87.594627, border: 3% --> | ||||||
| After Width: | Height: | Size: 861 B | 
| @ -0,0 +1,6 @@ | |||||||
|  | <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"> | ||||||
|  |     <g fill="none" fill-rule="evenodd"> | ||||||
|  |         <path d="M0 0h24v24H0z"/> | ||||||
|  |         <path fill="#000" fill-rule="nonzero" d="M2 4.819l8.11-1.105.004 7.823-8.107.047L2 4.819zm8.107 7.62l.006 7.83-8.107-1.114v-6.769l8.1.053zm.983-8.87L21.844 2v9.438l-10.754.085V3.57zm10.757 8.944l-.003 9.395L11.09 20.39l-.015-7.895 10.772.018z"/> | ||||||
|  |     </g> | ||||||
|  | </svg> | ||||||
| After Width: | Height: | Size: 428 B | 
| @ -0,0 +1,149 @@ | |||||||
|  | (function(){ | ||||||
|  | 'use strict'; | ||||||
|  | 
 | ||||||
|  | function validateFormData(data) { | ||||||
|  |   var errors = {} | ||||||
|  |   if(!data.address) { | ||||||
|  |     errors.email = "Please enter an email address."; | ||||||
|  |   } else if(data.address.length > 244) { | ||||||
|  |     errors.email = "Email is too long.<br>" + | ||||||
|  |       "If your email address is really this long, we apologize. <br>" + | ||||||
|  |       "Please email us directly (hello@ppl.family) so we can adjust our form."; | ||||||
|  |   } else if(!/.+@.+\..+/.test(data.address)) { | ||||||
|  |     errors.email = "Please enter a valid email address."; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   if(data.comment && data.comment.length > 102400) { | ||||||
|  |     errors.name = "Name is too long. <br>Please use a shorter version of your name."; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   if(Object.keys(errors).length) { | ||||||
|  |     return errors; | ||||||
|  |   } | ||||||
|  |   return false; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function enableForm(form) { | ||||||
|  |   var elements = form.elements; | ||||||
|  | 
 | ||||||
|  |   for(var i = 0; i < elements.length; ++i) { | ||||||
|  |     elements[i].removeAttribute("disabled"); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function disableForm(form) { | ||||||
|  |   var elements = form.elements; | ||||||
|  | 
 | ||||||
|  |   for(var i = 0; i < elements.length; ++i) { | ||||||
|  |     elements[i].setAttribute("disabled", ""); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function enableEmailForms() { | ||||||
|  |   enableForm(document.querySelector(".js-inline-email-form")); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function disableEmailForms() { | ||||||
|  |   disableForm(document.querySelector(".js-inline-email-form")); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function displaySuccess(form) { | ||||||
|  |   var successEle = form.querySelector(".success-message"); | ||||||
|  |   if(successEle) { | ||||||
|  |     successEle.classList.remove("js-inactive"); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | function hideSuccess(form){ | ||||||
|  |   var successEle = form.querySelector(".success-message"); | ||||||
|  |   if(successEle) { | ||||||
|  |     successEle.classList.add("js-inactive"); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function displayErrors(form, errors) { | ||||||
|  |   errors = errors || {}; | ||||||
|  | 
 | ||||||
|  |   form.querySelectorAll(".input-error").forEach(function(ele) { | ||||||
|  |     ele.classList.add("js-inactive"); | ||||||
|  |   }); | ||||||
|  |   form.querySelector(".form-error").classList.add("js-inactive"); | ||||||
|  | 
 | ||||||
|  |   Object.keys(errors).forEach(function(key) { | ||||||
|  |     var errorEle; | ||||||
|  |     if(key === "_form" && errors[key]) { | ||||||
|  |       errorEle = form.querySelector(".form-error"); | ||||||
|  |     } else if(errors[key]) { | ||||||
|  |       var query = "." + key + ".input-error"; | ||||||
|  |       errorEle = form.querySelector(query); | ||||||
|  |     } | ||||||
|  |     if(!errorEle) return; | ||||||
|  | 
 | ||||||
|  |     errorEle.innerHTML = errors[key]; | ||||||
|  |     errorEle.classList.remove("js-inactive"); | ||||||
|  |   }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function submitFormData(form) { | ||||||
|  |   hideSuccess(form); | ||||||
|  |   var data = new FormData(form); | ||||||
|  | 
 | ||||||
|  |   var msg = { | ||||||
|  |     address: data.get("email") | ||||||
|  |   , comment: 'telebit: ' + (data.get("name") || '') | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   var errors = validateFormData(msg); | ||||||
|  |   displayErrors(form, errors); | ||||||
|  |   if(errors) { | ||||||
|  |     console.warn("Form validation failed: ", errors); | ||||||
|  |     return Promise.resolve(); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |   disableEmailForms(); | ||||||
|  | 
 | ||||||
|  |   return window.fetch('https://api.ppl.family/api/ppl.family/public/list', { | ||||||
|  |     method: 'POST' | ||||||
|  |   , cors: true | ||||||
|  |   , headers: new Headers({ 'Content-Type': 'application/json' }) | ||||||
|  |   , body: JSON.stringify(msg) | ||||||
|  |   }).then(function (resp) { | ||||||
|  |     return resp.json(); | ||||||
|  |   }).then(function (data) { | ||||||
|  |     enableEmailForms(); | ||||||
|  |     if (data.error) { | ||||||
|  |       console.error("Error submitting form: ", data.error); | ||||||
|  |       err = { | ||||||
|  |         "_form": "Couldn't save email. <br>Try again or email hello@ppl.family directly." | ||||||
|  |       }; | ||||||
|  |       return displayErrors(form, errors); | ||||||
|  |     } | ||||||
|  |     displaySuccess(form); | ||||||
|  |     console.log("Successfully subscribed!"); | ||||||
|  | 
 | ||||||
|  |     form.reset(); | ||||||
|  | 
 | ||||||
|  |   }, function (err) { | ||||||
|  |     enableEmailForms(); | ||||||
|  |     console.error("Error sending form data to server: ", err); | ||||||
|  |     displayErrors(form, { | ||||||
|  |       "_form": "Unable to send the info to the server.<br>" + | ||||||
|  |       "Please try again or email hello@ppl.family directly." | ||||||
|  |     }); | ||||||
|  |   }); | ||||||
|  | } | ||||||
|  | document.body.addEventListener('submit', function (ev) { | ||||||
|  |   console.log("Caught event!"); | ||||||
|  |   function eleMatchesString(ele, selector) { | ||||||
|  |      return ele.matches ? ele.matches(selector) : ele.msMatchesSelector(selector); | ||||||
|  |   } | ||||||
|  |   var form = ev.target; | ||||||
|  |   if (!eleMatchesString(form, '.js-inline-email-form')) { | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |   ev.preventDefault(); | ||||||
|  |   ev.stopPropagation(); | ||||||
|  |   submitFormData(form); | ||||||
|  |   return; | ||||||
|  | }); | ||||||
|  | })(); | ||||||
							
								
								
									
										47
									
								
								lib/extensions/admin/static-site-assets/styles/1200.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,47 @@ | |||||||
|  | .quickstart-step-text { | ||||||
|  |   align-items: center; | ||||||
|  |   justify-content: center; | ||||||
|  |   margin: 0 0 1.5em; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .quickstart-step { | ||||||
|  |     flex-direction: column; | ||||||
|  |     justify-content: center; | ||||||
|  |     align-items: center; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .quickstart-terminal { | ||||||
|  |     flex: 0 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .container.quickstart-container { | ||||||
|  |     padding: 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @media (max-width: 900px) { | ||||||
|  | 
 | ||||||
|  |   .donate-section p { | ||||||
|  |       margin: 1.77777778em 10%; | ||||||
|  |       font-size: 1.6em; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   .quickstart-terminal { | ||||||
|  |       width: 100%; | ||||||
|  |       box-sizing: border-box; | ||||||
|  |       font-size: .95em; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |   h2 { | ||||||
|  |       font-size: 1.9em; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   .quickstart-step-name { | ||||||
|  |       font-size: 1.2em; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   h3 { | ||||||
|  |       font-size: 1.5em; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
							
								
								
									
										433
									
								
								lib/extensions/admin/static-site-assets/styles/main.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,433 @@ | |||||||
|  | body{ | ||||||
|  |   font-family: Source Sans Pro, sans-serrif; | ||||||
|  |   font-size: 17px; | ||||||
|  |   line-height: 1.3333; | ||||||
|  |   margin: 0; | ||||||
|  |   -webkit-text-size-adjust: none; | ||||||
|  |   text-size-adjust: none; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | a { | ||||||
|  |   text-decoration: none; | ||||||
|  |   color: inherit; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | header { | ||||||
|  |   background-color: #1a1a1a; | ||||||
|  |   color: white; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .hero { | ||||||
|  |   background-color: #1a1a1a; | ||||||
|  |   color: white; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | a:hover, u:hover { | ||||||
|  |   color: #ddd; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .mailing-list-form ul, footer ul, header ul { | ||||||
|  |   list-style-type: none; | ||||||
|  |   padding: 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .container { | ||||||
|  |   width: 788px; | ||||||
|  |   margin: auto; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | header > .container { | ||||||
|  |   display: flex; | ||||||
|  |   justify-content: space-between; | ||||||
|  |   align-items: center; | ||||||
|  |   padding: 1.77778em 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | footer .logo, header .logo { | ||||||
|  |   font-size: 1.55556em; | ||||||
|  |   font-weight: 900; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | header .navigation-menu { | ||||||
|  |   display: flex; | ||||||
|  |   margin: initial; | ||||||
|  |   align-items: center; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | header .navigation-menu li { | ||||||
|  |   margin-left: 1.77778em; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .hero .container { | ||||||
|  |   padding-top: 0.44444em; | ||||||
|  |   display: flex; | ||||||
|  |   flex-direction: column; | ||||||
|  |   align-items: center; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | h1 { | ||||||
|  |   font-size: 2.22222em; | ||||||
|  |   margin: initial; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .spiel { | ||||||
|  |   max-width: 60%; | ||||||
|  |   text-align: center; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .link-button { | ||||||
|  |   border: solid 1px white; | ||||||
|  |   padding: 0.444444em 0.8888889em; | ||||||
|  |   border-radius: 0.1111111em; | ||||||
|  |   display: inline-block; | ||||||
|  |   background-color: #1a1a1a; | ||||||
|  |   color: #ffffff; | ||||||
|  |   font-size: 1em; | ||||||
|  |   font-family: inherit; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .hero-download { | ||||||
|  |   margin: 1.33333333em; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | a.link-button.wide { | ||||||
|  |   padding: 0.44444em 1.7777776em; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .demo-container { | ||||||
|  |   margin-top: 1em; | ||||||
|  |   position: relative; | ||||||
|  |   height: 236px; | ||||||
|  |   width: 644px; | ||||||
|  |   overflow: hidden; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .demo-browser { | ||||||
|  |   position: absolute; | ||||||
|  |   bottom: 0; | ||||||
|  |   right: 0; | ||||||
|  |   width: 544px; | ||||||
|  |   height: 236px; | ||||||
|  |   background-color: #ffffff; | ||||||
|  |   border-radius: 4px 4px 0 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .demo-browser-buttons > div { | ||||||
|  |   width: 11px; | ||||||
|  |   height: 11px; | ||||||
|  |   border: solid 1px #a6a6a6; | ||||||
|  |   border-radius: 6px; | ||||||
|  |   display: inline-block; | ||||||
|  |   margin-left: 8px; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .demo-browser-header { | ||||||
|  |   background-color: #ededed; | ||||||
|  |   display: flex; | ||||||
|  |   align-items: center; | ||||||
|  |   border-radius: 4px 4px 0 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .demo-browser-buttons { | ||||||
|  |   margin: 4px; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .demo-browser-address-bar { | ||||||
|  |   color: #1a1a1a; | ||||||
|  |   border:  solid 1px #d9d9d9; | ||||||
|  |   border-radius: 2px; | ||||||
|  |   background-color: #ffffff; | ||||||
|  |   flex: 1; | ||||||
|  |   margin-left: 20px; | ||||||
|  |   font-size: 0.83333em; | ||||||
|  |   margin-right: 56px; | ||||||
|  |   margin: 8px 56px 8px 20px; | ||||||
|  |   padding: 5px; | ||||||
|  |   display: flex; | ||||||
|  |   justify-content: left; | ||||||
|  |   align-items: center; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .demo-browser-address-bar img { | ||||||
|  |   height: 17px; | ||||||
|  |   float: left; | ||||||
|  |   margin-left: 6px; | ||||||
|  |   margin-right: 8px; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .demo-browser-body { | ||||||
|  |   font-size: 32px; | ||||||
|  |   background-color: #ffffff; | ||||||
|  |   color: #bebebe; | ||||||
|  |   padding: 12px; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .demo-terminal { | ||||||
|  |   /*width: 418px;*/ | ||||||
|  |   width: 512px; | ||||||
|  |   position: absolute; | ||||||
|  |   font-size: 15px; | ||||||
|  |   background-color: #f7f7f7; | ||||||
|  |   font-weight: normal; | ||||||
|  |   color: #1a1a1a; | ||||||
|  |   padding: 24px 24px 20px; | ||||||
|  |   bottom: 0; | ||||||
|  |   font-family: monospace; | ||||||
|  |   line-height: 1.35; | ||||||
|  |   -webkit-box-shadow: -5px 5px 34px 7px rgba(17,17,17,0.18); | ||||||
|  |   -moz-box-shadow: -5px 5px 34px 7px rgba(17,17,17,0.18); | ||||||
|  |   box-shadow: -5px 5px 26px 10px rgba(17, 17, 17, 0.2); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .demo-terminal-line:before { | ||||||
|  |   content: " "; | ||||||
|  | } | ||||||
|  | .demo-terminal-input:before { | ||||||
|  |   content: "$"; | ||||||
|  | } | ||||||
|  | .demo-terminal-output { | ||||||
|  |   padding-left: 1em; | ||||||
|  |   text-indent: -1em; | ||||||
|  | } | ||||||
|  | .demo-terminal-output:before { | ||||||
|  |   content: ">"; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | h2 {text-align: center;font-size: 1.77778em;margin: 0 0 1.25em 0;} | ||||||
|  | 
 | ||||||
|  | body {} | ||||||
|  | 
 | ||||||
|  | .donate-section { | ||||||
|  |   background-color: #f7f7f7; | ||||||
|  |   padding: 1.777778em 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .use-it { | ||||||
|  |   text-align: left; | ||||||
|  |   text-indent: 4.3em; | ||||||
|  |   margin: 1.75em 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .accent-color { | ||||||
|  |   color: rgb(0,0,0,0.4); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .quickstart-step-number { | ||||||
|  |   border-radius: 1em; | ||||||
|  |   height: 1.583333333em; | ||||||
|  |   width: 1.5833333333em; | ||||||
|  |   font-weight: bold; | ||||||
|  |   display: inline-flex; | ||||||
|  |   align-items: center; | ||||||
|  |   justify-content: space-around; | ||||||
|  |   background-color: #f8f8f8; | ||||||
|  |   margin-right: 0.5em; | ||||||
|  |   flex-shrink: 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .quickstart-step { | ||||||
|  |   font-size: 1.33333em; | ||||||
|  |   display: flex; | ||||||
|  |   flex-wrap: wrap; | ||||||
|  |   margin-bottom: 2em; | ||||||
|  |   justify-content: center; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .quickstart-step-text { | ||||||
|  |   min-width: 9.583336em; | ||||||
|  |   margin-right: 1.3333333em; | ||||||
|  |   flex: 1 1; | ||||||
|  |   display: flex; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .quickstart-terminal { | ||||||
|  |   flex: 0 0 36.7em; | ||||||
|  |   background-color: #f7f7f7; | ||||||
|  |   font-family: monospace; | ||||||
|  |   font-size: 0.8em; | ||||||
|  |   width: 36.7em; | ||||||
|  |   line-height: 1.33; | ||||||
|  |   margin: 0; | ||||||
|  |   padding: 0.8em 1em 0.8em 2em; | ||||||
|  | } | ||||||
|  | .quickstart-line:before { | ||||||
|  |   content: "  "; | ||||||
|  | } | ||||||
|  | .quickstart-input:before { | ||||||
|  |   content: "$ "; | ||||||
|  | } | ||||||
|  | .quickstart-output:before { | ||||||
|  |   content: "> "; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | h3 { | ||||||
|  |   text-align: center; | ||||||
|  |   font-size: 1em; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .install-badges { | ||||||
|  |   display: flex; | ||||||
|  |   justify-content: space-between; | ||||||
|  |   margin: auto; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .install-badge { | ||||||
|  |   width: 9.9444444em; | ||||||
|  |   display: flex; | ||||||
|  |   align-items: center; | ||||||
|  |   background-color: #f8f8f8; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .install-badge img, .install-badge svg { | ||||||
|  |   width: 1.3333333em; | ||||||
|  |   margin: 0.888888889em; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .feature.badge { | ||||||
|  |   width: 9.888888889em; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .feature-badge img { | ||||||
|  |   margin: auto; | ||||||
|  |   display: block; | ||||||
|  |   width: 1.333333333em; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .feature-badge { | ||||||
|  |   width: 9.8888889em; | ||||||
|  |   text-align: center; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .feature-badge div { | ||||||
|  |   margin-top: 0.555555556em; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .feature-badges { | ||||||
|  |   display: flex; | ||||||
|  |   justify-content: space-between; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .donate-section p { | ||||||
|  |   margin: 1.7777778em 7.555555556em; | ||||||
|  |   text-align: center; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .feature-list { | ||||||
|  |   margin: 4em 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .donate-section h2 { | ||||||
|  |   margin: 0 0 0.88888889em 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .donate-section a.link-button { | ||||||
|  |   border: none; | ||||||
|  |   width: 11.1111111em; | ||||||
|  |   padding-left: 0; | ||||||
|  |   padding-right: 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .donate-section .container { | ||||||
|  |   text-align: center; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | input { | ||||||
|  |   font-size: 1em; | ||||||
|  |   padding: 0.44444444em; | ||||||
|  |   margin: 0; | ||||||
|  |   font-family: inherit; | ||||||
|  |   border: solid 1px #d9d9d9; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .mailing-list-form .link-button { | ||||||
|  |   border: none; | ||||||
|  |   margin-left: 0.88889em; | ||||||
|  |   width: 9em; | ||||||
|  |   padding-left: 0; | ||||||
|  |   padding-right: 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .mailing-list-form form { | ||||||
|  |   text-align: center; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .mailing-list-form { | ||||||
|  |   background-color: #d9d9d9; | ||||||
|  |   padding: 1.77777778em 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .mailing-list-form li img { | ||||||
|  |   width: 1.111111111em; | ||||||
|  |   margin-right: 0.4444444em; | ||||||
|  |   vertical-align: middle; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | footer .container { | ||||||
|  |   display: flex; | ||||||
|  |   justify-content: space-between; | ||||||
|  |   align-items: center; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | footer { | ||||||
|  |   background-color: #b3b3b3; | ||||||
|  |   color: white; | ||||||
|  |   padding: 1.444444444em 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | footer li { | ||||||
|  |   display: inline; | ||||||
|  |   margin-left: 2.2222em; | ||||||
|  |   font-size: 0.833333333em; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | footer ul { | ||||||
|  |   margin: 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .js-inactive { | ||||||
|  |   display: none; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | s {} | ||||||
|  | 
 | ||||||
|  | .mailing-list-form ul { | ||||||
|  |   display: inline-block; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .mailing-list-form .container { | ||||||
|  |   text-align: center; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .mailing-list-form li { | ||||||
|  |   text-align: left; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | a {} | ||||||
|  | 
 | ||||||
|  | .quickstart-terminal.qickstart-terminal-prompt:before { | ||||||
|  |   content: "$ "; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .install-badge:hover { | ||||||
|  |   cursor: pointer; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .install-badge:hover path { | ||||||
|  |   fill: #ababab; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | input[type="submit"] { | ||||||
|  |   appearance: none; | ||||||
|  |   -webkit-appearance: none; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .quickstart-container { | ||||||
|  |     max-width: 1025px; | ||||||
|  |     width: auto; | ||||||
|  |     padding: 0px 3.111111111em; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .quickstart-step-name { | ||||||
|  |     display: inline-block; | ||||||
|  | } | ||||||
| @ -0,0 +1,84 @@ | |||||||
|  | /* | ||||||
|  | interval: time between spans appearing | ||||||
|  | transitionTime: the time it takes for the span to finish "sliding" in to place. | ||||||
|  | 	transitionTime should be <= interval/2; | ||||||
|  | n: total number of spans sliding in and out | ||||||
|  | 	n should be > 1 | ||||||
|  | nth: the value in "nth-child())" | ||||||
|  | */ | ||||||
|  | .sliding-vertical{ | ||||||
|  | 	display: inline; | ||||||
|  | 	text-indent: 8px; | ||||||
|  | } | ||||||
|  | .sliding-vertical span{ | ||||||
|  | 	animation: topToBottom 15s linear infinite 0s;/* interval * n */ | ||||||
|  | 	-ms-animation: topToBottom 15s linear infinite 0s;/* interval * n */ | ||||||
|  | 	-webkit-animation: topToBottom 15s linear infinite 0s;/* interval * n */ | ||||||
|  | 	opacity: 0; | ||||||
|  | 	overflow: hidden; | ||||||
|  | 	position: absolute; | ||||||
|  | } | ||||||
|  | .sliding-vertical span:nth-child(2){ | ||||||
|  | 	animation-delay: 2.5s;/* (nth - 1) * interval */ | ||||||
|  | 	-ms-animation-delay: 2.5s;/* (nth - 1) * interval */ | ||||||
|  | 	-webkit-animation-delay: 2.5s;/* (nth - 1) * interval */ | ||||||
|  | } | ||||||
|  | .sliding-vertical span:nth-child(3){ | ||||||
|  | 	animation-delay: 5s; | ||||||
|  | 	-ms-animation-delay: 5s; | ||||||
|  | 	-webkit-animation-delay: 5s; | ||||||
|  | } | ||||||
|  | .sliding-vertical span:nth-child(4){ | ||||||
|  | 	animation-delay: 7.5s; | ||||||
|  | 	-ms-animation-delay: 7.5s; | ||||||
|  | 	-webkit-animation-delay: 7.5s; | ||||||
|  | } | ||||||
|  | .sliding-vertical span:nth-child(5){ | ||||||
|  | 	animation-delay: 10s; | ||||||
|  | 	-ms-animation-delay: 10s; | ||||||
|  | 	-webkit-animation-delay: 10s; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .sliding-vertical span:nth-child(6){ | ||||||
|  | 	animation-delay: 12.5s; | ||||||
|  | 	-ms-animation-delay: 12.5s; | ||||||
|  | 	-webkit-animation-delay: 12.5s; | ||||||
|  | } | ||||||
|  | /* | ||||||
|  | .sliding-vertical span:nth-child(7){ | ||||||
|  | 	animation-delay: 15s; | ||||||
|  | 	-ms-animation-delay: 15s; | ||||||
|  | 	-webkit-animation-delay: 15s; | ||||||
|  | } | ||||||
|  | */ | ||||||
|  | /*topToBottom Animation*/ | ||||||
|  | @keyframes topToBottom{ | ||||||
|  | 	0% { opacity: 0; transform: translateY(-50px); } | ||||||
|  | 	6.667% { opacity: 1; transform: translateY(0px); }/* transitionTime/(interval*n) */ | ||||||
|  | 	16.667% { opacity: 1; transform: translateY(0px); } /* 1/n */ | ||||||
|  | 	23.333% { opacity: 0; transform: translateY(50px); } /* (interval + transitionTime)/(n*interval) */ | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @-moz-keyframes topToBottom{ | ||||||
|  | 	0% { opacity: 0; -moz-transform: translateY(-50px); } | ||||||
|  | 	6.667% { opacity: 1; -moz-transform: translateY(0px); } | ||||||
|  | 	16.667% { opacity: 1; -moz-transform: translateY(0px); } | ||||||
|  | 	23.333% { opacity: 0; -moz-transform: translateY(50px); } | ||||||
|  | } | ||||||
|  | @-webkit-keyframes topToBottom{ | ||||||
|  | 	0% { opacity: 0; -webkit-transform: translateY(-50px); } | ||||||
|  | 	6.667% { opacity: 1; -webkit-transform: translateY(0px); } | ||||||
|  | 	16.667% { opacity: 1; -webkit-transform: translateY(0px); } | ||||||
|  | 	23.333% { opacity: 0; -webkit-transform: translateY(50px); } | ||||||
|  | } | ||||||
|  | @-ms-keyframes topToBottom{ | ||||||
|  | 	0% { opacity: 0; -ms-transform: translateY(-50px); } | ||||||
|  | 	6.667% { opacity: 1; -ms-transform: translateY(0px); } | ||||||
|  | 	16.667% { opacity: 1; -ms-transform: translateY(0px); } | ||||||
|  | 	23.333% { opacity: 0; -ms-transform: translateY(50px); } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | .install-for { | ||||||
|  |   margin-top: 3.1111111113em; | ||||||
|  | } | ||||||
							
								
								
									
										47
									
								
								lib/extensions/admin/utahjs2018/index.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,47 @@ | |||||||
|  | <!DOCTYPE html> | ||||||
|  | <html> | ||||||
|  |   <head> | ||||||
|  |     <meta http-equiv="refresh" content="0;url=https://docs.google.com/presentation/d/e/2PACX-1vRQ1YyZcTDKYINLvUe8OdaDn_mIoCc0v8XSK-rgI3-b8EldgqpwbZEGmPn7J9pN1vnEJ1-pOcl_T-QP/pub"> | ||||||
|  |     <style> | ||||||
|  |       body, html { | ||||||
|  |         height: 100%; | ||||||
|  |         margin: 0%; | ||||||
|  |         padding: 0%; | ||||||
|  |         background-color: black; | ||||||
|  |       } | ||||||
|  |       .bg { | ||||||
|  |         background-image: url("http://www.tshirtvortex.net/wp-content/uploads/thenamesrex.jpg"); | ||||||
|  |         height: 100%; | ||||||
|  |         background-position: center; | ||||||
|  |         background-repeat: no-repeat; | ||||||
|  |         background-size: cover; | ||||||
|  |       } | ||||||
|  |       a { | ||||||
|  |         color: white; | ||||||
|  |       } | ||||||
|  |     </style> | ||||||
|  |   </head> | ||||||
|  |   <body> | ||||||
|  |     <div class="bg"> | ||||||
|  |     <center> | ||||||
|  |       <br> | ||||||
|  |       <br> | ||||||
|  |       <br> | ||||||
|  |       <br> | ||||||
|  |       <br> | ||||||
|  |       <br> | ||||||
|  |       <br> | ||||||
|  |       <br> | ||||||
|  |       <br> | ||||||
|  |       <br> | ||||||
|  |       <br> | ||||||
|  |       <br> | ||||||
|  |       <br> | ||||||
|  |       <br> | ||||||
|  |       <br> | ||||||
|  |       <br> | ||||||
|  |       <h1><a href="https://docs.google.com/presentation/d/e/2PACX-1vRQ1YyZcTDKYINLvUe8OdaDn_mIoCc0v8XSK-rgI3-b8EldgqpwbZEGmPn7J9pN1vnEJ1-pOcl_T-QP/pub?start=false&loop=false&delayms=3000">Access Ability (look ma, no cloud) [slides]</a></h1> | ||||||
|  |     </center> | ||||||
|  |     </div> | ||||||
|  |   </body> | ||||||
|  | </html> | ||||||
							
								
								
									
										
											BIN
										
									
								
								lib/extensions/admin/utahjs2018/thenamesrex.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 30 KiB | 
							
								
								
									
										47
									
								
								lib/extensions/data-files-to-json.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,47 @@ | |||||||
|  | 'use strict'; | ||||||
|  | 
 | ||||||
|  | var fs = require('fs'); | ||||||
|  | var path = require('path'); | ||||||
|  | var basedir = path.join(__dirname, 'emails'); | ||||||
|  | var files = fs.readdirSync(basedir) | ||||||
|  | 
 | ||||||
|  | var emails = {}; | ||||||
|  | files.forEach(function (fname) { | ||||||
|  |   var fpath = path.join(basedir, fname); | ||||||
|  |   var data; | ||||||
|  |   var email; | ||||||
|  |   var iat; | ||||||
|  |   var mdata; | ||||||
|  |   if (!/\.data$/.test(fname)) { | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |   data = JSON.parse(fs.readFileSync(fpath)); | ||||||
|  |   email = fname.replace('\.' + data.domains.join('') + '\.data', ''); | ||||||
|  |   mdata = JSON.parse(fs.readFileSync(path.join(basedir, email))); | ||||||
|  |   if (data.iat) { | ||||||
|  |     iat = new Date(data.iat * 1000).toISOString(); | ||||||
|  |   } | ||||||
|  |   if (!emails[email]) { | ||||||
|  |     emails[email] = { | ||||||
|  |       domains: [] | ||||||
|  |     , ports: [] | ||||||
|  |     , nodes: [ { createdAt: iat, scheme: 'mailto', type: 'email', name: email } ] | ||||||
|  |     , jtis: [] | ||||||
|  |     }; | ||||||
|  |   } | ||||||
|  |   emails[email].jtis.push(data.id); | ||||||
|  |   data.domains.forEach(function (d) { | ||||||
|  |     emails[email].domains.push({ createdAt: iat, name: d, wildcard: true, hostname: mdata.hostname | ||||||
|  |       , os: mdata.os_type, arch: mdata.os_arch }); | ||||||
|  |   }); | ||||||
|  |   data.ports.forEach(function (p) { | ||||||
|  |     emails[email].ports.push({ createdAt: iat, number: p, hostname: mdata.hostname | ||||||
|  |       , os: mdata.os_type, arch: mdata.os_arch }); | ||||||
|  |   }); | ||||||
|  | }); | ||||||
|  | console.log(''); | ||||||
|  | console.log('[\n' + Object.keys(emails).map(function (k) { return JSON.stringify(emails[k]); }).join(',\n') + '\n]'); | ||||||
|  | console.log(''); | ||||||
|  | console.log(''); | ||||||
|  | console.log(Object.keys(emails).join(', ')); | ||||||
|  | console.log(''); | ||||||
							
								
								
									
										315
									
								
								lib/extensions/db.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,315 @@ | |||||||
|  | 'use strict'; | ||||||
|  | 
 | ||||||
|  | var PromiseA; | ||||||
|  | try { | ||||||
|  |   PromiseA = require('bluebird'); | ||||||
|  | } catch(e) { | ||||||
|  |   PromiseA = global.Promise; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | var path = require('path'); | ||||||
|  | var sfs = require('safe-replace'); | ||||||
|  | 
 | ||||||
|  | var DB = module.exports = {}; | ||||||
|  | DB._savefile = path.join(__dirname, 'permissions.json'); | ||||||
|  | DB._load = function () { | ||||||
|  |   try { | ||||||
|  |     DB._perms = require(DB._savefile); | ||||||
|  |   } catch(e) { | ||||||
|  |     try { | ||||||
|  |       DB._perms = require(DB._savefile + '.bak'); | ||||||
|  |     } catch(e) { | ||||||
|  |       DB._perms = []; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   DB._byDomain = {}; | ||||||
|  |   DB._byPort = {}; | ||||||
|  |   DB._byEmail = {}; | ||||||
|  |   DB._byPpid = {}; | ||||||
|  |   DB._byId = {}; | ||||||
|  |   DB._grants = {}; | ||||||
|  |   DB._grantsMap = {}; | ||||||
|  |   DB._authz = {}; | ||||||
|  |   DB._perms.forEach(function (acc) { | ||||||
|  |     if ('authz' === acc.type) { | ||||||
|  |       DB._authz[acc.id] = acc; | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |     if (acc.id) { | ||||||
|  |       // if account has an id
 | ||||||
|  |       DB._byId[acc.id] = acc; | ||||||
|  |       if (!DB._grants[acc.id]) { | ||||||
|  |         DB._grantsMap[acc.id] = {}; | ||||||
|  |         DB._grants[acc.id] = []; | ||||||
|  |       } | ||||||
|  |       acc.domains.forEach(function (d) { | ||||||
|  |         DB._grants[d.name + '|id|' + acc.id] = true; | ||||||
|  |         if (!DB._grantsMap[acc.id][d.name]) { | ||||||
|  |           DB._grantsMap[acc.id][d.name] = d; | ||||||
|  |           DB._grants[acc.id].push(d); | ||||||
|  |         } | ||||||
|  |       }); | ||||||
|  |       acc.ports.forEach(function (p) { | ||||||
|  |         DB._grants[p.number + '|id|' + acc.id] = true; | ||||||
|  |         if (!DB._grantsMap[acc.id][p.number]) { | ||||||
|  |           DB._grantsMap[acc.id][p.number] = p; | ||||||
|  |           DB._grants[acc.id].push(p); | ||||||
|  |         } | ||||||
|  |       }); | ||||||
|  |     } else if (acc.nodes[0] && 'email' === acc.nodes[0].type) { | ||||||
|  |       // if primary (first) node is email
 | ||||||
|  |       //console.log("XXXX email", acc.nodes[0].name);
 | ||||||
|  |       if (!DB._byEmail[acc.nodes[0].name]) { | ||||||
|  |         DB._byEmail[acc.nodes[0].name] = { | ||||||
|  |           account: acc | ||||||
|  |         , node: acc.nodes[0] | ||||||
|  |         }; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     // map domains to all nodes that have permission
 | ||||||
|  |     // (which permission could be granted by more than one account)
 | ||||||
|  |     acc.nodes.forEach(function (node) { | ||||||
|  |       if ('mailto' === node.scheme || 'email' === node.type) { | ||||||
|  |         if (!DB._grants[node.name]) { | ||||||
|  |           DB._grantsMap[node.name] = {}; | ||||||
|  |           DB._grants[node.name] = []; | ||||||
|  |         } | ||||||
|  |         acc.domains.forEach(function (d) { | ||||||
|  |           DB._grants[d.name + '|' + (node.scheme||node.type) + '|' + node.name] = true; | ||||||
|  |           if (!DB._grantsMap[node.name][d.name]) { | ||||||
|  |             DB._grantsMap[node.name][d.name] = d; | ||||||
|  |             DB._grants[node.name].push(d); | ||||||
|  |           } | ||||||
|  |         }); | ||||||
|  |         acc.ports.forEach(function (p) { | ||||||
|  |           DB._grants[p.number + '|' + (node.scheme||node.type) + '|' + node.name] = true; | ||||||
|  |           if (!DB._grantsMap[node.name][p.number]) { | ||||||
|  |             DB._grantsMap[node.name][p.number] = p; | ||||||
|  |             DB._grants[node.name].push(p); | ||||||
|  |           } | ||||||
|  |         }); | ||||||
|  |       } | ||||||
|  |     }); | ||||||
|  |     // TODO this also should be maps/arrays (... or just normal database)
 | ||||||
|  |     acc.domains.forEach(function (domain) { | ||||||
|  |       if (DB._byDomain[domain.name]) { | ||||||
|  |         console.warn("duplicate domain '" + domain.name + "'"); | ||||||
|  |         console.warn("::existing account '" + acc.nodes.map(function (node) { return node.name; }) + "'"); | ||||||
|  |         console.warn("::new account '" + DB._byDomain[domain.name].account.nodes.map(function (node) { return node.name; }) + "'"); | ||||||
|  |       } | ||||||
|  |       DB._byDomain[domain.name] = { | ||||||
|  |         account: acc | ||||||
|  |       , domain: domain | ||||||
|  |       }; | ||||||
|  |     }); | ||||||
|  |     acc.ports.forEach(function (port) { | ||||||
|  |       if (DB._byPort[port.number]) { | ||||||
|  |         console.warn("duplicate port '" + port.number + "'"); | ||||||
|  |         console.warn("::existing account '" + acc.nodes.map(function (node) { return node.name; }) + "'"); | ||||||
|  |         console.warn("::new account '" + DB._byPort[port.number].account.nodes.map(function (node) { return node.name; }) + "'"); | ||||||
|  |       } | ||||||
|  |       DB._byPort[port.number] = { | ||||||
|  |         account: acc | ||||||
|  |       , port: port | ||||||
|  |       }; | ||||||
|  |     }); | ||||||
|  |   }); | ||||||
|  | }; | ||||||
|  | DB._load(); | ||||||
|  | DB.accounts = {}; | ||||||
|  | DB.accounts.get = function (obj) { | ||||||
|  |   return PromiseA.resolve().then(function () { | ||||||
|  |     //console.log('XXXX obj.name', DB._byEmail[obj.name]);
 | ||||||
|  |     return DB._byId[obj.name] || (DB._byEmail[obj.name] || {}).account || null; | ||||||
|  |   }); | ||||||
|  | }; | ||||||
|  | DB.accounts.add = function (obj) { | ||||||
|  |   return PromiseA.resolve().then(function () { | ||||||
|  |     if (obj.id) { | ||||||
|  |       // TODO more checks
 | ||||||
|  |       DB._perms.push(obj); | ||||||
|  |     } else if ('email' === obj.nodes[0].type || obj.email) { | ||||||
|  |       obj.email = undefined; | ||||||
|  |       DB._perms.push(obj); | ||||||
|  |     } | ||||||
|  |   }); | ||||||
|  | }; | ||||||
|  | DB.authorizations = {}; | ||||||
|  | DB.authorizations.create = function (acc, claim) { | ||||||
|  |   if (!acc.id || !claim.type || !claim.value) { throw new Error("requires account id"); } | ||||||
|  |   var crypto = require('crypto'); | ||||||
|  |   var authz = DB._authz[acc.id]; | ||||||
|  |   if (!authz) { | ||||||
|  |     authz = { | ||||||
|  |       id: acc.id | ||||||
|  |     , type: 'authz' | ||||||
|  |     , claims: [] | ||||||
|  |     }; | ||||||
|  |     DB._authz[acc.id] = authz; | ||||||
|  |     DB._perms.push(authz); | ||||||
|  |   } | ||||||
|  |   // TODO check for unique type:value pairing in claims
 | ||||||
|  |   claim.challenge = crypto.randomBytes(16).toString('hex'); | ||||||
|  |   claim.createdAt = Date.now(); | ||||||
|  |   claim.verifiedAt = 0; | ||||||
|  |   authz.claims.push(claim); | ||||||
|  |   DB.save(); | ||||||
|  |   return JSON.parse(JSON.stringify(claim)); | ||||||
|  | }; | ||||||
|  | DB.authorizations.check = function (acc, claim) { | ||||||
|  |   var authz = DB._authz[acc.id]; | ||||||
|  |   var vclaim = null; | ||||||
|  |   if (!authz) { | ||||||
|  |     return vclaim; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   authz.claims.some(function (c) { | ||||||
|  |     console.log('authz.check', c); | ||||||
|  |     if (claim.challenge) { | ||||||
|  |       if (c.challenge === claim.challenge) { | ||||||
|  |         vclaim = JSON.parse(JSON.stringify(c)); | ||||||
|  |         return true; | ||||||
|  |       } | ||||||
|  |     } else if (claim.value === c.value) { | ||||||
|  |       vclaim = JSON.parse(JSON.stringify(c)); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|  |   }); | ||||||
|  |   return vclaim; | ||||||
|  | }; | ||||||
|  | DB.authorizations.checkAll = function (acc) { | ||||||
|  |   var authz = DB._authz[acc.id]; | ||||||
|  |   if (!authz) { | ||||||
|  |     return []; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   return authz.claims.map(function (claim) { | ||||||
|  |     return JSON.parse(JSON.stringify(claim)); | ||||||
|  |   }); | ||||||
|  | }; | ||||||
|  | DB.authorizations.verify = function (acc, claim) { | ||||||
|  |   var scmp = require('scmp'); | ||||||
|  |   var authz = DB._authz[acc.id]; | ||||||
|  |   var vclaim; | ||||||
|  |   if (!authz) { return false; } | ||||||
|  | 
 | ||||||
|  |   authz.claims.some(function (c) { | ||||||
|  |     if (scmp(c.challenge, claim.challenge)) { | ||||||
|  |       vclaim = c; | ||||||
|  |       c.verifiedAt = Date.now(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   if (vclaim) { | ||||||
|  |     DB.save(); | ||||||
|  |     return true; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   return false; | ||||||
|  | }; | ||||||
|  | DB.domains = {}; | ||||||
|  | DB.domains.available = function (name) { | ||||||
|  |   return PromiseA.resolve().then(function () { | ||||||
|  |     return !DB._byDomain[name]; | ||||||
|  |   }); | ||||||
|  | }; | ||||||
|  | DB.domains._add = function (acc, opts) { | ||||||
|  |   // TODO verifications to change ownership of a domain
 | ||||||
|  |   return PromiseA.resolve().then(function () { | ||||||
|  |     var err; | ||||||
|  |     //var acc = DB._byId[aid];
 | ||||||
|  |     var domain = { | ||||||
|  |       name: (opts.domain || opts.name) | ||||||
|  |     , hostname: opts.hostname | ||||||
|  |     , os: opts.os | ||||||
|  |     , createdAt: new Date().toISOString() | ||||||
|  |     , wildcard: opts.wildcard | ||||||
|  |     }; | ||||||
|  |     var pdomain; | ||||||
|  |     var parts = (opts.domain || domain.name).split('.').map(function (el, i, arr) { | ||||||
|  |       return arr.slice(i).join('.'); | ||||||
|  |     }).reverse(); | ||||||
|  |     parts.shift(); | ||||||
|  |     parts.pop(); | ||||||
|  |     if (parts.some(function (part) { | ||||||
|  |       if (DB._byDomain[part] && DB._byDomain[part].wildcard) { | ||||||
|  |         pdomain = part; | ||||||
|  |         return true; | ||||||
|  |       } | ||||||
|  |     })) { | ||||||
|  |       err = new Error("'" + domain.name + "' exists as '" + pdomain + "' and therefore requires an admin to review and approve"); | ||||||
|  |       err.code = "E_REQ_ADMIN"; | ||||||
|  |       throw err; | ||||||
|  |     } | ||||||
|  |     if (DB._byDomain[domain.name]) { | ||||||
|  |       if (acc !== DB._byDomain[domain.name].account) { | ||||||
|  |         throw new Error("domain '" + domain.name + "' exists"); | ||||||
|  |       } | ||||||
|  |       // happily ignore non-change
 | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |     DB._byDomain[domain.name] = { | ||||||
|  |       account: acc | ||||||
|  |     , domain: domain | ||||||
|  |     }; | ||||||
|  |     acc.domains.push(domain); | ||||||
|  |     DB.save(); | ||||||
|  |   }); | ||||||
|  | }; | ||||||
|  | DB.ports = {}; | ||||||
|  | DB.ports.available = function (number) { | ||||||
|  |   return PromiseA.resolve().then(function () { | ||||||
|  |     return !DB._byPort[number]; | ||||||
|  |   }); | ||||||
|  | }; | ||||||
|  | DB.ports._add = function (acc, opts) { | ||||||
|  |   return PromiseA.resolve().then(function () { | ||||||
|  |     //var acc = DB._byId[aid];
 | ||||||
|  |     var port = { | ||||||
|  |       number: opts.port || opts.number | ||||||
|  |     , hostname: opts.hostname | ||||||
|  |     , os: opts.os | ||||||
|  |     , createdAt: new Date().toISOString() | ||||||
|  |     }; | ||||||
|  |     if (DB._byPort[port.number]) { | ||||||
|  |       // TODO verifications
 | ||||||
|  |       throw new Error("port '" + port.number + "' exists"); | ||||||
|  |     } | ||||||
|  |     DB._byPort[port.number] = { | ||||||
|  |       account: acc | ||||||
|  |     , port: port | ||||||
|  |     }; | ||||||
|  |     acc.ports.push(port); | ||||||
|  |   }); | ||||||
|  | }; | ||||||
|  | DB._save = function () { | ||||||
|  |   return sfs.writeFileAsync(DB._savefile, JSON.stringify(DB._perms)); | ||||||
|  | }; | ||||||
|  | DB._saveToken = null; | ||||||
|  | DB._savePromises = []; | ||||||
|  | DB._savePromise = PromiseA.resolve(); | ||||||
|  | DB.save = function () { | ||||||
|  |   clearTimeout(DB._saveToken); | ||||||
|  |   return new PromiseA(function (resolve, reject) { | ||||||
|  |     function doSave() { | ||||||
|  |       DB._savePromise = DB._savePromise.then(function () { | ||||||
|  |         return DB._save().then(function (yep) { | ||||||
|  |           DB._savePromises.forEach(function (p) { | ||||||
|  |             p.resolve(yep); | ||||||
|  |           }); | ||||||
|  |           DB._savePromises.length = 1; | ||||||
|  |         }, function (err) { | ||||||
|  |           DB._savePromises.forEach(function (p) { | ||||||
|  |             p.reject(err); | ||||||
|  |           }); | ||||||
|  |           DB._savePromises.length = 1; | ||||||
|  |         }); | ||||||
|  |       }); | ||||||
|  |       return DB._savePromise; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     DB._saveToken = setTimeout(doSave, 2500); | ||||||
|  |     DB._savePromises.push({ resolve: resolve, reject: reject }); | ||||||
|  |   }); | ||||||
|  | }; | ||||||
							
								
								
									
										0
									
								
								lib/extensions/emails/.gitkeep
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										1232
									
								
								lib/extensions/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										17
									
								
								lib/extensions/package.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,17 @@ | |||||||
|  | { | ||||||
|  |   "name": "telebit.commercial", | ||||||
|  |   "version": "1.0.0", | ||||||
|  |   "private": true, | ||||||
|  |   "description": "Commercial node.js APIs for Telebit", | ||||||
|  |   "main": "index.js", | ||||||
|  |   "scripts": { | ||||||
|  |     "test": "echo \"Error: no test specified\" && exit 1" | ||||||
|  |   }, | ||||||
|  |   "author": "", | ||||||
|  |   "license": "SEE LICENSE IN LICENSE", | ||||||
|  |   "dependencies": { | ||||||
|  |     "jwk-to-pem": "^2.0.0", | ||||||
|  |     "oauth3.js": "^1.2.5", | ||||||
|  |     "scmp": "^1.0.2" | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										12
									
								
								lib/extensions/perms-to-email.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,12 @@ | |||||||
|  | 'use strict'; | ||||||
|  | 
 | ||||||
|  | var perms = require('./permissions.json'); | ||||||
|  | var emails = {}; | ||||||
|  | perms.forEach(function (p) { | ||||||
|  |   p.nodes.forEach(function (n) { | ||||||
|  |     if ('email' === n.type) { | ||||||
|  |       emails[n.name] = true; | ||||||
|  |     } | ||||||
|  |   }); | ||||||
|  | }); | ||||||
|  | console.log(Object.keys(emails).join(', ')); | ||||||
| @ -10,7 +10,7 @@ function noSniCallback(tag) { | |||||||
|     var err = new Error("[noSniCallback] no handler set for '" + tag + "':'" + servername + "'"); |     var err = new Error("[noSniCallback] no handler set for '" + tag + "':'" + servername + "'"); | ||||||
|     console.error(err.message); |     console.error(err.message); | ||||||
|     cb(new Error(err)); |     cb(new Error(err)); | ||||||
|   }; |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| module.exports.create = function (state) { | module.exports.create = function (state) { | ||||||
| @ -72,11 +72,20 @@ module.exports.create = function (state) { | |||||||
|   state.tlsInvalidSniServer.on('tlsClientError', function () { |   state.tlsInvalidSniServer.on('tlsClientError', function () { | ||||||
|     console.error('tlsClientError InvalidSniServer'); |     console.error('tlsClientError InvalidSniServer'); | ||||||
|   }); |   }); | ||||||
|   state.createHttpInvalid = function (opts) { |   state.httpsInvalid = function (servername, socket) { | ||||||
|     return http.createServer(function (req, res) { |     // none of these methods work:
 | ||||||
|       if (!opts.servername) { |     // httpsServer.emit('connection', socket);  // this didn't work
 | ||||||
|  |     // tlsServer.emit('connection', socket);    // this didn't work either
 | ||||||
|  |     //console.log('chunkLen', firstChunk.byteLength);
 | ||||||
|  | 
 | ||||||
|  |     console.log('[httpsInvalid] servername', servername); | ||||||
|  |     //state.tlsInvalidSniServer.emit('connection', wrapSocket(socket));
 | ||||||
|  |     var tlsInvalidSniServer = tls.createServer(state.tlsOptions, function (tlsSocket) { | ||||||
|  |       console.log('[tlsInvalid] tls connection'); | ||||||
|  |       // things get a little messed up here
 | ||||||
|  |       var httpInvalidSniServer = http.createServer(function (req, res) { | ||||||
|  |         if (!servername) { | ||||||
|           res.statusCode = 422; |           res.statusCode = 422; | ||||||
|         res.setHeader('Content-Type', 'text/plain; charset=utf-8'); |  | ||||||
|           res.end( |           res.end( | ||||||
|             "3. An inexplicable temporal shift of the quantum realm... that makes me feel uncomfortable.\n\n" |             "3. An inexplicable temporal shift of the quantum realm... that makes me feel uncomfortable.\n\n" | ||||||
|           + "[ERROR] No SNI header was sent. I can only think of two possible explanations for this:\n" |           + "[ERROR] No SNI header was sent. I can only think of two possible explanations for this:\n" | ||||||
| @ -86,30 +95,15 @@ module.exports.create = function (state) { | |||||||
|           return; |           return; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|       // TODO use req.headers.host instead of servername (since domain fronting is disabled anyway)
 |  | ||||||
|       res.statusCode = 502; |  | ||||||
|       res.setHeader('Content-Type', 'text/html; charset=utf-8'); |  | ||||||
|         res.end( |         res.end( | ||||||
|         "<h1>Oops!</h1>" |           "You came in hot looking for '" + servername + "' and, granted, the IP address for that domain" | ||||||
|       + "<p>It looks like '" + encodeURIComponent(opts.servername) + "' isn't connected right now.</p>" |         + " must be pointing here (or else how could you be here?), nevertheless either it's not registered" | ||||||
|       + "<p><small>Last seen: " + opts.ago + "</small></p>" |         + " in the internal system at all (which Seth says isn't even a thing) or there is no device" | ||||||
|       + "<p><small>Error: 502 Bad Gateway</small></p>" |         + " connected on the south side of the network which has informed me that it's ready to have traffic" | ||||||
|       ); |         + " for that domain forwarded to it (sorry I didn't check that deeply to determine which).\n\n" | ||||||
|  |         + "Either way, you're doing strange things that make me feel uncomfortable... Please don't touch me there any more."); | ||||||
|       }); |       }); | ||||||
|   }; |       httpInvalidSniServer.emit('connection', tlsSocket); | ||||||
|   state.httpsInvalid = function (opts, socket) { |  | ||||||
|     // none of these methods work:
 |  | ||||||
|     // httpsServer.emit('connection', socket);  // this didn't work
 |  | ||||||
|     // tlsServer.emit('connection', socket);    // this didn't work either
 |  | ||||||
|     //console.log('chunkLen', firstChunk.byteLength);
 |  | ||||||
| 
 |  | ||||||
|     console.log('[httpsInvalid] servername', opts.servername); |  | ||||||
|     //state.tlsInvalidSniServer.emit('connection', wrapSocket(socket));
 |  | ||||||
|     var tlsInvalidSniServer = tls.createServer(state.tlsOptions, function (tlsSocket) { |  | ||||||
|       console.log('[tlsInvalid] tls connection'); |  | ||||||
|       // We create an entire http server object because it's difficult to figure out
 |  | ||||||
|       // how to access the original tlsSocket to get the servername
 |  | ||||||
|       state.createHttpInvalid(opts).emit('connection', tlsSocket); |  | ||||||
|     }); |     }); | ||||||
|     tlsInvalidSniServer.on('tlsClientError', function () { |     tlsInvalidSniServer.on('tlsClientError', function () { | ||||||
|       console.error('tlsClientError InvalidSniServer httpsInvalid'); |       console.error('tlsClientError InvalidSniServer httpsInvalid'); | ||||||
|  | |||||||
| @ -68,6 +68,7 @@ module.exports.create = function (state) { | |||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     if (initToken) { |     if (initToken) { | ||||||
|  |       console.log('[wss.onConnection] token provided in http headers'); | ||||||
|       return Server.addToken(state, srv, initToken).then(function () { |       return Server.addToken(state, srv, initToken).then(function () { | ||||||
|         Server.init(state, srv); |         Server.init(state, srv); | ||||||
|       }).catch(function (err) { |       }).catch(function (err) { | ||||||
|  | |||||||
| @ -172,7 +172,6 @@ var Server = { | |||||||
| , _initSocketHandlers: function (state, srv) { | , _initSocketHandlers: function (state, srv) { | ||||||
|     function refreshTimeout() { |     function refreshTimeout() { | ||||||
|       srv.lastActivity = Date.now(); |       srv.lastActivity = Date.now(); | ||||||
|       Devices.touchDevice(state.deviceLists, srv); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     function checkTimeout() { |     function checkTimeout() { | ||||||
|  | |||||||
| @ -2,16 +2,6 @@ | |||||||
| 
 | 
 | ||||||
| var sni = require('sni'); | var sni = require('sni'); | ||||||
| var pipeWs = require('./pipe-ws.js'); | var pipeWs = require('./pipe-ws.js'); | ||||||
| var ago = require('./ago.js').AGO; |  | ||||||
| var up = Date.now(); |  | ||||||
| 
 |  | ||||||
| function fromUptime(ms) { |  | ||||||
|   if (ms) { |  | ||||||
|     return ago(Date.now() - ms); |  | ||||||
|   } else { |  | ||||||
|     return "Not seen since relay restarted, " + ago(Date.now() - up); |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| 
 | 
 | ||||||
| module.exports.createTcpConnectionHandler = function (state) { | module.exports.createTcpConnectionHandler = function (state) { | ||||||
|   var Devices = state.Devices; |   var Devices = state.Devices; | ||||||
| @ -37,16 +27,6 @@ module.exports.createTcpConnectionHandler = function (state) { | |||||||
|       var str; |       var str; | ||||||
|       var m; |       var m; | ||||||
| 
 | 
 | ||||||
|       if (!firstChunk) { |  | ||||||
|         try { |  | ||||||
|           conn.end(); |  | ||||||
|         } catch(e) { |  | ||||||
|           console.error("[lib/unwrap-tls.js] Error:", e); |  | ||||||
|           conn.destroy(); |  | ||||||
|         } |  | ||||||
|         return; |  | ||||||
|       } |  | ||||||
| 
 |  | ||||||
|       //conn.pause();
 |       //conn.pause();
 | ||||||
|       conn.unshift(firstChunk); |       conn.unshift(firstChunk); | ||||||
| 
 | 
 | ||||||
| @ -58,15 +38,8 @@ module.exports.createTcpConnectionHandler = function (state) { | |||||||
| 
 | 
 | ||||||
|       // defer after return (instead of being in many places)
 |       // defer after return (instead of being in many places)
 | ||||||
|       function deferData(fn) { |       function deferData(fn) { | ||||||
|         if ('httpsInvalid' === fn) { |         if (fn) { | ||||||
|           state[fn]({ |  | ||||||
|             servername: servername |  | ||||||
|           , ago: fromUptime(Devices.lastSeen(state.deviceLists, servername)) |  | ||||||
|           }, conn); |  | ||||||
|         } else if (fn) { |  | ||||||
|           state[fn](servername, conn); |           state[fn](servername, conn); | ||||||
|         } else { |  | ||||||
|           console.error("[SANITY ERROR] '" + fn + "' doesn't have a state handler"); |  | ||||||
|         } |         } | ||||||
|         /* |         /* | ||||||
|         process.nextTick(function () { |         process.nextTick(function () { | ||||||
| @ -75,80 +48,32 @@ module.exports.createTcpConnectionHandler = function (state) { | |||||||
|         */ |         */ | ||||||
|       } |       } | ||||||
| 
 | 
 | ||||||
|       var httpOutcomes = { |       function tryTls() { | ||||||
|         missingServername: function () { |         var vhost; | ||||||
|           console.log("[debug] [http] missing servername"); |  | ||||||
|           // TODO use a more specific error page
 |  | ||||||
|           deferData('handleInsecureHttp'); |  | ||||||
|         } |  | ||||||
|       , requiresSetup: function () { |  | ||||||
|           console.log("[debug] [http] requires setup"); |  | ||||||
|           // TODO Insecure connections for setup will not work on secure domains (i.e. .app)
 |  | ||||||
|           state.httpSetupServer.emit('connection', conn); |  | ||||||
|         } |  | ||||||
|       , isInternal: function () { |  | ||||||
|           console.log("[debug] [http] is known internally (admin)"); |  | ||||||
|           if (/well-known/.test(str)) { |  | ||||||
|             deferData('handleHttp'); |  | ||||||
|           } else { |  | ||||||
|             deferData('handleInsecureHttp'); |  | ||||||
|           } |  | ||||||
|         } |  | ||||||
|       , isVhost: function () { |  | ||||||
|           console.log("[debug] [http] is vhost (normal server)"); |  | ||||||
|           if (/well-known/.test(str)) { |  | ||||||
|             deferData('handleHttp'); |  | ||||||
|           } else { |  | ||||||
|             deferData('handleInsecureHttp'); |  | ||||||
|           } |  | ||||||
|         } |  | ||||||
|       , assumeExternal: function () { |  | ||||||
|           console.log("[debug] [http] assume external"); |  | ||||||
|           var service = 'http'; |  | ||||||
| 
 | 
 | ||||||
|           if (!Devices.exist(state.deviceLists, servername)) { |         if (!servername) { | ||||||
|             // It would be better to just re-read the host header rather
 |  | ||||||
|             // than creating a whole server object, but this is a "rare"
 |  | ||||||
|             // case and I'm feeling lazy right now.
 |  | ||||||
|             console.log("[debug] [http] no device connected"); |  | ||||||
|             state.createHttpInvalid({ |  | ||||||
|               servername: servername |  | ||||||
|             , ago: fromUptime(Devices.lastSeen(state.deviceLists, servername)) |  | ||||||
|             }).emit('connection', conn); |  | ||||||
|             return; |  | ||||||
|           } |  | ||||||
| 
 |  | ||||||
|           // TODO make https redirect configurable on a per-domain basis
 |  | ||||||
|           // /^\/\.well-known\/acme-challenge\//.test(str)
 |  | ||||||
|           if (/well-known/.test(str)) { |  | ||||||
|             // HTTP
 |  | ||||||
|             console.log("[debug] [http] passthru"); |  | ||||||
|             pipeWs(servername, service, Devices.next(state.deviceLists, servername), conn, serviceport); |  | ||||||
|             return; |  | ||||||
|           } else { |  | ||||||
|             console.log("[debug] [http] redirect to https"); |  | ||||||
|             deferData('handleInsecureHttp'); |  | ||||||
|           } |  | ||||||
|         } |  | ||||||
|       }; |  | ||||||
|       var tlsOutcomes = { |  | ||||||
|         missingServername: function () { |  | ||||||
|           if (state.debug) { console.log("No SNI was given, so there's nothing we can do here"); } |           if (state.debug) { console.log("No SNI was given, so there's nothing we can do here"); } | ||||||
|           deferData('httpsInvalid'); |           deferData('httpsInvalid'); | ||||||
|  |           return; | ||||||
|         } |         } | ||||||
|       , requiresSetup: function () { | 
 | ||||||
|  |         if (!state.servernames.length) { | ||||||
|           console.info("[Setup] https => admin => setup => (needs bogus tls certs to start?)"); |           console.info("[Setup] https => admin => setup => (needs bogus tls certs to start?)"); | ||||||
|           deferData('httpsSetupServer'); |           deferData('httpsSetupServer'); | ||||||
|  |           return; | ||||||
|         } |         } | ||||||
|       , isInternal: function () { | 
 | ||||||
|  |         if (-1 !== state.servernames.indexOf(servername)) { | ||||||
|           if (state.debug) { console.log("[Admin]", servername); } |           if (state.debug) { console.log("[Admin]", servername); } | ||||||
|           deferData('httpsTunnel'); |           deferData('httpsTunnel'); | ||||||
|  |           return; | ||||||
|         } |         } | ||||||
|       , isVhost: function (vhost) { | 
 | ||||||
|           if (state.debug) { console.log("[tcp] [vhost]", state.config.vhost, "=>", vhost); } |         if (state.config.nowww && /^www\./i.test(servername)) { | ||||||
|           deferData('httpsVhost'); |           console.log("TODO: use www bare redirect"); | ||||||
|         } |         } | ||||||
|       , assumeExternal: function () { | 
 | ||||||
|  |         function run() { | ||||||
|           var nextDevice = Devices.next(state.deviceLists, servername); |           var nextDevice = Devices.next(state.deviceLists, servername); | ||||||
|           if (!nextDevice) { |           if (!nextDevice) { | ||||||
|             if (state.debug) { console.log("No devices match the given servername"); } |             if (state.debug) { console.log("No devices match the given servername"); } | ||||||
| @ -157,33 +82,27 @@ module.exports.createTcpConnectionHandler = function (state) { | |||||||
|           } |           } | ||||||
| 
 | 
 | ||||||
|           if (state.debug) { console.log("pipeWs(servername, service, deviceLists['" + servername + "'], socket)"); } |           if (state.debug) { console.log("pipeWs(servername, service, deviceLists['" + servername + "'], socket)"); } | ||||||
|  |           deferData(); | ||||||
|           pipeWs(servername, service, nextDevice, conn, serviceport); |           pipeWs(servername, service, nextDevice, conn, serviceport); | ||||||
|         } |         } | ||||||
|       }; |  | ||||||
| 
 |  | ||||||
|       function handleConnection(outcomes) { |  | ||||||
|         var vhost; |  | ||||||
| 
 |  | ||||||
|         // No routing information available
 |  | ||||||
|         if (!servername) { outcomes.missingServername(); return; } |  | ||||||
|         // Server needs to be set up
 |  | ||||||
|         if (!state.servernames.length) { outcomes.requiresSetup(); return; } |  | ||||||
|         // This is one of the admin domains
 |  | ||||||
|         if (-1 !== state.servernames.indexOf(servername)) { outcomes.isInternal(); return; } |  | ||||||
| 
 | 
 | ||||||
|         // TODO don't run an fs check if we already know this is working elsewhere
 |         // TODO don't run an fs check if we already know this is working elsewhere
 | ||||||
|         //if (!state.validHosts) { state.validHosts = {}; }
 |         //if (!state.validHosts) { state.validHosts = {}; }
 | ||||||
|         if (state.config.vhost) { |         if (state.config.vhost) { | ||||||
|           vhost = state.config.vhost.replace(/:hostname/, servername); |           vhost = state.config.vhost.replace(/:hostname/, (servername||'reallydoesntexist')); | ||||||
|  |           if (state.debug) { console.log("[tcp] [vhost]", state.config.vhost, "=>", vhost); } | ||||||
|  |           //state.httpsVhost(servername, conn);
 | ||||||
|  |           //return;
 | ||||||
|           require('fs').readdir(vhost, function (err, nodes) { |           require('fs').readdir(vhost, function (err, nodes) { | ||||||
|             if (state.debug && err) { console.log("VHOST error", err); } |             if (state.debug && err) { console.log("VHOST error", err); } | ||||||
|             if (err || !nodes) { outcomes.assumeExternal(); return; } |             if (err || !nodes) { run(); return; } | ||||||
|             outcomes.isVhost(vhost); |             //if (nodes) { deferData('httpsVhost'); return; }
 | ||||||
|  |             deferData('httpsVhost'); | ||||||
|           }); |           }); | ||||||
|           return; |           return; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         outcomes.assumeExternal(); |         run(); | ||||||
|       } |       } | ||||||
| 
 | 
 | ||||||
|       // https://github.com/mscdex/httpolyglot/issues/3#issuecomment-173680155
 |       // https://github.com/mscdex/httpolyglot/issues/3#issuecomment-173680155
 | ||||||
| @ -192,19 +111,40 @@ module.exports.createTcpConnectionHandler = function (state) { | |||||||
|         service = 'https'; |         service = 'https'; | ||||||
|         servername = (sni(firstChunk)||'').toLowerCase().trim(); |         servername = (sni(firstChunk)||'').toLowerCase().trim(); | ||||||
|         if (state.debug) { console.log("[tcp] tls hello from '" + servername + "'"); } |         if (state.debug) { console.log("[tcp] tls hello from '" + servername + "'"); } | ||||||
|         handleConnection(tlsOutcomes); |         tryTls(); | ||||||
|         return; |         return; | ||||||
|       } |       } | ||||||
| 
 | 
 | ||||||
|       if (firstChunk[0] > 32 && firstChunk[0] < 127) { |       if (firstChunk[0] > 32 && firstChunk[0] < 127) { | ||||||
|         // (probably) HTTP
 |  | ||||||
|         str = firstChunk.toString(); |         str = firstChunk.toString(); | ||||||
|         m = str.match(/(?:^|[\r\n])Host: ([^\r\n]+)[\r\n]*/im); |         m = str.match(/(?:^|[\r\n])Host: ([^\r\n]+)[\r\n]*/im); | ||||||
|         servername = (m && m[1].toLowerCase() || '').split(':')[0]; |         servername = (m && m[1].toLowerCase() || '').split(':')[0]; | ||||||
|         if (state.debug) { console.log("[tcp] http hostname '" + servername + "'"); } |         if (state.debug) { console.log("[tcp] http hostname '" + servername + "'"); } | ||||||
| 
 | 
 | ||||||
|         if (/HTTP\//i.test(str)) { |         if (/HTTP\//i.test(str)) { | ||||||
|           handleConnection(httpOutcomes); |           if (!state.servernames.length) { | ||||||
|  |             console.info("[tcp] No admin servername. Entering setup mode."); | ||||||
|  |             deferData(); | ||||||
|  |             state.httpSetupServer.emit('connection', conn); | ||||||
|  |             return; | ||||||
|  |           } | ||||||
|  | 
 | ||||||
|  |           service = 'http'; | ||||||
|  |           // TODO make https redirect configurable
 | ||||||
|  |           // /^\/\.well-known\/acme-challenge\//.test(str)
 | ||||||
|  |           if (/well-known/.test(str)) { | ||||||
|  |             // HTTP
 | ||||||
|  |             if (Devices.exist(state.deviceLists, servername)) { | ||||||
|  |               deferData(); | ||||||
|  |               pipeWs(servername, service, Devices.next(state.deviceLists, servername), conn, serviceport); | ||||||
|  |               return; | ||||||
|  |             } | ||||||
|  |             deferData('handleHttp'); | ||||||
|  |             return; | ||||||
|  |           } | ||||||
|  | 
 | ||||||
|  |           // redirect to https
 | ||||||
|  |           deferData('handleInsecureHttp'); | ||||||
|           return; |           return; | ||||||
|         } |         } | ||||||
|       } |       } | ||||||
|  | |||||||
							
								
								
									
										14
									
								
								package.json
									
									
									
									
									
								
							
							
						
						| @ -37,20 +37,32 @@ | |||||||
|   }, |   }, | ||||||
|   "homepage": "https://git.coolaj86.com/coolaj86/telebit-relay.js", |   "homepage": "https://git.coolaj86.com/coolaj86/telebit-relay.js", | ||||||
|   "dependencies": { |   "dependencies": { | ||||||
|     "bluebird": "^3.5.1", |     "@coolaj86/urequest": "^1.3.5", | ||||||
|  |     "body-parser": "^1.18.3", | ||||||
|     "cluster-store": "^2.0.8", |     "cluster-store": "^2.0.8", | ||||||
|  |     "connect-cors": "^0.5.6", | ||||||
|  |     "escape-html": "^1.0.3", | ||||||
|  |     "express": "^4.16.3", | ||||||
|     "finalhandler": "^1.1.1", |     "finalhandler": "^1.1.1", | ||||||
|     "greenlock": "^2.2.4", |     "greenlock": "^2.2.4", | ||||||
|     "human-readable-ids": "^1.0.4", |     "human-readable-ids": "^1.0.4", | ||||||
|     "js-yaml": "^3.11.0", |     "js-yaml": "^3.11.0", | ||||||
|     "jsonwebtoken": "^8.3.0", |     "jsonwebtoken": "^8.3.0", | ||||||
|  |     "jwk-to-pem": "^2.0.0", | ||||||
|  |     "mkdirp": "^0.5.1", | ||||||
|  |     "nowww": "^1.2.1", | ||||||
|     "proxy-packer": "^2.0.0", |     "proxy-packer": "^2.0.0", | ||||||
|     "recase": "^1.0.4", |     "recase": "^1.0.4", | ||||||
|     "redirect-https": "^1.1.5", |     "redirect-https": "^1.1.5", | ||||||
|  |     "request": "^2.87.0", | ||||||
|  |     "safe-replace": "^1.0.3", | ||||||
|     "serve-static": "^1.13.2", |     "serve-static": "^1.13.2", | ||||||
|     "sni": "^1.0.0", |     "sni": "^1.0.0", | ||||||
|     "ws": "^5.1.1" |     "ws": "^5.1.1" | ||||||
|   }, |   }, | ||||||
|  |   "trulyOptionalDependencies": { | ||||||
|  |     "bluebird": "^3.5.1" | ||||||
|  |   }, | ||||||
|   "engineStrict": true, |   "engineStrict": true, | ||||||
|   "engines": { |   "engines": { | ||||||
|     "node": "10.2.1" |     "node": "10.2.1" | ||||||
|  | |||||||
| @ -1,24 +0,0 @@ | |||||||
| name: telebit-relay |  | ||||||
| version: '0.20.0' |  | ||||||
| summary: Because friends don't let friends localhost |  | ||||||
| description: | |  | ||||||
|   A server that works in combination with Telebit Remote |  | ||||||
|   to allow you to serve http and https from any computer, |  | ||||||
|   anywhere through a secure tunnel. |  | ||||||
| 
 |  | ||||||
| grade: stable |  | ||||||
| confinement: strict |  | ||||||
| 
 |  | ||||||
| apps: |  | ||||||
|   telebit-relay: |  | ||||||
|     command: telebit-relay --config $SNAP_COMMON/config.yml |  | ||||||
|     plugs: [network, network-bind] |  | ||||||
|     daemon: simple |  | ||||||
| 
 |  | ||||||
| parts: |  | ||||||
|   telebit-relay: |  | ||||||
|     plugin: nodejs |  | ||||||
|     node-engine: 10.13.0 |  | ||||||
|     source: . |  | ||||||
|     override-build: | |  | ||||||
|       snapcraftctl build |  | ||||||