mirror of
https://github.com/hardillb/node-red-alexa-home-skill-web.git
synced 2025-12-11 09:29:44 +01:00
First check in
This commit is contained in:
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
node_modules/*
|
||||
|
||||
280
index.js
Normal file
280
index.js
Normal 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
12
models/account.js
Normal 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
19
models/devices.js
Normal 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
57
models/oauth.js
Normal 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
49
oauth.js
Normal 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
36
package.json
Normal 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
14
server.crt
Normal 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
15
server.key
Normal 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
45
static/about.html
Normal 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
17
static/css/basic.css
Normal 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
68
static/docs/docs.html
Normal 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
BIN
static/node-red.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.4 KiB |
90
test/getToken.js
Normal file
90
test/getToken.js
Normal 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
8
test/public/done.html
Normal file
@@ -0,0 +1,8 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>Got all the tokens</title>
|
||||
</head>
|
||||
<body>
|
||||
All done
|
||||
</body>
|
||||
</html>
|
||||
6
views/fragments/footer.ejs
Normal file
6
views/fragments/footer.ejs
Normal 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>
|
||||
40
views/fragments/header.ejs
Normal file
40
views/fragments/header.ejs
Normal 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
12
views/pages/index.ejs
Normal 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
15
views/pages/login.ejs
Normal 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
48
views/pages/oauth.ejs
Normal 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
46
views/pages/register.ejs
Normal 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 %>
|
||||
Reference in New Issue
Block a user