Compare commits

...

1 Commits

Author SHA1 Message Date
0e8f78444b
initial commit 2020-04-21 17:57:25 -07:00
10 changed files with 2340 additions and 0 deletions

0
.cargo-ok Normal file
View File

19
.eslintrc.js Normal file
View File

@ -0,0 +1,19 @@
module.exports = {
env: {
browser: true,
es6: true,
},
extends: [
'airbnb-base',
],
globals: {
Atomics: 'readonly',
SharedArrayBuffer: 'readonly',
},
parserOptions: {
ecmaVersion: 2018,
sourceType: 'module',
},
rules: {
},
};

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
wrangler.toml
node_modules

7
.prettierrc Normal file
View File

@ -0,0 +1,7 @@
{
"singleQuote": true,
"semi": false,
"trailingComma": "all",
"tabWidth": 2,
"printWidth": 80
}

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020 Emil Lerch
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

27
README.md Normal file
View File

@ -0,0 +1,27 @@
# Troopmaster Cloudflare worker
Allows for a site to use Troopmaster without having to redirect to the site
and lose Google-foo.
The worker does a few things:
* Does a server-side rather than client-side-after-the-fact https redirection
* On a home page load, will insert a tracking image so that troopmaster
cookies can be established for login
* On a home page load, will insert the home page content and remove the
Javascript on the page that tries to get it after the fact
If login gets "broken", its because the origin HTML has changed and the regexs
need adjustment. There are http headers that tell you if this is happening.
Broken here would mean that clicking login forces you through the multiple
drop downs to select site.
This section should be in your wrangler.toml file. Replace with the correct
values of course.
```toml
[vars]
TMSITEID = "203232"
TMSITENAME = "Troop618"
```

430
home-after.html Normal file
View File

@ -0,0 +1,430 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title id="title"></title>
<input id="titleval" name="titleval" type="hidden" value="" />
<link href="/WebsiteContent/css/bootstrap.min.css" rel="stylesheet" />
<!-- Google Fonts -->
<link href='http://fonts.googleapis.com/css?family=Raleway:400,300,500,700' rel='stylesheet' type='text/css'>
<link href='http://fonts.googleapis.com/css?family=Cinzel+Decorative' rel='stylesheet' type='text/css'>
<!-- Wrapper and Supporting Styles -->
<link href="/WebsiteContent/owl-carousel/owl.carousel.css" rel="stylesheet" type="text/css">
<link href="/WebsiteContent/css/sidewaze_columns.css" rel="stylesheet" type="text/css">
<link href="/WebsiteContent/css/sidewaze_base.css" rel="stylesheet" type="text/css">
<link href="/WebsiteContent/css/colors/sidewaze_base_color.css" rel="stylesheet" />
<link href="/WebsiteContent/blueimp/Gallery/blueimp-gallery.min.css" rel="stylesheet" />
<link href="/WebsiteContent/blueimp/Gallery/bootstrap-image-gallery.min.css" rel="stylesheet" />
<script src="/WebsiteContent/js/modernizr-2.6.2-respond-1.1.0.min.js"></script>
<script src="/Scripts/jquery-2.1.4.min.js"></script>
<script src="/Scripts/jquery.validate.min.js"></script>
<script src="/Scripts/jquery.validate.unobtrusive.min.js"></script>
<script src="/Scripts/jquery.unobtrusive-ajax.js"></script>
<script src="/WebsiteContent/js/windows-fix.js"></script>
<script src="/Scripts/TSI/bootbox.js"></script>
<script src="/WebsiteContent/js/jquery.sidewaze_base-UNPACKED.js"></script>
<script src="/WebsiteContent/blueimp/Gallery/jquery.blueimp-gallery.min.js"></script>
<script src="/WebsiteContent/blueimp/Gallery/bootstrap-image-gallery.min.js"></script>
<style>
.gray1{
background-color:lightgray;
}
.modal-padding {
padding: 10px !important;
}
.modal { /*added for modal over modal the second modal once closed prevented scolling on the first*/
overflow-y:auto !important;
}
.bootbox.modal {
z-index: 99999 !important;
}
#loading-indicator {
position: absolute;
left: 50%;
top: 50%;
width: 100px;
height: 100px;
margin-left: -50px;
margin-top: -50px;
border: 0;
z-index: 999999;
}
</style>
</head>
<body>
<link href="/WebsiteContent/css/colors/green-gray.css" rel="stylesheet" />
<div class="nav-col">
<nav id="nav" class="clearfix" role="navigation">
<!-- ____________ User Wrapper for login and sign up links _______________ -->
<div class="user-wrapper">
<p>
<a href="#" onclick="PickSite()"> <i class=" fa fa-hacker-news"></i>&nbsp;&nbsp;New Site</a>
<span class="v-divider"></span>
<a href="#" onclick="Login()"> <i class=" fa fa-sign-in"></i>&nbsp;&nbsp;Login</a>
<span id="customize">
<span class="v-divider"></span>
<a href="#" onclick="Customize()"> <i class="fa fa-pencil"></i>&nbsp;&nbsp;Customize</a>
</span>
</p>
</div>
<!-- ____________ Primary Menu _______________ -->
<!-- TO KEEP OPEN WHEN ON THAT SECTION add class "active" to the a tag of the parent li
(the one with the .has-children class on it) <li class="has-children"><a href="#" class="active" ... -->
<ul class="primary-nav">
<li class="active"><a href="#" onclick="GetContent('menu','2032329f2e05cfb2374b1d95d13e4b925d6f39'); return false;">Home</a></li>
<li><a href="#" onclick="GetContent('menu','203232a7429b3a8bc04670a694a583972529db'); return false;">Troop Activities</a></li>
<li><a href="#" onclick="GetContent('menu','2032321a2b894dfc204e8f848007bc44b63d38'); return false;">Merit Badges and More Scouting</a></li>
<li class="has-children">
<a href="#">Resources</a>
<ul>
<li><a href="#" onclick="GetContent('submenu','20323230d830184763466e9baa4021381ce236'); return false;">Trail to First Class at Home</a></li>
<li><a href="#" onclick="GetContent('submenu','20323253b43f96a9264585ad90903cd6edb684'); return false;"><span class="fa fa-lock"> </span> Resources</a></li>
<li><a href="#" onclick="GetContent('submenu','20323204e2ae44cc61437d904146bd4f18a2f0'); return false;">Med Forms</a></li>
</ul>
</li>
<li class="has-children">
<a href="#">Campouts</a>
<ul>
<li><a href="#" onclick="GetContent('submenu','20323229b98403302948d7b71212f82f97fa19'); return false;">2020-03 Scout Skills</a></li>
<li><a href="#" onclick="GetContent('submenu','203232e67e3b5f454a47a09207a6f134251152'); return false;">2020-02 Snow Caving</a></li>
<li><a href="#" onclick="GetContent('submenu','203232180ad8213f524197b6ff1fde74bf6be5'); return false;">2020-02 Nanitch Lodge</a></li>
<li><a href="#" onclick="GetContent('submenu','20323210697163dd574cc9970e965d4b8c32d6'); return false;">2020-01 Butte Creek</a></li>
<li><a href="#" onclick="GetContent('submenu','203232f6b0cabc9517445fa8cc1ed0c033f6b6'); return false;">2019-11 Camp Cooper Archery</a></li>
<li><a href="#" onclick="GetContent('submenu','203232e8cc8e8dc7a04737aede0b72608fab32'); return false;">2019-10 Webelos Woods</a></li>
<li><a href="#" onclick="GetContent('submenu','203232731484be6a1d4e8a87e0977123b2f762'); return false;">2019-09 June Lake Backpacking</a></li>
<li><a href="#" onclick="GetContent('submenu','203232454a4aa2780d4f0bb030ff96ed89f6ad'); return false;">2019-07 Philmont</a></li>
<li><a href="#" onclick="GetContent('submenu','20323200179ec61ba043bf91714c01d76fb212'); return false;">2019-07 Summer Camp Baldwin</a></li>
<li><a href="#" onclick="GetContent('submenu','20323210d23ffc3ee647a3a2ccc509bb762386'); return false;">2019-06 Potato Butte</a></li>
<li><a href="#" onclick="GetContent('submenu','203232768285dd93294d6bbe44bee218515871'); return false;">2019-05 Camporee</a></li>
<li><a href="#" onclick="GetContent('submenu','2032329b23e7749cdd48ff853539525503f189'); return false;">2019-04 Family Camp</a></li>
</ul>
</li>
<li><a href="#" onclick="GetContent('menu','2032326d382b0df135478daf4895616b19c4a9'); return false;">Spring Plant Sale</a></li>
<li class="has-children">
<a href="#">Eagle Scouts</a>
<ul>
<li><a href="#" onclick="GetContent('submenu','20323213ca3d799c0644148616dd6efd5263d0'); return false;">Troop618 Eagle Scouts</a></li>
<li><a href="#" onclick="GetContent('submenu','203232e4f0adaf47d84f4f891500d4d8ca2a96'); return false;">Timothy Rose Villa</a></li>
<li><a href="#" onclick="GetContent('submenu','2032327166e40fa9d4432da996c8443591944b'); return false;">Mason Cat Adoption Team</a></li>
<li><a href="#" onclick="GetContent('submenu','203232086492b1372644d88d08580131f217a6'); return false;">Dhruva Dog Shelters</a></li>
<li><a href="#" onclick="GetContent('submenu','20323225cc3b98656949a6b9ae161b9275aceb'); return false;">Benjamin Church Shed</a></li>
<li><a href="#" onclick="GetContent('submenu','2032327fe01b5c2ebe40669f75bc75696c0d73'); return false;">Matt Valley Catholic Outdoor Classroom Completion</a></li>
</ul>
</li>
<li><a href="#" onclick="GetContent('menu','2032326ff8a8c377124b6ea5f695574542050c'); return false;">Crew 618</a></li>
</ul>
</nav>
</div>
<script>
$(document).ready(function () {
$('#customize').hide();
var cookie = getCookie("TroopMasterWebCustomize");
var siteid = getCookie("TroopMasterWebSiteID");
if (cookie === 'yes' + siteid )
{
$('#customize').show();
}
});
function PickSite() {
window.location.href = "../Login/PickSite";
}
function getCookie(cname) {
var name = cname + "=";
var ca = document.cookie.split(';');
for (var i = 0; i < ca.length; i++) {
var c = ca[i];
while (c.charAt(0) == ' ') c = c.substring(1);
//if (c.indexOf(name) != -1) return c.substring(name.length, c.length); //Changed do to error in Safari on MAC
if (c.indexOf(name) != -1) {
var equalindex = c.indexOf('=');
var value = c.substring(equalindex + 1);
return value
}
}
return "";
}
</script>
<style>
.logotext {
/* Safari */
-webkit-transform: rotate(-90deg);
/* Firefox */
-moz-transform: rotate(-90deg);
/* IE */
-ms-transform: rotate(-90deg);
/* Opera */
-o-transform: rotate(-90deg);
/* Internet Explorer */
filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=3);
/*transform-origin:bottom right;*/
position: relative;
top: 100px;
right: 5px;
}
.logonumber{
font-family: 'Raleway';
font-size:40px;
font-weight:100;
}
.troop{
font-family: 'Raleway';
font-size:40px;
font-weight:700;
}
</style>
<div class="logo-col" >
<a href="#" class="toggle-menu"><i class="fa fa-bars"></i></a>
<br />
<div class="logo-wrapper">
<div class="logotext"><table style="vertical-align:middle"><tr style="vertical-align:middle"><td><span class="troop">Troop</span></td><td><span class="logonumber">618</span></td></tr></table></div>
</div>
<div class="social-wrapper">
<br /><br /><br /><br /><br /><br />
<ul>
</ul>
</div>
</div>
<script>
$(document).ready(function () {
$('#titleval').val("Troop 618")
$(document).attr("title", "Troop 618 - Home");
});
</script>
<img src="/images/ajax-loading.gif" id="loading-indicator" style="display:none" />
<div class="content-col" id="page">
<div class="inner-content">
<div id="pagecontent">
</div>
<script>
$(document).ready(function () {
var url = window.location.href;
if (url.indexOf('https:') === -1 && url.indexOf('localhost') === -1 && url.indexOf('tmtest.me') === -1) {
url = url.replace('http://', 'https://');
url = url.replace('http://', 'https://');
window.location.assign(url)
}
var pagename = '';
if (pagename != "") {
$('a').each(function () {
var atext = $(this).html();
atext = atext.toUpperCase();
if (atext.indexOf(pagename) != -1)
{
$(this).click()
return;
}
})
//$('a:contains(' + pagename + ')').trigger('click');
//return;
}
else
{
var data = {
type: "",
id: "",
password: "",
home: true,
};
$.ajax({
cache: false,
url: '/WebSite/GetContent',
type: 'POST',
contentType: 'application/json',
data: JSON.stringify(data),
success: function (resp) {
$('#pagecontent').html(resp);
}
});
}
});
</script>
</div>
</div>
<footer class="footer footer-col clearfix">
<div class="footer-content-1">
<div class="inner-content">
<p style="margin:14.66px 0px;">&nbsp; %ANNOUNCEMENTS%</p>
</div>
</div>
<hr class="divider">
<div class="footer-content-2">
<div class="inner-content">
<p style="background-color:transparent;color:#62554e;font-family:'font-size:13px;font-style:normal;font-variant:normal;font-weight:400;letter-spacing:normal;line-height:20.8px;margin-bottom:0px;margin-left:0px;margin-right:0px;margin-top:0px;orphans:2;padding-bottom:0px;padding-left:0px;padding-right:0px;padding-top:0px;text-align:left;text-decoration:none;text-indent:0px;text-transform:none;-webkit-text-stroke-width:0px;white-space:normal;word-spacing:0px;font-size:12px;font-style:normal;font-variant:normal;font-weight:400;letter-spacing:normal;margin-bottom:12px;margin-left:0px;margin-right:0px;margin-top:0px;orphans:2;text-align:left;text-decoration:none;text-indent:0px;text-transform:none;-webkit-text-stroke-width:0px;white-space:normal;word-spacing:0px;"><br /></p>
</div>
</div>
<hr class="divider">
</footer>
<link href="/WebsiteContent/css/nofooter.css" rel="stylesheet" />
<script>
$(document).ready(function () {
$('.footer').hide();
});
</script>
<!-- _________ FAUX COLUMNS ________ -->
<div class="faux-col faux-nav-col">
</div>
<div class="faux-col faux-logo-col">
</div>
<div class="faux-col faux-content-col">
</div>
<div class="faux-col faux-footer-col">
</div>
<!--to top -->
<a id="go-to-top" title="up"> <i class="fa fa-arrow-circle-o-up"></i> </a>
<!-- this ends the site content and begins the modals for sign up and login -->
<script src="/WebsiteContent/js/bootstrap.min.js"></script>
<!-- Responsive Carousel LEARN IT HERE: http://www.owlgraphic.com/owlcarousel/demos/custom.html-->
<script src="/WebsiteContent/owl-carousel/owl.carousel.min.js"></script>
<!-- Way Better Bootstrap Modals INSTRUCTIONS: https://github.com/jschr/bootstrap-modal/ -->
<!-- DEMO:: http://jschr.github.io/bootstrap-modal/bs3.html -->
<script>
function Login() {
location.href = '/Login/Index?website';
}
function Customize() {
window.open('/Website/Customize', '_blank');
}
function GetContent(type, id, password) {
var data = {
cache:false,
type: type,
id: id,
password: password,
};
$.ajax({
cache: false,
url: '/WebSite/GetContent',
type: 'POST',
contentType: 'application/json',
data: JSON.stringify(data),
success: function (resp) {
$('#pagecontent').html(resp);
}
});
}
</script>
<div class="modal fade" id="edit-modal" data-keyboard="false" data-backdrop="static" tabindex="-1" role="dialog" aria-labelledby="modal-container-label" aria-hidden="true" style="z-index:1051;">
<div class="modal-dialog modal-lg" >
<div class="modal-content" id="edit-modal-body">
</div>
</div>
</div>
<div class="modal fade" id="edit-page-modal" data-keyboard="false" data-backdrop="static" tabindex="-1" role="dialog" aria-labelledby="modal-container-label" aria-hidden="true" style="z-index:1051;">
<div class="modal-dialog modal-lg">
<div class="modal-content" id="edit-page-modal-body">
</div>
</div>
</div>
</body>
</html>

148
index.js Normal file
View File

@ -0,0 +1,148 @@
async function modifyBody(pageContentTextPromise, originalResponse) {
// If you're wondering, yeah, this is incredibly brittle. I'm expecting
// that troopmaster basically never changes. We're looking for the first
// instance of "go get this content", which occurs during a document ready
// only if we are on the home page
const initialloadJs = /\$.ajax\([\s\S]*?WebSite\/GetContent[\s\S]*?}\);/m;
// This one is easier. Look for the pageContent div
const pageContentDiv = /<div id="pagecontent">[\s\S]*?<\/div>/m;
// ...and the last one, we need to change the location for Login
const login = /location.href = '\/Login\/Index\?website'/m;
// const responseBodyText = originalResponse.body.text;
let responseBodyText = await originalResponse.text();
const extraheaders = {
'X-worker-modification-status': 'original',
'X-worker-version': '1.0',
};
if (responseBodyText.match(pageContentDiv)
&& responseBodyText.match(initialloadJs)
&& responseBodyText.match(login)) {
// only replace things if all our brittleness is as expected. There is
// only a performance and probable SEO hit if we don't enter this block
const pageContentResponse = await pageContentTextPromise;
if (pageContentResponse.ok) {
// html comment added here so we can see that we've entered this block.
// Also gives end user some idea were to go if something goes horribly
// wrong
// We need to make a request back to origin from the client so the
// right cookies are setup over there just in case the client navigates
// to login...wow, Troopmaster...
//
// So, the only way we can do that effectively is to basically use a
// tracking pixel. The only endpoint that actually works for this, though,
// is the /mysite/sitename endpoint, which returns HTML. So, the image
// will be big, ugly, and contain two redirects. But...it's behind the
// scenes and doesn't show anything visible, so, you know, YOLO...
responseBodyText = responseBodyText.replace(pageContentDiv,
`<div id="pagecontent">
<!--edge side include via cf worker-->
${await pageContentResponse.text()}
<!--invisible image to establish tm cookie-->
<img src="https://tmweb.troopmaster.com/mysite/${TMSITENAME}" height="1" width="1" style="opacity:0" >
<!--end invisible image to establish tm cookie-->
<!--End edge side include via cf worker-->
</div>`);
responseBodyText = responseBodyText.replace(initialloadJs, '');
responseBodyText = responseBodyText.replace(login,
`location.href = 'https://tmweb.troopmaster.com/Login/Index?website'`); // eslint-disable-line no-undef
extraheaders['X-worker-modification-status'] = 'modified';
extraheaders['X-worker-modification-getcontent-ok'] = 'true';
} else {
extraheaders['X-worker-modification-getcontent-ok'] = 'false';
}
} else {
extraheaders['X-worker-modification-div'] = (!!responseBodyText.match(pageContentDiv)).toString();
extraheaders['X-worker-modification-load'] = (!!responseBodyText.match(initialloadJs)).toString();
extraheaders['X-worker-modification-location'] = (!!responseBodyText.match(login)).toString();
}
const response = new Response(responseBodyText, {
status: originalResponse.status,
statusText: originalResponse.statusText,
headers: originalResponse.headers,
});
Object.keys(extraheaders).forEach((k) => response.headers.set(k, extraheaders[k]));
return response;
}
async function homePage(origin) {
const data = {
type: '',
id: '',
password: '',
home: true,
};
return fetch(`${origin}/WebSite/GetContent`, {
method: 'POST',
body: JSON.stringify(data),
headers: {
Accept: '*/*',
'User-Agent': 'troop618worker 1.0',
Cookie: `TroopMasterWebSiteID=${TMSITEID}`, // eslint-disable-line no-undef
'Content-Type': 'application/json',
},
});
}
/**
* Respond with whatever origin gives us
* @param {Request} request
*/
async function handleRequest(request) {
let response;
try {
// curl -b TroopMasterWebSiteID=203232 -v https://tmweb.troopmaster.com/Website/Home
const origin = 'https://tmweb.troopmaster.com/Website/Home';
const originHost = 'https://tmweb.troopmaster.com';
const requestUrl = new URL(request.url)
const requestPath = requestUrl.pathname;
let originUrl = `${originHost}${requestPath}`;
let home = null;
if (requestUrl.protocol === 'http:') {
// The front-end has javascript to refresh the page after the whole
// thing has been rendered, resulting in an ugly flash. We'll do the
// redirect server (well, edge) side instead. Note this breaks debugging,
// which we can fix later because deploys are so fast that we can
// just YOLO our changes
return Response.redirect(request.url.replace(/^http/, 'https'), 301);
}
if (requestPath === '/' && request.method === 'GET') {
originUrl = origin;
home = homePage(originHost);
}
response = await fetch(originUrl, {
method: request.method,
body: request.body,
headers: {
'User-Agent': 'troop618worker 1.0',
Cookie: `TroopMasterWebSiteID=${TMSITEID}`, // eslint-disable-line no-undef
'Content-Type': request.headers.get('Content-Type') || 'text/html',
},
});
if (home) {
response = await modifyBody(home, response);
}
} catch (err) {
// Without event.waitUntil(), our fetch() to our logging service may
// or may not complete.
// event.waitUntil(postLog(err));
const stack = JSON.stringify(err.stack) || err;
// Copy the response and initialize body to the stack trace
response = new Response(stack, response);
// Shove our rewritten URL into a header to find out what it was.
response.headers.set('X-Debug-stack', stack);
response.headers.set('X-Debug-err', err);
}
return response;
}
// This is "browser", but it's running in a V8 isolate, so it's really
// neither browser nor node. This is the hook into the CF Worker ecosystem,
// so we need to have this code and is safe to disable eslint for this line
addEventListener('fetch', (event) => { // eslint-disable-line no-restricted-globals
event.respondWith(handleRequest(event.request));
});

1666
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

20
package.json Normal file
View File

@ -0,0 +1,20 @@
{
"private": true,
"name": "worker",
"version": "1.0.0",
"description": "A template for kick starting a Cloudflare Workers project",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"format": "prettier --write '**/*.{js,css,json,md}'"
},
"author": "root",
"license": "MIT",
"devDependencies": {
"eslint": "^6.8.0",
"eslint-config-airbnb-base": "^14.1.0",
"eslint-plugin-import": "^2.20.2",
"prettier": "^1.18.2"
},
"dependencies": {}
}