|
"use latest"; |
|
/* |
|
Copyright (c) 2017 Maxwell Bloch, All rights reserved. |
|
This Source Code Form is subject to the terms of the Mozilla Public |
|
License, v. 2.0. If a copy of the MPL was not distributed with this |
|
file, You can obtain one at http://mozilla.org/MPL/2.0/. |
|
*/ |
|
import express from "express"; |
|
import { fromExpress } from "webtask-tools"; |
|
import bodyParser from "body-parser"; |
|
import request from "request"; |
|
import cookieParser from "cookie-parser"; |
|
|
|
const accessCookie = { name: "Access", options: { httpOnly: true, signed: true, secure: true } }; |
|
const DEBUG = false; |
|
const Views = { |
|
"index": (model) => ` |
|
<p class="row"> |
|
Hello there |
|
</p> |
|
${model.linodes ? |
|
(function(linodes) { |
|
return ` |
|
<table class="table row"> |
|
<thead> |
|
<tr> |
|
<th>Group</th> |
|
<th>Hypervisor</th> |
|
<th>Label</th> |
|
<th>Status</th> |
|
</tr> |
|
</thead> |
|
<tbody> |
|
${linodes.map(linode => ` |
|
<tr> |
|
<td>${linode.group}</td> |
|
<td>${linode.hypervisor}</td> |
|
<td>${linode.label}</td> |
|
<td>${linode.status}</td> |
|
</tr> |
|
`).join(" ")} |
|
</tbody> |
|
</table>`; |
|
})(model.linodes) |
|
: ""} |
|
`, |
|
"error": (model) => ` |
|
<div class="alert alert-danger" role="alert"> |
|
<p> |
|
<strong>An error has occurred</strong> |
|
</p> |
|
<p> |
|
${model.message} |
|
</p> |
|
</div> |
|
` |
|
} |
|
|
|
var app = express(); |
|
app.use(function (req, res, next) { |
|
let ctx = req.webtaskContext; |
|
cookieParser(ctx.secrets.ACCESS_KEY)(req, res, next); |
|
}); |
|
app.use(function (req, res, next) { |
|
res.setHeader("Cache-Control", "no-cache, must-revalidate"); |
|
next(); |
|
}); |
|
app.use(function decodeAccessCookie(req, res, next) { |
|
let ctx = req.webtaskContext; |
|
if (req.accessToken) { |
|
next(); return; |
|
} |
|
if (req.signedCookies == null |
|
|| req.signedCookies.Access == null |
|
|| req.signedCookies.Access.access == null) { |
|
res.clearCookie(accessCookie.name); |
|
next(); return; |
|
} |
|
req.accessToken = req.signedCookies.Access.access; |
|
next(); |
|
}); |
|
|
|
app.use(bodyParser.json()); |
|
|
|
app.get("/login", (req,res) => { |
|
var ctx = req.webtaskContext; |
|
var clientId = ctx.secrets.CLIENT_ID; |
|
var clientSecret = ctx.secrets.CLIENT_SECRET; |
|
var code = req.query.code; |
|
DEBUG && console.log(req.query); |
|
|
|
request.post({ |
|
url: "https://login.linode.com/oauth/token", |
|
form: { client_id: clientId, client_secret: clientSecret, code: code, state: "test" } |
|
}, (err, resp, body) => { |
|
body = JSON.parse(body); |
|
if (err || body.success === false) { |
|
err = err || body.error; |
|
return res.send(View("error", ctx, { message: err })); |
|
} |
|
|
|
let accessToken = body.access_token; |
|
if (accessToken == null) { |
|
return res.send(View("error", ctx, { message: "Unable to parse access token" })); |
|
} else { |
|
res.cookie(accessCookie.name, { access: accessToken }, accessCookie.options) |
|
res.redirect("/linode-mgmt/"); |
|
} |
|
} |
|
); |
|
}); |
|
|
|
app.get("/logout", (req, res) => { |
|
res.clearCookie(accessCookie.name); |
|
res.redirect("/linode-mgmt/"); |
|
}) |
|
|
|
app.get("/", (req, res) => { |
|
let ctx = req.webtaskContext; |
|
if (req.accessToken) { |
|
request.get({ |
|
url: "https://api.linode.com/v4/linode/instances", |
|
headers: { "Authorization": `token ${req.accessToken}`} |
|
}, (err, resp, body) => { |
|
body = JSON.parse(body); |
|
if (err) { |
|
// err = err || body.error; |
|
return res.send(View("error", ctx, { message: err })); |
|
} |
|
res.send(View("index", ctx, { linodes: body.data }, "Linodes - My Linode Manager")); |
|
}); |
|
} else { |
|
res.send(View("index", ctx, {}, "My Linode Manager")); |
|
} |
|
}); |
|
|
|
function View(name, ctx, model, opts) { |
|
DEBUG && console.log(`View: ${name}`, model); |
|
let title = typeof opts === "string" ? opts : (opts) ? opts.title : "Page"; |
|
let clientId = ctx.secrets.CLIENT_ID; |
|
let html = Views[name](model); |
|
return ` |
|
<!DOCTYPE html> |
|
<html lang="en"> |
|
<head> |
|
<meta charset="utf-8"> |
|
<meta http-equiv="X-UA-Compatible" content="IE=edge"> |
|
<meta name="viewport" content="width=device-width, initial-scale=1"> |
|
<!-- The above 3 meta tags *must* come first in the head; any other head content must come *after* these tags --> |
|
<title>${title}</title> |
|
|
|
<!-- Latest compiled and minified CSS --> |
|
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous"> |
|
|
|
<!-- Optional theme --> |
|
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css" integrity="sha384-rHyoN1iRsVXV4nD0JutlnGaslCJuC7uwjduW9SVrLvRYooPp2bWYgmgJQIXwl/Sp" crossorigin="anonymous"> |
|
|
|
<!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries --> |
|
<!-- WARNING: Respond.js doesn't work if you view the page via file:// --> |
|
<!--[if lt IE 9]> |
|
<script src="https://oss.maxcdn.com/html5shiv/3.7.3/html5shiv.min.js"></script> |
|
<script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script> |
|
<![endif]--> |
|
</head> |
|
<body> |
|
<nav class="navbar navbar-default"> |
|
<div class="container-fluid"> |
|
<div class="navbar-header"> |
|
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar-main" aria-expanded="false"><span class="sr-only">Toggle navigation</span><span class="glyphicon glyphicon-console"></span></button> |
|
<a class="navbar-brand" href="/linode-mgmt/">Linode Manager</a> |
|
</div> |
|
<div class="collapse navbar-collapse" id="navbar-main"> |
|
<div class="navbar-left"> |
|
<span data-name="username" style="display: inline-block; height: 50px; padding: 15px;"></span> |
|
</div> |
|
<form id="login" class="navbar-form navbar-right hidden" action="https://login.linode.com/oauth/authorize" method="GET"> |
|
<input type="submit" class="btn btn-default" value="Login"> |
|
<input type="hidden" name="client_id" value="${clientId}"> |
|
<input type="hidden" name="scope" value="linodes:view"> |
|
<input type="hidden" name="response_type" value="code"> |
|
<input type="hidden" name="state" value="test"> |
|
</form> |
|
<div id="logout" class="navbar-form navbar-right hidden"> |
|
<a class="btn btn-default" id="logout" href="logout">Logout</a> |
|
</div> |
|
</div> |
|
</div> |
|
</nav> |
|
<div class="container"> |
|
<!-- container BEGIN --> |
|
${html} |
|
<!-- container END --> |
|
</div> |
|
<!-- jQuery (necessary for Bootstrap's JavaScript plugins) --> |
|
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script> |
|
<!-- Latest compiled and minified JavaScript --> |
|
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script> |
|
<script> |
|
$.getJSON("/linode-profile", data => { |
|
$("[data-name=username]").text(data.username); |
|
if (data.username !== undefined) { |
|
$("#logout").removeClass("hidden"); |
|
} else { |
|
$("#login").removeClass("hidden"); |
|
} |
|
}); |
|
</script> |
|
</body> |
|
</html> |
|
`; |
|
} |
|
|
|
module.exports = fromExpress(app); |