Compare commits

..

8 Commits

67 changed files with 335 additions and 4291 deletions

14
.gitignore vendored
View File

@ -1,8 +1,3 @@
emails
lib/extensions/permissions.json
lib/extensions/permissions.json.bak
lib/extensions/admin/sclient/dist/
lib/extensions/admin/optify/dist/
node_modules.*
include
bin/node
@ -48,3 +43,12 @@ jspm_packages
# Optional REPL history
.node_repl_history
# Snapcraft
/parts/
/prime/
/stage/
.snapcraft
*.snap
*.tar.bz2

View File

@ -2,10 +2,6 @@
(function () {
'use strict';
var fs = require('fs');
var path = require('path');
var os = require('os');
var pkg = require('../package.json');
var argv = process.argv.slice(2);
@ -51,7 +47,13 @@ function applyConfig(config) {
} else {
state.Promise = require('bluebird');
}
state.tlsOptions = {}; // TODO just close the sockets that would use this early? or use the admin servername
state.tlsOptions = {
// 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.servernames = config.servernames || [];
state.secret = state.config.secret;
@ -71,59 +73,54 @@ function applyConfig(config) {
state.config.greenlock.configDir = require('os').homedir() + require('path').sep + 'acme';
}
// The domains being approved for the first time are listed in opts.domains
// Certs being renewed are listed in certs.altnames
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
function allow() {
// The domains being approved for the first time are listed in opts.domains
// Certs being renewed are listed in certs.altnames
if (certs) {
opts.domains = certs.altnames;
cb(null, { options: opts, certs: certs });
return;
}
if (!state.validHosts) { state.validHosts = {}; }
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;
opts.email = state.config.email;
opts.agreeTos = state.config.agreeTos;
opts.communityMember = state.config.communityMember || state.config.greenlock.communityMember;
opts.challenges = {
// TODO dns-01
'http-01': require('le-challenge-fs').create({ webrootPath: path.join(os.tmpdir(), 'acme-challenges') })
'http-01': require('le-challenge-fs').create({ webrootPath: '/tmp/acme-challenges' })
};
opts.communityMember = state.config.communityMember;
cb(null, { options: opts, certs: certs });
}
function deny() {
cb(new Error("[bin/telebit-relay.js] failed the approval chain '" + opts.domains[0] + "'"));
return;
function check() {
if (state.debug) { console.log('[sni] checking servername'); }
if (-1 !== state.servernames.indexOf(opts.domain) || -1 !== (state._servernames||[]).indexOf(opts.domain)) {
approve();
} else {
cb(new Error("failed the approval chain '" + opts.domains[0] + "'"));
}
}
// 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();
check();
}
state.greenlock = Greenlock.create({
@ -205,7 +202,7 @@ function applyConfig(config) {
//});
}
fs.readFile(confpath, 'utf8', function (err, text) {
require('fs').readFile(confpath, 'utf8', function (err, text) {
var config;
var recase = require('recase').create({});

View File

@ -1,27 +0,0 @@
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'))"

View File

@ -1,24 +1,17 @@
email: coolaj86@gmail.com
agree_tos: true
community_member: true
telemetry: true
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: example.com
api_domain: example.com
shared_domain: example.com
servernames:
- www.example.com
- example.com
- api.example.com
vhost: /srv/www/:hostname
shared_domain: xm.pl
servernames: # hostnames that direct to the Telebit Relay admin console
- telebit.example.com
- telebit.example.net
vhost: /srv/www/:hostname # load secure websites at this path (uses template string, i.e. /var/www/:hostname/public)
greenlock:
version: 'draft-11'
server: 'https://acme-v02.api.letsencrypt.org/directory'
store:
strategy: le-store-certbot
config_dir: /opt/telebit-relay/etc/acme
secret: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
mailer:
url: 'https://api.mailgun.net/v3/EXAMPLE.COM/messages'
api_key: 'key-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
from: 'Example Mailer <MALIER@EXAMPLE.COM>'
debug: true
strategy: le-store-certbot # certificate storage plugin
config_dir: /etc/acme # directory for ssl certificates
secret: '' # generate with node -e "console.log(crypto.randomBytes(16).toString('hex'))"

View File

@ -1,5 +0,0 @@
servernames: [ 'telebit.cloud' ]
email: 'coolaj86@gmail.com'
agree_tos: true
community_member: false
vhost: /srv/www/:hostname

View File

@ -179,9 +179,6 @@ 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 '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 '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"
fi

35
lib/ago-test.js Normal file
View File

@ -0,0 +1,35 @@
'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 Normal file
View File

@ -0,0 +1,50 @@
;(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));

View File

@ -1,6 +1,7 @@
'use strict';
var Devices = module.exports;
// TODO enumerate store's keys and device's keys for documentation
Devices.addPort = function (store, serverport, newDevice) {
// TODO make special
return Devices.add(store, serverport, newDevice, true);
@ -14,6 +15,7 @@ Devices.add = function (store, servername, newDevice, isPort) {
if (!store._domains) { store._domains = {}; }
if (!store._domains[servername]) { store._domains[servername] = []; }
store._domains[servername].push(newDevice);
Devices.touch(store, servername);
// add device
// TODO only use a device id
@ -95,7 +97,6 @@ Devices.list = function (store, servername) {
// aliases have ._primary which is the name of the original
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
// first so the one with the biggest natural match with be found first.
var deviceList = [];
@ -104,10 +105,7 @@ Devices.list = function (store, servername) {
}).sort(function (a, b) {
return b.length - a.length;
}).some(function (pattern) {
// '.example.com' = '*.example.com'.split(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)) {
console.log('[Devices.list] "'+servername+'" matches "'+pattern+'"');
deviceList = store._domains[pattern];
@ -130,7 +128,11 @@ Devices.active = function (store, id) {
};
*/
Devices.exist = function (store, servername) {
return !!(Devices.list(store, servername).length);
if (Devices.list(store, servername).length) {
Devices.touch(store, servername);
return true;
}
return false;
};
Devices.next = function (store, servername) {
var devices = Devices.list(store, servername);
@ -142,5 +144,20 @@ Devices.next = function (store, servername) {
device = devices[devices._index || 0];
devices._index = (devices._index || 0) + 1;
if (device) { Devices.touch(store, servername); }
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;
};

View File

@ -1,3 +0,0 @@
commercial
Copyright ppl 2018

View File

@ -1 +0,0 @@
_apis

View File

@ -1 +0,0 @@
oauth3.org

View File

@ -1 +0,0 @@
../assets/oauth3.org/_apis/oauth3.org

View File

@ -1,11 +0,0 @@
{ "terms_of_service": ":hostname/tos/"
, "api_host": "api.:hostname"
, "pair_request": {
"method": "POST"
, "pathname": "api/telebit.cloud/pair_request"
}
, "tunnel": {
"method": "wss"
, "pathname": ""
}
}

View File

@ -1,90 +0,0 @@
<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 +0,0 @@
Subproject commit 8e2e09f5823ae919c615c9c3b21114e01096b1ee

View File

@ -1,4 +0,0 @@
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

View File

@ -1,86 +0,0 @@
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; }`.

View File

@ -1,12 +0,0 @@
'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" };
};

View File

@ -1,357 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=900">
<title>Telebit&trade; 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">&nbsp;
</div>
<div class="demo-terminal-output">
Forwarding https://jondoe.telebit.io =&gt; 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 =&gt; 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 =&gt; localhost:22</code>
<code class="quickstart-output">Forwarding ssh+https (openssl proxy) =&gt; 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 =&gt; 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>

View File

@ -1,215 +0,0 @@
/*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);
}
}());

View File

@ -1,65 +0,0 @@
(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;
}
*/
});
}());

View File

@ -1,28 +0,0 @@
<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>

View File

@ -1,228 +0,0 @@
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;
}

View File

@ -1,161 +0,0 @@
<!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&trade; 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&trade; 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>

View File

@ -1,180 +0,0 @@
(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();
});
});
}());

View File

@ -1,18 +0,0 @@
<!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>

View File

@ -1,58 +0,0 @@
#!/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

View File

@ -1 +0,0 @@
v0.0.5

View File

@ -1,102 +0,0 @@
<!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] &lt;remote&gt; &lt;local&gt;</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 &lt;string&gt;</kbd> spoof SNI
(to disable use IP as &lt;remote&gt; and do not use this option)</li>
</ul>
<h3>Arguments</h3>
<ul>
<li><kbd>&lt;remote&gt;</kbd> the servername and port of the tls-enabled server (default port is 443)</li>
<li><kbd>&lt;local&gt;</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
&gt; [listening] example.com:443 &lt;= localhost:3000</code></pre>
<pre><code>$ telnet localhost 3000</code></pre>
<h3>stdin/stdout</h3>
<pre><code>$ sclient whatever.com -
&gt; (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>&gt; GET / HTTP/1.1
&gt; Host: whatever.com
&gt; Connection: close
&gt;
</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>

View File

@ -1,16 +0,0 @@
<!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>

View File

@ -1,4 +0,0 @@
<?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>

Before

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -1,4 +0,0 @@
<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>

Before

Width:  |  Height:  |  Size: 302 B

View File

@ -1,4 +0,0 @@
<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>

Before

Width:  |  Height:  |  Size: 261 B

View File

@ -1,4 +0,0 @@
<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>

Before

Width:  |  Height:  |  Size: 200 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 954 B

View File

@ -1,4 +0,0 @@
<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>

Before

Width:  |  Height:  |  Size: 959 B

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 5.6 KiB

View File

@ -1,9 +0,0 @@
<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>

Before

Width:  |  Height:  |  Size: 436 B

View File

@ -1,4 +0,0 @@
<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>

Before

Width:  |  Height:  |  Size: 375 B

View File

@ -1,6 +0,0 @@
<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>

Before

Width:  |  Height:  |  Size: 982 B

View File

@ -1,8 +0,0 @@
<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>

Before

Width:  |  Height:  |  Size: 3.2 KiB

View File

@ -1 +0,0 @@
<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>

Before

Width:  |  Height:  |  Size: 4.4 KiB

View File

@ -1,18 +0,0 @@
<?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% -->

Before

Width:  |  Height:  |  Size: 861 B

View File

@ -1,6 +0,0 @@
<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>

Before

Width:  |  Height:  |  Size: 428 B

View File

@ -1,149 +0,0 @@
(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;
});
})();

View File

@ -1,47 +0,0 @@
.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;
}
}

View File

@ -1,433 +0,0 @@
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;
}

View File

@ -1,84 +0,0 @@
/*
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;
}

View File

@ -1,47 +0,0 @@
<!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>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

View File

@ -1,47 +0,0 @@
'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('');

View File

@ -1,315 +0,0 @@
'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 });
});
};

File diff suppressed because it is too large Load Diff

View File

@ -1,17 +0,0 @@
{
"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"
}
}

View File

@ -1,12 +0,0 @@
'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(', '));

View File

@ -10,7 +10,7 @@ function noSniCallback(tag) {
var err = new Error("[noSniCallback] no handler set for '" + tag + "':'" + servername + "'");
console.error(err.message);
cb(new Error(err));
}
};
}
module.exports.create = function (state) {
@ -72,38 +72,44 @@ module.exports.create = function (state) {
state.tlsInvalidSniServer.on('tlsClientError', function () {
console.error('tlsClientError InvalidSniServer');
});
state.httpsInvalid = function (servername, socket) {
state.createHttpInvalid = function (opts) {
return http.createServer(function (req, res) {
if (!opts.servername) {
res.statusCode = 422;
res.setHeader('Content-Type', 'text/plain; charset=utf-8');
res.end(
"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"
+ "\t1. You really love Windows XP and you just won't let go of Internet Explorer 6\n"
+ "\t2. You're writing a bot and you forgot to set the servername parameter\n"
);
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(
"<h1>Oops!</h1>"
+ "<p>It looks like '" + encodeURIComponent(opts.servername) + "' isn't connected right now.</p>"
+ "<p><small>Last seen: " + opts.ago + "</small></p>"
+ "<p><small>Error: 502 Bad Gateway</small></p>"
);
});
};
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', servername);
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');
// things get a little messed up here
var httpInvalidSniServer = http.createServer(function (req, res) {
if (!servername) {
res.statusCode = 422;
res.end(
"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"
+ "\t1. You really love Windows XP and you just won't let go of Internet Explorer 6\n"
+ "\t2. You're writing a bot and you forgot to set the servername parameter\n"
);
return;
}
res.end(
"You came in hot looking for '" + servername + "' and, granted, the IP address for that domain"
+ " must be pointing here (or else how could you be here?), nevertheless either it's not registered"
+ " in the internal system at all (which Seth says isn't even a thing) or there is no device"
+ " 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);
// 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 () {
console.error('tlsClientError InvalidSniServer httpsInvalid');

View File

@ -68,7 +68,6 @@ module.exports.create = function (state) {
});
if (initToken) {
console.log('[wss.onConnection] token provided in http headers');
return Server.addToken(state, srv, initToken).then(function () {
Server.init(state, srv);
}).catch(function (err) {

View File

@ -172,6 +172,7 @@ var Server = {
, _initSocketHandlers: function (state, srv) {
function refreshTimeout() {
srv.lastActivity = Date.now();
Devices.touchDevice(state.deviceLists, srv);
}
function checkTimeout() {

View File

@ -2,6 +2,16 @@
var sni = require('sni');
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) {
var Devices = state.Devices;
@ -27,6 +37,16 @@ module.exports.createTcpConnectionHandler = function (state) {
var str;
var m;
if (!firstChunk) {
try {
conn.end();
} catch(e) {
console.error("[lib/unwrap-tls.js] Error:", e);
conn.destroy();
}
return;
}
//conn.pause();
conn.unshift(firstChunk);
@ -38,8 +58,15 @@ module.exports.createTcpConnectionHandler = function (state) {
// defer after return (instead of being in many places)
function deferData(fn) {
if (fn) {
if ('httpsInvalid' === fn) {
state[fn]({
servername: servername
, ago: fromUptime(Devices.lastSeen(state.deviceLists, servername))
}, conn);
} else if (fn) {
state[fn](servername, conn);
} else {
console.error("[SANITY ERROR] '" + fn + "' doesn't have a state handler");
}
/*
process.nextTick(function () {
@ -48,33 +75,81 @@ module.exports.createTcpConnectionHandler = function (state) {
*/
}
function tryTls() {
var vhost;
var httpOutcomes = {
missingServername: function () {
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 (!servername) {
if (!Devices.exist(state.deviceLists, 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"); }
deferData('httpsInvalid');
return;
}
if (!state.servernames.length) {
, requiresSetup: function () {
console.info("[Setup] https => admin => setup => (needs bogus tls certs to start?)");
deferData('httpsSetupServer');
return;
}
if (-1 !== state.servernames.indexOf(servername)) {
, isInternal: function () {
if (state.debug) { console.log("[Admin]", servername); }
deferData('httpsTunnel');
return;
}
if (state.config.nowww && /^www\./i.test(servername)) {
console.log("TODO: use www bare redirect");
, isVhost: function (vhost) {
if (state.debug) { console.log("[tcp] [vhost]", state.config.vhost, "=>", vhost); }
deferData('httpsVhost');
}
function run() {
var nextDevice = Devices.next(state.deviceLists, servername);
, assumeExternal: function () {
var nextDevice = Devices.next(state.deviceLists, servername);
if (!nextDevice) {
if (state.debug) { console.log("No devices match the given servername"); }
deferData('httpsInvalid');
@ -82,27 +157,33 @@ module.exports.createTcpConnectionHandler = function (state) {
}
if (state.debug) { console.log("pipeWs(servername, service, deviceLists['" + servername + "'], socket)"); }
deferData();
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
//if (!state.validHosts) { state.validHosts = {}; }
if (state.config.vhost) {
vhost = state.config.vhost.replace(/:hostname/, (servername||'reallydoesntexist'));
if (state.debug) { console.log("[tcp] [vhost]", state.config.vhost, "=>", vhost); }
//state.httpsVhost(servername, conn);
//return;
vhost = state.config.vhost.replace(/:hostname/, servername);
require('fs').readdir(vhost, function (err, nodes) {
if (state.debug && err) { console.log("VHOST error", err); }
if (err || !nodes) { run(); return; }
//if (nodes) { deferData('httpsVhost'); return; }
deferData('httpsVhost');
if (err || !nodes) { outcomes.assumeExternal(); return; }
outcomes.isVhost(vhost);
});
return;
}
run();
outcomes.assumeExternal();
}
// https://github.com/mscdex/httpolyglot/issues/3#issuecomment-173680155
@ -111,40 +192,19 @@ module.exports.createTcpConnectionHandler = function (state) {
service = 'https';
servername = (sni(firstChunk)||'').toLowerCase().trim();
if (state.debug) { console.log("[tcp] tls hello from '" + servername + "'"); }
tryTls();
handleConnection(tlsOutcomes);
return;
}
if (firstChunk[0] > 32 && firstChunk[0] < 127) {
// (probably) HTTP
str = firstChunk.toString();
m = str.match(/(?:^|[\r\n])Host: ([^\r\n]+)[\r\n]*/im);
servername = (m && m[1].toLowerCase() || '').split(':')[0];
if (state.debug) { console.log("[tcp] http hostname '" + servername + "'"); }
if (/HTTP\//i.test(str)) {
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');
handleConnection(httpOutcomes);
return;
}
}

View File

@ -37,32 +37,20 @@
},
"homepage": "https://git.coolaj86.com/coolaj86/telebit-relay.js",
"dependencies": {
"@coolaj86/urequest": "^1.3.5",
"body-parser": "^1.18.3",
"bluebird": "^3.5.1",
"cluster-store": "^2.0.8",
"connect-cors": "^0.5.6",
"escape-html": "^1.0.3",
"express": "^4.16.3",
"finalhandler": "^1.1.1",
"greenlock": "^2.2.4",
"human-readable-ids": "^1.0.4",
"js-yaml": "^3.11.0",
"jsonwebtoken": "^8.3.0",
"jwk-to-pem": "^2.0.0",
"mkdirp": "^0.5.1",
"nowww": "^1.2.1",
"proxy-packer": "^2.0.0",
"recase": "^1.0.4",
"redirect-https": "^1.1.5",
"request": "^2.87.0",
"safe-replace": "^1.0.3",
"serve-static": "^1.13.2",
"sni": "^1.0.0",
"ws": "^5.1.1"
},
"trulyOptionalDependencies": {
"bluebird": "^3.5.1"
},
"engineStrict": true,
"engines": {
"node": "10.2.1"

24
snap/snapcraft.yaml Normal file
View File

@ -0,0 +1,24 @@
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