First check in

This commit is contained in:
Ben Hardill
2016-10-30 11:31:39 +00:00
commit 6dd0d41fc8
22 changed files with 879 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
node_modules/*

0
README.md Normal file
View File

280
index.js Normal file
View File

@@ -0,0 +1,280 @@
var fs = require('fs');
var url = require('url');
var http = require('http');
var https = require('https');
var flash = require('connect-flash');
var express = require('express');
var session = require('express-session');
var morgan = require('morgan');
var passport = require('passport');
var mongoose = require('mongoose');
var bodyParser = require('body-parser');
var cookieParser = require('cookie-parser');
var LocalStrategy = require('passport-local').Strategy;
var PassportOAuthBearer = require('passport-http-bearer');
var oauthServer = require('./oauth');
var Account = require('./models/account');
var oauthModels = require('./models/oauth');
var Devices = require('./models/devices');
var port = (process.env.VCAP_APP_PORT || process.env.PORT ||3000);
var host = (process.env.VCAP_APP_HOST || '0.0.0.0');
var mongo_url = (process.env.MONGO_URL || 'mongodb://localhost/users');
if (process.env.VCAP_SERVICES) {
var services = JSON.parse(process.env.VCAP_SERVICES);
for (serviceName in services) {
if (serviceName.match('^mongo')) {
var creds = services[serviceName][0]['credentials'];
mongo_url = creds.url;
} else {
console.log("no database found");
}
}
}
var app_id = 'https://localhost:' + port;
if (process.env.VCAP_APPLICATION) {
var application = JSON.parse(process.env.VCAP_APPLICATION);
var app_uri = application['application_uris'][0];
app_id = 'https://' + app_uri;
}
var cookieSecret = 'ihytsrf334';
var app = express();
app.set('view engine', 'ejs');
app.enable('trust proxy');
app.use(morgan("combined"));
app.use(cookieParser(cookieSecret));
app.use(flash());
app.use(session({
// genid: function(req) {
// return genuuid() // use UUIDs for session IDs
// },
secret: cookieSecret,
resave: false,
saveUninitialized: false,
cookie: {
secure: true
}
}));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(passport.initialize());
app.use(passport.session());
function requireHTTPS(req, res, next) {
if (req.get('X-Forwarded-Proto') === 'http') {
//FYI this should work for local development as well
var url = 'https://' + req.get('host');
if (req.get('host') === 'localhost') {
url += ':' + port;
}
url += req.url;
return res.redirect(url);
}
next();
}
app.use(requireHTTPS);
app.use('/',express.static('static'));
passport.use(new LocalStrategy(Account.authenticate()));
passport.serializeUser(Account.serializeUser());
passport.deserializeUser(Account.deserializeUser());
var accessTokenStrategy = new PassportOAuthBearer(function(token, done) {
oauthModels.AccessToken.findOne({ token: token }).populate('user').populate('grant').exec(function(error, token) {
if (token && token.active && token.grant.active && token.user) {
done(null, token.user, { scope: token.scope });
} else if (!error) {p
done(null, false);
} else {
done(error);
}
});
});
passport.use(accessTokenStrategy);
mongoose.connect(mongo_url);
app.get('/', function(req,res){
res.render('pages/index');
});
app.get('/login', function(req,res){
res.render('pages/login');
});
//app.post('/login',passport.authenticate('local', { failureRedirect: '/login', successRedirect: '/2faCheck', failureFlash: true }));
app.post('/login',passport.authenticate('local', { failureRedirect: '/login', failureFlash: true }));
app.get('/newuser', function(req,res){
res.render('pages/register');
});
app.post('/newuser', function(req,res){
Account.register(new Account({ username : req.body.username }), req.body.password, function(err, account) {
if (err) {
console.log(err);
return res.status(400).send(err.message);
}
passport.authenticate('local')(req, res, function () {
console.log("created new user %s", req.body.username);
res.status(201).send();
});
});
});
app.get('/auth/start',oauthServer.authorize(function(applicationID, redirectURI,done){
oauthModels.Application.findOne({ oauth_id: applicationID }, function(error, application) {
if (application) {
var match = false, uri = url.parse(redirectURI || '');
for (var i = 0; i < application.domains.length; i++) {
if (uri.host == application.domains[i] || (uri.protocol == application.domains[i] && uri.protocol != 'http' && uri.protocol != 'https')) {
match = true;
break;
}
}
if (match && redirectURI && redirectURI.length > 0) {
done(null, application, redirectURI);
} else {
done(new Error("You must supply a redirect_uri that is a domain or url scheme owned by your app."), false);
}
} else if (!error) {
done(new Error("There is no app with the client_id you supplied."), false);
} else {
done(error);
}
});
}),function(req,res){
var scopeMap = {
// ... display strings for all scope variables ...
access_devices: 'access you devices',
create_devices: 'create new devices'
};
res.render('pages/oauth', {
transaction_id: req.oauth2.transactionID,
currentURL: req.originalUrl,
response_type: req.query.response_type,
errors: req.flash('error'),
scope: req.oauth2.req.scope,
application: req.oauth2.client,
user: req.user,
map: scopeMap
});
});
app.post('/auth/finish',function(req,res,next) {
if (req.user) {
next();
} else {
passport.authenticate('local', {
session: false
}, function(error,user,info){
if (user) {
next();
} else if (!error){
req.flash('error', 'Your email or password was incorrect. Try again.');
res.redirect(req.body['auth_url'])
}
})(req,res,next);
}
}, oauthServer.decision(function(req,done){
done(null, { scope: req.oauth2.req.scope });
}));
app.post('/auth/exchange',function(req,res,next){
var appID = req.body['client_id'];
var appSecret = req.body['client_secret'];
oauthModels.Application.findOne({ oauth_id: appID, oauth_secret: appSecret }, function(error, application) {
if (application) {
req.appl = application;
next();
} else if (!error) {
error = new Error("There was no application with the Application ID and Secret you provided.");
next(error);
} else {
next(error);
}
});
}, oauthServer.token(), oauthServer.errorHandler());
app.get('/api/v1/discover',
passport.authenticate('bearer', { session: false }),
function(req,res,next){
}
);
app.post('/api/v1/command',
passport.authenticate('bearer', { session: false }),
function(req,res,next){
}
);
app.post('/api/v1/devices',
passport.authenticate('bearer', { session: false }),
function(req,res,next){
var devices = req.body;
if (typeof devices == 'object' && Array.isArray(foo)) {
for (var i=0; i<devices.lenght; i++) {
var applianceId = devices[i].applianceId;
Devices.update({
username: req.user,
applianceId: applianceId
},
devices[i],
{
upsert: true
},
function(err){
//log error
});
}
} else {
res.error(400);
}
}
);
app.get('/api/v1/devices',
passport.authenticate('bearer', { session: false }),
function(req,res,next){
Devices.find({user: req.user},function(error, data){
res.send(data);
});
}
);
var server = http.Server(app);
if (app_id.match(/^https:\/\/localhost:/)) {
var options = {
key: fs.readFileSync('server.key'),
cert: fs.readFileSync('server.crt')
};
server = https.createServer(options, app);
}
server.listen(port, host, function(){
console.log('App listening on %s:%d!', host, port);
console.log("App_ID -> %s", app_id);
});

12
models/account.js Normal file
View File

@@ -0,0 +1,12 @@
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var passportLocalMongoose = require('passport-local-mongoose');
var Account = new Schema({
username: String,
password: String
});
Account.plugin(passportLocalMongoose);
module.exports = mongoose.model('Account', Account);

19
models/devices.js Normal file
View File

@@ -0,0 +1,19 @@
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var Devices = new Schema({
username: String,
applianceId: String,
friendlyName: String,
friendlyDescription: String,
isReachable: Boolean,
action: [String],
additionalApplianceDetails: {
extraDetail1: String,
extraDetail2: String,
extraDetail3: String,
extraDetail4: String
}
});
module.exports = mongoose.model('Devices', Devices);

57
models/oauth.js Normal file
View File

@@ -0,0 +1,57 @@
var uid = require('uid2');
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var ApplicationSchema = new Schema({
title: { type: String, required: true },
oauth_id: { type: Number, unique: true },
oauth_secret: { type: String, unique: true, default: function() {
return uid(42);
}
},
domains: [ { type: String } ]
});
var GrantCodeSchema = new Schema({
code: { type: String, unique: true, default: function() {
return uid(24);
}
},
user: { type: Schema.Types.ObjectId, ref: 'User' },
application: { type: Schema.Types.ObjectId, ref: 'Application' },
scope: [ { type: String } ],
active: { type: Boolean, default: true }
});
var AccessTokenSchema = new Schema({
token: { type: String, unique: true, default: function() {
return uid(124);
}
},
user: { type: Schema.Types.ObjectId, ref: 'User' },
application: { type: Schema.Types.ObjectId, ref: 'Application' },
grant: { type: Schema.Types.ObjectId, ref: 'GrantCode' },
scope: [ { type: String }],
expires: { type: Date, default: function(){
var today = new Date();
var length = 60; // Length (in minutes) of our access token
return new Date(today.getTime() + length*60000);
} },
active: { type: Boolean, get: function(value) {
if (this.expires < new Date() || !value) {
return false;
} else {
return value;
}
}, default: true }
});
var Application = mongoose.model('Application', ApplicationSchema);
var GrantCode = mongoose.model('GrantCode', GrantCodeSchema);
var AccessToken = mongoose.model('AccessToken', AccessTokenSchema);
module.exports = {
Application: Application,
GrantCode: GrantCode,
AccessToken: AccessToken
}

49
oauth.js Normal file
View File

@@ -0,0 +1,49 @@
var oauth2orize = require('oauth2orize');
var OAuth = require('./models/oauth');
var server = oauth2orize.createServer();
server.grant(oauth2orize.grant.code({
scopeSeparator: [ ' ', ',' ]
}, function(application, redirectURI, user, ares, done) {
var grant = new OAuth.GrantCode({
application: application,
user: user,
scope: ares.scope
});
grant.save(function(error) {
done(error, error ? null : grant.code);
});
}));
server.exchange(oauth2orize.exchange.code({
userProperty: 'appl'
}, function(application, code, redirectURI, done) {
OAuth.GrantCode.findOne({ code: code }, function(error, grant) {
if (grant && grant.active && grant.application == application.id) {
var token = new OAuth.AccessToken({
application: grant.application,
user: grant.user,
grant: grant,
scope: grant.scope
});
token.save(function(error) {
done(error, error ? null : token.token, null, error ? null : { token_type: 'standard' });
});
} else {
done(error, false);
}
});
}));
server.serializeClient(function(application, done) {
done(null, application.id);
});
server.deserializeClient(function(id, done) {
OAuth.Application.findById(id, function(error, application) {
done(error, error ? null : application);
});
});
module.exports = server;

36
package.json Normal file
View File

@@ -0,0 +1,36 @@
{
"name": "home-skill-web",
"version": "0.0.1",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [
"alexa",
"smart",
"home"
],
"author": "hardillb@gmail.com",
"license": "Apache-2.0",
"dependencies": {
"body-parser": "^1.15.2",
"connect-flash": "^0.1.1",
"cookie-parser": "^1.4.3",
"ejs": "^2.5.2",
"express": "^4.14.0",
"express-session": "^1.14.1",
"mongoose": "^4.6.5",
"morgan": "^1.7.0",
"oauth2orize": "^1.5.1",
"passport": "^0.3.2",
"passport-http-bearer": "^1.0.1",
"passport-local": "^1.0.0",
"passport-local-mongoose": "^4.0.0",
"passport-totp": "0.0.2",
"uid2": "0.0.3"
},
"devDependencies": {
"passport-oauth2": "^1.3.0"
}
}

14
server.crt Normal file
View File

@@ -0,0 +1,14 @@
-----BEGIN CERTIFICATE-----
MIICMzCCAZwCCQDjaso0IudC9TANBgkqhkiG9w0BAQUFADBeMQswCQYDVQQGEwJH
QjESMBAGA1UECAwJWW9ya3NoaXJlMRMwEQYDVQQHDApMaXZlcnNlZGdlMRIwEAYD
VQQKDAlBQ01FIEx0ZC4xEjAQBgNVBAMMCWxvY2FsaG9zdDAeFw0xNjAzMjgwOTI4
NDFaFw0xNzAzMjgwOTI4NDFaMF4xCzAJBgNVBAYTAkdCMRIwEAYDVQQIDAlZb3Jr
c2hpcmUxEzARBgNVBAcMCkxpdmVyc2VkZ2UxEjAQBgNVBAoMCUFDTUUgTHRkLjES
MBAGA1UEAwwJbG9jYWxob3N0MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCe
svibrux0l4AXWPtMdlrZPl4c234NBxa8VTQLTzZ5KLPnAZDgW6gBMmUHeK6Ob5rw
UUOtO6yIgMhU6m2YR9IQRlTRg6mgmLaa8tcLkl70mpDoxO9yvoX2+Dt6Fo3uL84r
rzgk07HNKSityIG115ils1/UEX1MS01YUO898h1SvwIDAQABMA0GCSqGSIb3DQEB
BQUAA4GBAFAt6jk5Regz+QHpTTTyyQwINmRfG6dcJ6CmSUaZOpmgGEXPcmzJAzkU
3rqN249y1OUFXfIY/KgGCPqHSO030yMB0BVi2WeQzCvssyxGGKtAJiTxd2cSrrZY
ggr53B3mODDx+oFzNYxj/HUPFEVPKuEgM4PoEA1n5sjpwYAentCg
-----END CERTIFICATE-----

15
server.key Normal file
View File

@@ -0,0 +1,15 @@
-----BEGIN RSA PRIVATE KEY-----
MIICXQIBAAKBgQCesvibrux0l4AXWPtMdlrZPl4c234NBxa8VTQLTzZ5KLPnAZDg
W6gBMmUHeK6Ob5rwUUOtO6yIgMhU6m2YR9IQRlTRg6mgmLaa8tcLkl70mpDoxO9y
voX2+Dt6Fo3uL84rrzgk07HNKSityIG115ils1/UEX1MS01YUO898h1SvwIDAQAB
AoGALgBo6g/hgMZtndbwOTDRksluVGUXe8VDROJZzLFwc9xlkZ+1lCNdZsNp36mO
x8D4I5Maz/tNPuA9scembw7ah+I8mrEfwAg7rLWVXUqNaBq5rD3SqpKarUFFpJ+0
bf51iFZZFz2IvkO4o6mEeZ3ThWxe3dWhh1MD2oHUzKBDkkkCQQDRFAMVGK3SMU/t
NRh5m3Ww1kbgtvHT9isBkczYw4WY6bJcdLPlWFVtxZ5S1d9hlm6F5zEYUaRrKa4F
+XZ6IvwVAkEAwlCS9A04XWLnT50j8pquoQsjKIRyqy59YwJ71ZhPPjAFpgZTv1wL
VvrfrByWiofMQxGtu9nG/1HoO11LUK4EgwJAFyrehJHtgOMR9jjx81e0nNnBlNjw
xwn6Dfx39HUF8sHCj9gmrv0wyi8hshAc5pVivde2avlw/Kbrr6HK7RG/WQJBALtg
cWTv/qNnBUEregevC6h2EfA3UFAsI3M/aOS+2+NO8ZN41HdaLgExKFFSvARYESu3
t33G8nMwq63bOA5T0DsCQQCG9SJvwU/6p8MQFZhjmQ7Jker9ss7PFUbTFSF4nTTL
pq/zOGdjB+/0lS1UJjeIBkZ9NQ/CRTqL5ivxIw5JwF56
-----END RSA PRIVATE KEY-----

45
static/about.html Normal file
View File

@@ -0,0 +1,45 @@
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Node-RED Alexa Home Skill Bridge</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- 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">
<link rel="stylesheet" href="/css/basic.css">
</head>
<body>
<nav class="navbar navbar-inverse navbar-fixed-top">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="#">Node-RED Alexa Home Skill Bridge</a>
</div>
<div id="navbar" class="collapse navbar-collapse">
<ul class="nav navbar-nav">
<li><a href="/">Home</a></li>
<li><a href="/docs/docs.html">Documentation</a></li>
<li class="active"><a href="/about.html">About</a></li>
</ul>
</div><!--/.nav-collapse -->
</div>
</nav>
<div class="container main">
<h1>About<h1>
</div>
<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>
</body>
</html>

17
static/css/basic.css Normal file
View File

@@ -0,0 +1,17 @@
body {
padding-top: 50px;
}
.main {
display: flex;
}
.side-menu {
float: left;
padding-top: 60px;
width: 150px;
}
.main-content {
padding-left: 10px;
}

68
static/docs/docs.html Normal file
View File

@@ -0,0 +1,68 @@
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Node-RED Alexa Home Skill Bridge</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- 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">
<link rel="stylesheet" href="/css/basic.css">
</head>
<body>
<nav class="navbar navbar-inverse navbar-fixed-top">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="#">Node-RED Alexa Home Skill Bridge</a>
</div>
<div id="navbar" class="collapse navbar-collapse">
<ul class="nav navbar-nav">
<li><a href="/">Home</a></li>
<li class="active"><a href="/docs/docs.html">Documentation</a></li>
<li><a href="/about.html">About</a></li>
</ul>
</div><!--/.nav-collapse -->
</div>
</nav>
<div class="container main">
<nav class="side-menu">
<ul class="nav nav-pills nav-stacked">
<li><a href="#createAccount">Create Account</a></li>
<li><a href="#installNodes">Install Nodes</a></li>
<li><a href="#configNodes">Configure Nodes</a></li>
</ul>
</nav>
<div class="container main-content">
<h1>Documentation</h1>
<p>Welcome to the Node-RED Alexa Home Skill Bridge documentation</p>
<h2 id="createAccount">Create Account</h2>
<p>Before you start the first thing that needs to be done is to create an account on this site.
You can do this by clicking <a href="/newuser">here</a></p>
<p></p>
<h2 id="installNodes">Install Nodes</h2>
<p>There are serveral ways to install new nodes into Node-RED</p>
<p>The simplest is to use the installer built into the Node-RED admin UI.</p>
<p>You can also install the nodes from the command line</p>
<p>And finally if you are embedding Node-RED into your own application you can add the node to
the package.json file for your project and it will be included when you do an install.</p>
<h2 id="configNodes">Configure Nodes</h2>
<p></p>
</div>
</div>
<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>
</body>
</html>

BIN
static/node-red.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

90
test/getToken.js Normal file
View File

@@ -0,0 +1,90 @@
var http = require('http');
var express = require('express');
var morgan = require('morgan');
var session = require('express-session');
var passport = require('passport');
var OAuth2Strategy = require('passport-oauth2');
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
var app = express();
app.use(morgan("combined"));
app.use(session({
// genid: function(req) {
// return genuuid() // use UUIDs for session IDs
// },
secret: 'boo',
resave: false,
saveUninitialized: false,
cookie: {
secure: true
}
}));
app.use(passport.initialize());
app.use(passport.session());
var users = {};
passport.use(new OAuth2Strategy({
authorizationURL: 'https://localhost:3000/auth/start',
tokenURL: 'https://localhost:3000/auth/exchange',
clientID: '1',
clientSecret: 'password1234',
scope: "access_devices",
callbackURL: 'http://localhost:3001/callback'
}, function(accessToken, refreshToken, profile, callback){
console.log("accessToken: ", accessToken);
console.log("refreshToken: ", refreshToken);
console.log("profile: ",profile);
profile.accessToken = accessToken;
profile.refreshToken = refreshToken;
profile.id = 0;
callback(null,profile);
}));
passport.serializeUser(function(user, done){
console.log("serialize user ",user);
users[user.id] = user;
done(null, user.id);
});
passport.deserializeUser(function(id, done){
done(null,users[id]);
});
app.get('/start',passport.authenticate('oauth2'));
app.get('/callback',
function(req,res,next){
console.log("callback");
next();
},
passport.authenticate('oauth2', { failureRedirect: '/login' }),
function(req, res){
console.log("callback part 2");
res.redirect('/done');
});
app.get('/done',function(req,res){
var options = {
root: __dirname + '/public/',
dotfiles: 'deny',
headers: {
'x-timestamp': Date.now(),
'x-sent': true
}
};
res.sendFile('done.html',options,function(err){
if (err) {
console.log(err);
}
});
})
var port = 3001;
var host = '127.0.0.1';
var server = http.Server(app);
server.listen(port, host, function(){
console.log('App listening on %s:%d!', host, port);
});
console.log("done");

8
test/public/done.html Normal file
View File

@@ -0,0 +1,8 @@
<html>
<head>
<title>Got all the tokens</title>
</head>
<body>
All done
</body>
</html>

View File

@@ -0,0 +1,6 @@
</div>
<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>
</body>
</html>

View File

@@ -0,0 +1,40 @@
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Node-RED Alexa Home Skill Bridge</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- 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">
<link rel="stylesheet" href="/css/basic.css">
</head>
<body>
<nav class="navbar navbar-inverse navbar-fixed-top">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="#">Node-RED Alexa Home Skill Bridge</a>
</div>
<div id="navbar" class="collapse navbar-collapse">
<ul class="nav navbar-nav">
<li class="active"><a href="/">Home</a></li>
<li><a href="/docs/docs.html">Documentation</a></li>
<li><a href="/about.html">About</a></li>
</ul>
</div><!--/.nav-collapse -->
</div>
</nav>
<div class="container main">
<!-- <div class="container">
<a href="/login">Login</a> or <a href="/newuser">Register</a>
</div> -->

12
views/pages/index.ejs Normal file
View File

@@ -0,0 +1,12 @@
<% include ../fragments/header.ejs %>
<div class="container main-content">
<h1>Node-RED Alexa Home Skill Bridge</h1>
<p>This site is the registration point for using the
node-red-contrib-alexa-home-skill
<a href="http://nodered.org">Node-RED</a> module.</p>
<p>This node allows you to create your own "devices" to
work with the Amazon Echo's Home Skill system, giving
you voice control over basically anything you can
interface with Node-RED.</p>
</div
<% include ../fragments/footer.ejs %>

15
views/pages/login.ejs Normal file
View File

@@ -0,0 +1,15 @@
<% include ../fragments/header.ejs %>
<div class="container main-content">
<div id="login">
<form method="POST" action="/login">
<label for="username">Username</label>
<input type="text" id="username" name="username"/>
<br>
<label for="password">Password:</label>
<input type="password" id="password" name="password"/>
<br>
<input type="submit" value="Login">
</form>
</div>
</div>
<% include ../fragments/footer.ejs %>

48
views/pages/oauth.ejs Normal file
View File

@@ -0,0 +1,48 @@
<% include ../fragments/header.ejs %>
<div class="container main-content">
<h1>Connect with <%= application.title %></h1>
<form action="/auth/finish" method="post">
<input type="hidden" name="transaction_id" value="<%= transaction_id %>">
<input type="hidden" name="response_type" value="<%= response_type %>">
<input type="hidden" name="client_id" value="<%= application.oauth_id %>">
<input type="hidden" name="auth_url" value="<%= currentURL %>">
<input type="hidden" name="scope" value="<%= scope.join(',') %>">
<div class="">
<p><%= application.title %> requires permission to:</p>
<ul>
<% scope.forEach(function(i){ %>
<li><%= map[i] %></li>
<% });%>
</ul>
<% if (user) { %>
<p>Click <em>Authorise</em> to allow this app access</p>
<% } else { %>
<p>Please sign in to allow this app access</p>
<% } %>
<div>
<% if (user) { %>
<div class="form-group">
<p>Signed in as <strong><%= user.name%></strong>.</p>
<a href="/logout?next=">Not </a><%= user.name%>?
<input type="submit" value="Authorise">
</div>
<% } else { %>
<div class="form-group">
<label for="username">Username: </label>
<input type="text" id="username" name="username"/>
</div>
<div>
<label for="password">Password: </label>
<input type="password" id="password" name="password">
</div>
<div>
<input type="submit" value="Authorise">
</div>
<% } %>
</form>
</div>
<% include ../fragments/footer.ejs %>

46
views/pages/register.ejs Normal file
View File

@@ -0,0 +1,46 @@
<% include ../fragments/header.ejs %>
<div class="container main-content">
<div id="register">
<label for="username">Username</label>
<input type="text" id="username"/>
<br>
<label for="password">Password:</label>
<input type="password" id="password"/>
<br>
<label for="password">Password:</label>
<input type="password" id="passwordAgain"/>
<br>
<button id="registerButton">Register</button>
<script type="application/javascript">
var xhr = new XMLHttpRequest();
var button = document.getElementById('registerButton');
button.onclick = function(){
var username = document.getElementById('username').value;
var password = document.getElementById('password').value;
var passwordAgain = document.getElementById('passwordAgain').value;
if (password !== passwordAgain) {
alert("Passwords don't match");
return;
}
var params = "username=" + username + "&password=" + password;
xhr.open('POST', '/newUser',true);
xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
xhr.onreadystatechange = function () {
if( xhr.readyState == 4 && xhr.status == 201) {
//new user created
//forward to 2FA addition page
window.location = '/';
} else if (xhr.readyState == 4 && xhr.status == 400) {
//show error
alert(xhr.responseText);
}
}
xhr.send(params);
};
</script>
</div>
</div>
<% include ../fragments/footer.ejs %>