├── .bowerrc ├── .editorconfig ├── .gitignore ├── .gitmodules ├── .jscsrc ├── .jshintrc ├── .travis.yml ├── Dockerfile ├── LICENSE ├── README.md ├── app.js ├── auth └── basic.js ├── bower.json ├── config ├── config.json ├── default.json └── test.json ├── docs ├── design-iot.jpg ├── iot.jpg └── struct.png ├── gulpfile.js ├── migrations ├── 20150731220954-create-user.js ├── 20150805085332-add-user.js └── 20150818114216-create-message.js ├── models ├── encrypt │ ├── bcrypt.js │ └── crypto.js ├── index.js ├── message.js └── user.js ├── modules ├── coap.js ├── http.js ├── mqtt.js ├── utils │ └── getAuth.js └── websocket.js ├── package.json ├── persistence └── mongo.js ├── prepare.sh ├── server.js ├── server ├── index.js ├── public │ └── stylesheets │ │ └── style.css └── views │ ├── error.jade │ ├── footer.jade │ ├── index.jade │ ├── layout.jade │ ├── login │ ├── index.jade │ └── success.jade │ ├── nav.jade │ ├── success.jade │ └── user │ ├── index.jade │ └── register.jade ├── test ├── mocha.opts ├── models │ └── user_spec.js ├── server │ ├── app_spec.js │ ├── coap_spec.js │ ├── http_spec.js │ ├── mqtt_spec.js │ └── ws_spec.js └── spec_helper.js ├── test_scripts ├── coap_get_test.js ├── coap_post_test.js └── ws_test.js ├── utils └── common.js └── yarn.lock /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "server/public/assets/bower" 3 | } 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | 10 | [*.md] 11 | trim_trailing_whitespace = false 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | dist 4 | *.log 5 | .idea 6 | *.sqlite 7 | server/public/assets/bower 8 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "jobs"] 2 | path = jobs 3 | url = https://github.com/phodal/lan-jobs-cron 4 | -------------------------------------------------------------------------------- /.jscsrc: -------------------------------------------------------------------------------- 1 | { 2 | "requireCurlyBraces": ["if", "else", "for", "while", "do", "try", "catch"], 3 | "requireSpaceAfterKeywords": ["if", "else", "for", "while", "do", "switch", "return", "try", "catch", "function"], 4 | "requireSpacesInFunctionExpression": { 5 | "beforeOpeningCurlyBrace": true 6 | }, 7 | "disallowMultipleVarDecl": true, 8 | "disallowSpacesInsideArrayBrackets": true, 9 | "disallowSpacesInsideParentheses": true, 10 | "disallowSpaceAfterObjectKeys": true, 11 | "disallowQuotedKeysInObjects": true, 12 | "requireSpaceBeforeBinaryOperators": ["?", "+", "/", "*", "=", "==", "===", "!=", "!==", ">", ">=", "<", "<="], 13 | "disallowSpaceAfterBinaryOperators": ["!"], 14 | "requireSpaceAfterBinaryOperators": ["?", ",", "+", "/", "*", ":", "=", "==", "===", "!=", "!==", ">", ">=", "<", "<="], 15 | "disallowSpaceBeforeBinaryOperators": [","], 16 | "disallowSpaceAfterPrefixUnaryOperators": ["++", "--", "+", "-", "~", "!"], 17 | "disallowSpaceBeforePostfixUnaryOperators": ["++", "--"], 18 | "requireSpaceBeforeBinaryOperators": ["+", "-", "/", "*", "=", "==", "===", "!=", "!=="], 19 | "requireSpaceAfterBinaryOperators": ["+", "-", "/", "*", "=", "==", "===", "!=", "!=="], 20 | "disallowImplicitTypeConversion": ["numeric", "binary", "string"], 21 | "disallowKeywords": ["with", "eval"], 22 | "disallowMultipleLineBreaks": true, 23 | "disallowKeywordsOnNewLine": ["else"], 24 | "requireLineFeedAtFileEnd": true, 25 | "excludeFiles": ["node_modules/**", "client/**"], 26 | "validateIndentation": 2 27 | } 28 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | "esnext": true, 4 | "bitwise": false, 5 | "curly": true, 6 | "eqeqeq": true, 7 | "eqnull": true, 8 | "immed": true, 9 | "latedef": false, 10 | "newcap": true, 11 | "noarg": true, 12 | "undef": true, 13 | "strict": true, 14 | "quotmark": "single", 15 | "scripturl": true 16 | } 17 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - v7 5 | - v6 6 | - v5 7 | notifications: 8 | email: false 9 | services: mongodb 10 | before_install: npm install crypto;npm install -g gulp sequelize-cli 11 | before_script: sequelize db:migrate 12 | install: npm install 13 | after_success: CODECLIMATE_REPO_TOKEN=9338786ef61d320210d94fa289f33d566f3c1a111a38c9e96acdb4c77493d363 codeclimate-test-reporter < coverage/lcov.info 14 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Pull base image. 2 | FROM ubuntu:14.04 3 | 4 | 5 | # Install MongoDB. 6 | RUN \ 7 | apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 7F0CEB10 && \ 8 | echo 'deb http://downloads-distro.mongodb.org/repo/ubuntu-upstart dist 10gen' > /etc/apt/sources.list.d/mongodb.list && \ 9 | apt-get update && \ 10 | apt-get install -y mongodb-org && \ 11 | rm -rf /var/lib/apt/lists/* 12 | 13 | RUN apt-get update 14 | RUN apt-get install -y python-software-properties curl sqlite3 git wget build-essential 15 | # Define mountable directories. 16 | VOLUME ["/data/db"] 17 | 18 | # Define working directory. 19 | WORKDIR /data 20 | 21 | # Define default command. 22 | CMD ["mongod"] 23 | 24 | # Expose ports. 25 | EXPOSE 27017:27017 26 | EXPOSE 28017 27 | EXPOSE 5683:5683 28 | EXPOSE 1883:1883 29 | EXPOSE 8899:8899 30 | EXPOSE 8898:8898 31 | 32 | # Install Node.js 33 | RUN curl -sL https://deb.nodesource.com/setup_0.12 | sudo bash - 34 | RUN apt-get install -y nodejs 35 | # Define working directory. 36 | 37 | RUN cd /home 38 | RUN git clone http://github.com/phodal/lan /home/lan 39 | 40 | WORKDIR /home/lan 41 | RUN npm install --production 42 | RUN npm install -g sequelize-cli 43 | RUN npm install -g forever 44 | RUN sequelize db:migrate 45 | #RUN forever start server 46 | 47 | 48 | # Define default command. 49 | CMD ["bash"] 50 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Phodal Huang 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Lan IoT Server 2 | 3 | [![Build Status](https://travis-ci.org/phodal/lan.svg?branch=master)](https://travis-ci.org/phodal/lan) 4 | [![Code Climate](https://codeclimate.com/github/phodal/lan/badges/gpa.svg)](https://codeclimate.com/github/phodal/lan) 5 | [![Test Coverage](https://codeclimate.com/github/phodal/lan/badges/coverage.svg)](https://codeclimate.com/github/phodal/lan/coverage) 6 | 7 | > Internet of Things Server Layer with CoAP, WebSocket, MQTT, HTTP Protocol. 8 | 9 | Inspired by [Qest](https://github.com/mcollina/qest) 10 | 11 | Test on Node Version: ``v5``,``v6`` 12 | 13 | ## Architecture: 14 | 15 | ![IoT Struct](docs/struct.png) 16 | 17 | 详细可见:《自己动手设计物联网》 18 | 19 | ![Designiot](docs/design-iot.jpg) 20 | 21 | 立即购买:[亚马逊](https://www.amazon.cn/dp/B01IBZWTWW/ref=wl_it_dp_o_pC_nS_ttl?_encoding=UTF8&colid=BDXF90QZX6WX&coliid=I19EB97K0GNLW8)、[京东](http://search.jd.com/Search?keyword=%E8%87%AA%E5%B7%B1%E5%8A%A8%E6%89%8B%E8%AE%BE%E8%AE%A1%E7%89%A9%E8%81%94%E7%BD%91&enc=utf-8&wq=%E8%87%AA%E5%B7%B1%E5%8A%A8%E6%89%8B%E8%AE%BE%E8%AE%A1%E7%89%A9%E8%81%94%E7%BD%91&pvid=k24y6hri.l4xi28) 22 | 23 | ## Lan Server Layer: 24 | 25 | ![Lan Struct](docs/iot.jpg) 26 | 27 | ## 配置 28 | 29 | 默认配置: 30 | 31 | ```javascript 32 | { 33 | "encrypt": "crypto", 34 | "db_url": "mongodb://localhost:27017/lan", 35 | "db_collection": "documents", 36 | "db_collection_user": "user", 37 | "modules": [ 38 | "coap", 39 | "http", 40 | "mqtt", 41 | "websocket" 42 | ], 43 | "port": { 44 | "http": 8899, 45 | "websocket": 8898, 46 | "coap": 5683, 47 | "mqtt": 1883 48 | }, 49 | "logging" :true, 50 | "secret": "keyboard cat" 51 | } 52 | ``` 53 | 54 | encrypt: ["crypto", "bcrypt"] 55 | 56 | modules: ["coap", "http", "mqtt", "websocket"] 57 | 58 | Use ``bcrypt``, please install it: 59 | 60 | npm install --save bcrypt 61 | 62 | ## Docker 63 | 64 | ``Require``: Docker 65 | 66 | docker build . 67 | 68 | ## 安装(Setup) 69 | 70 | ``必装``: 71 | 72 | 1. MongoDB -> NoSQL: 数据存储 73 | 2. Sqlite || MySQL || PostgreSQL || MariaDB || MSSQL -> SQL: 存储用户信息 74 | 75 | 然后: 76 | 77 | 1.Clone 78 | 79 | git clone https://github.com/phodal/lan --recursive 80 | 81 | 2.安装依赖 82 | 83 | npm install 84 | bower install 85 | 86 | 3.修改config下的配置 87 | 88 | /config.json 数据库配置 89 | /default.json Lan系统配置 90 | 91 | 4.数据库初始化 92 | 93 | npm install -g sequelize-cli 94 | sequelize db:migrate 95 | 96 | 5.Start Cron 97 | 98 | node jobs/cron.js 99 | 100 | 6.运行 101 | 102 | npm start 103 | 104 | ## Setup 105 | 106 | ``require``: Install 107 | 108 | 1. ``MongoDB`` 109 | 2. Sqlite || MySQL || PostgreSQL || MariaDB || MSSQL -> SQL: save user info 110 | 111 | Then. 112 | 113 | 1.Install dependencies 114 | 115 | npm install 116 | 117 | Or Just Production only: 118 | 119 | npm install --production 120 | 121 | 2.Setup Database 122 | 123 | sequelize db:migrate 124 | 125 | 126 | 3.Start Cron 127 | 128 | node jobs/cron.js 129 | 130 | 4.Run 131 | 132 | npm start 133 | 134 | ## Test With Tool 135 | 136 | ### HTTP 137 | 138 | Get 139 | 140 | curl --user root:root -X GET -H "Content-Type: application/json" http://localhost:8899/topics/root 141 | 142 | PUT/POST - cUrl 143 | 144 | curl --user root:root -X PUT -d '{ "dream": 1 }' -H "Content-Type: application/json" http://localhost:8899/topics/root 145 | 146 | ### MQTT 147 | 148 | Publish - Mosquitto 149 | 150 | mosquitto_pub -u root -P root -h localhost -d -t lettuce -m "Hello, MQTT. This is my first message." 151 | 152 | Subscribe - Mosquitto 153 | 154 | mosquitto_sub -t message -h localhost -u root -P root 155 | 156 | ### CoAP 157 | 158 | POST/PUT - libcoap 159 | 160 | coap-client -e "{message: 'hello,world}" -m put coap://127.0.0.1/topic?root:root 161 | 162 | GET - libcoap 163 | 164 | coap-client -m get coap://127.0.0.1:5683/topic?root:root 165 | 166 | GET/POST/PUT - Copper 167 | 168 | 1. Visit [coap://127.0.0.1:5683/topic?root:root](coap://127.0.0.1:5683/topic?root:root) 169 | 170 | GET: Click ``GET`` 171 | 172 | POST: Type on ``Outgoing``, Click ``POST`` 173 | 174 | ### WebSocket 175 | 176 | Message 177 | 178 | node test_scripts/ws_test.js 179 | 180 | ## Auth 181 | 182 | Standalone (单机) 183 | 184 | ``User`` -> ``SQL Database`` (Auth) 185 | 186 | ``SQL Database`` -> ``NoSQL`` (Save) 187 | 188 | Multi 189 | 190 | ``User`` -> ``SQL Database`` (Save) 191 | 192 | ``SQL Database`` -> ``NoSQL`` (Cron Job || MQ) 193 | 194 | ``User`` -> ``NoSQL`` (Auth && Save) 195 | 196 | ## License 197 | 198 | © 2015~2016 [Phodal](https://www.phodal.com/). This code is distributed under the MIT license. 199 | 200 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var path = require('path'); 3 | var logger = require('morgan'); 4 | var cookieParser = require('cookie-parser'); 5 | var bodyParser = require('body-parser'); 6 | var session = require('express-session'); 7 | 8 | var routes = require('./server/index'); 9 | var passport = require('passport'); 10 | var LocalStrategy = require('passport-local').Strategy; 11 | var config = require('config'); 12 | 13 | var app = exports.app = express(); 14 | var configure, start; 15 | 16 | var model = require('./models/'); 17 | 18 | var loader = function (app, key) { 19 | var loadPath; 20 | loadPath = __dirname + ("/modules/"); 21 | app[key] = require(loadPath + key)(app); 22 | return app; 23 | }; 24 | 25 | passport.use(new LocalStrategy( 26 | { 27 | usernameField: 'name', 28 | passwordField: 'password' 29 | }, 30 | function (username, password, done) { 31 | model.User.find({where: {name: username}}).then(function (user) { 32 | if (!user) { 33 | done(null, false, {message: 'Unknown user'}); 34 | } else { 35 | user.comparePassword(password, function (err, result) { 36 | if (result) { 37 | done(null ,true); 38 | } else { 39 | done(null, false, {message: 'Password || Username error'}) 40 | } 41 | }); 42 | } 43 | }).error(function (err) { 44 | done(err); 45 | }); 46 | } 47 | )); 48 | 49 | passport.serializeUser(function (user, done) { 50 | done(null, user.uid); 51 | }); 52 | 53 | passport.deserializeUser(function (uid, done) { 54 | model.User.find({where: {uid: uid}}) 55 | .then(function (user) { 56 | done(null, user); 57 | }).error(function (err) { 58 | console.log(id, err); 59 | done(err, null); 60 | }); 61 | }); 62 | 63 | configure = function () { 64 | app.set('views', path.join(__dirname + '/server', 'views')); 65 | app.set('view engine', 'jade'); 66 | app.use(require('cookie-parser')()); 67 | app.use(require('body-parser').urlencoded({ extended: true })); 68 | app.use(session({ 69 | secret: config.get('secret'), 70 | resave: true, 71 | saveUninitialized: true 72 | })); 73 | 74 | app.use(passport.initialize()); 75 | app.use(passport.session()); 76 | 77 | 78 | if(config.get('logging')){ 79 | app.use(logger('dev')); 80 | } 81 | 82 | app.use(bodyParser.json()); 83 | app.use(bodyParser.urlencoded({extended: false})); 84 | app.use(cookieParser()); 85 | app.use(express.static(path.join(__dirname + '/server', 'public'))); 86 | app.use('/', routes); 87 | 88 | app.config = config; 89 | var modules = config.get('modules'); 90 | for (var i = 0; i < modules.length; i++) { 91 | app = loader(app, modules[i]); 92 | } 93 | return app; 94 | }; 95 | 96 | module.exports.app = app; 97 | module.exports.configure = configure; 98 | -------------------------------------------------------------------------------- /auth/basic.js: -------------------------------------------------------------------------------- 1 | var model = require('../models'); 2 | 3 | module.exports = function (userInfo, noUserCB, successCB, errorCB) { 4 | 'use strict'; 5 | model.User.findOne({where: {name: userInfo.name}}).then(function (user) { 6 | if (!user) { 7 | return noUserCB(); 8 | } 9 | user.comparePassword(userInfo.password, function (err, result) { 10 | if (result) { 11 | return successCB(user); 12 | } else { 13 | return errorCB(); 14 | } 15 | }); 16 | }); 17 | }; 18 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lan", 3 | "version": "0.0.0", 4 | "homepage": "https://github.com/phodal/lan", 5 | "dependencies": { 6 | "bootstrap": "~3.3.5" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /config/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "development": { 3 | "dialect": "sqlite", 4 | "storage": "./db.development.sqlite", 5 | "logging": false 6 | }, 7 | "test": { 8 | "dialect": "sqlite", 9 | "storage": "./db.test.sqlite", 10 | "logging": false 11 | }, 12 | "production": { 13 | "username": "root", 14 | "password": null, 15 | "database": "database_production", 16 | "host": "127.0.0.1", 17 | "dialect": "mysql" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /config/default.json: -------------------------------------------------------------------------------- 1 | { 2 | "encrypt": "crypto", 3 | "db_url": "mongodb://localhost:27017/lan", 4 | "db_collection": "documents", 5 | "db_collection_user": "user", 6 | "modules": [ 7 | "coap", 8 | "http", 9 | "mqtt", 10 | "websocket" 11 | ], 12 | "port": { 13 | "http": 8899, 14 | "websocket": 8898, 15 | "coap": 5683, 16 | "mqtt": 1883 17 | }, 18 | "logging": true, 19 | "secret": "keyboard cat" 20 | } 21 | -------------------------------------------------------------------------------- /config/test.json: -------------------------------------------------------------------------------- 1 | { 2 | "encrypt": "crypto", 3 | "db_url": "mongodb://localhost:27017/lan_test", 4 | "db_collection": "documents", 5 | "db_collection_user": "user", 6 | "modules": [ 7 | "coap", 8 | "http", 9 | "mqtt", 10 | "websocket" 11 | ], 12 | "port": { 13 | "http": 8899, 14 | "websocket": 8898, 15 | "coap": 5683, 16 | "mqtt": 1883 17 | }, 18 | "logging": false, 19 | "secret": "keyboard cat" 20 | } 21 | -------------------------------------------------------------------------------- /docs/design-iot.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phodal/lan/72dd1cc9880e080809975681dec53868ab2a0fc6/docs/design-iot.jpg -------------------------------------------------------------------------------- /docs/iot.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phodal/lan/72dd1cc9880e080809975681dec53868ab2a0fc6/docs/iot.jpg -------------------------------------------------------------------------------- /docs/struct.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phodal/lan/72dd1cc9880e080809975681dec53868ab2a0fc6/docs/struct.png -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var path = require('path'); 3 | var gulp = require('gulp'); 4 | var excludeGitignore = require('gulp-exclude-gitignore'); 5 | var mocha = require('gulp-mocha'); 6 | var jshint = require('gulp-jshint'); 7 | var jscs = require('gulp-jscs'); 8 | var istanbul = require('gulp-istanbul'); 9 | var nsp = require('gulp-nsp'); 10 | var plumber = require('gulp-plumber'); 11 | var coveralls = require('gulp-coveralls'); 12 | var babel = require('gulp-babel'); 13 | var shell = require('shelljs'); 14 | 15 | // Initialize the babel transpiler so ES2015 files gets compiled 16 | // when they're loaded 17 | require('babel-core/register'); 18 | 19 | var handleErr = function (err) { 20 | console.log(err.message); 21 | process.exit(1); 22 | }; 23 | 24 | gulp.task('static', function () { 25 | return gulp.src(['server/**/*.js', 'modules/**/*.js', 'jobs/**/*.js', 'auth/**/*.js', 'persistence/**/*.js']) 26 | .pipe(excludeGitignore()) 27 | .pipe(jshint('.jshintrc')) 28 | .pipe(jshint.reporter('jshint-stylish')) 29 | .pipe(jshint.reporter('fail')) 30 | .pipe(jscs()) 31 | .on('error', handleErr); 32 | }); 33 | 34 | gulp.task('nsp', function (cb) { 35 | nsp('package.json', cb); 36 | }); 37 | 38 | gulp.task('pre-test', function () { 39 | return gulp.src(['modules/**/*.js', 'app.js','server/*.js', 'loader.js', 'models/**/*.js', 'persistence/**/*.js']) 40 | .pipe(babel()) 41 | .pipe(istanbul({includeUntested: true})) 42 | .pipe(istanbul.hookRequire()); 43 | }); 44 | 45 | gulp.task('test', ['cleanDev', 'pre-test'], function (cb) { 46 | var mochaErr; 47 | 48 | gulp.src('test/**/*.js') 49 | .pipe(plumber()) 50 | .pipe(mocha({reporter: 'spec'})) 51 | .on('error', function (err) { 52 | mochaErr = err; 53 | console.log(err); 54 | }) 55 | .pipe(istanbul.writeReports()) 56 | .on('end', function () { 57 | cb(mochaErr); 58 | }); 59 | }); 60 | 61 | gulp.task('coveralls', ['test'], function () { 62 | if (!process.env.CI) { 63 | return; 64 | } 65 | 66 | return gulp.src(path.join(__dirname, 'coverage/lcov.info')) 67 | .pipe(coveralls()); 68 | }); 69 | 70 | gulp.task('cleanDev', function(){ 71 | var localDBFile = 'db.test.sqlite'; 72 | if(shell.test('-f', localDBFile)){ 73 | console.log('===================='); 74 | shell.rm(localDBFile); 75 | } 76 | shell.exec('sequelize db:migrate'); 77 | }); 78 | 79 | gulp.task('babel', function () { 80 | return gulp.src('lib/**/*.js') 81 | .pipe(babel()) 82 | .pipe(gulp.dest('dist')); 83 | }); 84 | 85 | gulp.task('prepublish', ['nsp', 'babel']); 86 | gulp.task('default', ['cleanDev', 'static', 'test', 'coveralls']); 87 | -------------------------------------------------------------------------------- /migrations/20150731220954-create-user.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = { 3 | up: function(queryInterface, Sequelize) { 4 | return queryInterface.createTable('Users', { 5 | id: { 6 | allowNull: false, 7 | autoIncrement: true, 8 | primaryKey: true, 9 | type: Sequelize.INTEGER 10 | }, 11 | name: { 12 | type: Sequelize.STRING 13 | }, 14 | password: { 15 | type: Sequelize.STRING 16 | }, 17 | expiration: { 18 | type: Sequelize.DATE 19 | }, 20 | isAdmin: { 21 | type: Sequelize.BOOLEAN 22 | }, 23 | uid: { 24 | type: Sequelize.UUID 25 | }, 26 | phone: { 27 | type: Sequelize.STRING 28 | }, 29 | alias: { 30 | type: Sequelize.STRING 31 | }, 32 | createdAt: { 33 | allowNull: false, 34 | type: Sequelize.DATE 35 | }, 36 | updatedAt: { 37 | allowNull: false, 38 | type: Sequelize.DATE 39 | } 40 | }); 41 | }, 42 | down: function(queryInterface, Sequelize) { 43 | return queryInterface.dropTable('Users'); 44 | } 45 | }; 46 | -------------------------------------------------------------------------------- /migrations/20150805085332-add-user.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var models = require('../models'); 4 | 5 | module.exports = { 6 | up: function (queryInterface, Sequelize, done) { 7 | models.User.create({ 8 | name: 'root', 9 | password: 'root', 10 | expiration: '2016-03-03', 11 | uuid: '84e824cb-bfae-4d95-a76d-51103c556057', 12 | phone: '12345678901', 13 | isAdmin: true, 14 | alias: 'fengda' 15 | }).then(function () { 16 | done(); 17 | }) 18 | }, 19 | 20 | down: function (queryInterface, Sequelize, done) { 21 | done(); 22 | } 23 | }; 24 | -------------------------------------------------------------------------------- /migrations/20150818114216-create-message.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = { 3 | up: function(queryInterface, Sequelize) { 4 | return queryInterface.createTable('Messages', { 5 | id: { 6 | allowNull: false, 7 | autoIncrement: true, 8 | primaryKey: true, 9 | type: Sequelize.INTEGER 10 | }, 11 | name: { 12 | type: Sequelize.STRING 13 | }, 14 | userId: { 15 | type: Sequelize.INTEGER 16 | }, 17 | uuid: { 18 | type: Sequelize.STRING 19 | }, 20 | status: { 21 | type: Sequelize.STRING 22 | }, 23 | createdAt: { 24 | allowNull: false, 25 | type: Sequelize.DATE 26 | }, 27 | updatedAt: { 28 | allowNull: false, 29 | type: Sequelize.DATE 30 | } 31 | }); 32 | }, 33 | down: function(queryInterface, Sequelize) { 34 | return queryInterface.dropTable('Messages'); 35 | } 36 | }; 37 | -------------------------------------------------------------------------------- /models/encrypt/bcrypt.js: -------------------------------------------------------------------------------- 1 | var bcrypt = require('bcrypt'); 2 | 3 | function hash(password, callback) { 4 | return bcrypt.hash(password, 10, callback); 5 | } 6 | 7 | module.exports = { 8 | 'hash': hash, 9 | 'validate': bcrypt.compare 10 | }; -------------------------------------------------------------------------------- /models/encrypt/crypto.js: -------------------------------------------------------------------------------- 1 | var crypto = require('crypto'); 2 | 3 | var SaltLength = 9; 4 | 5 | function createHash(password, callback) { 6 | var salt = generateSalt(SaltLength); 7 | var hash = md5(password + salt); 8 | callback(null, salt + hash); 9 | } 10 | 11 | function validateHash(password, hash , callback) { 12 | var salt = hash.substr(0, SaltLength); 13 | var validHash = salt + md5(password + salt); 14 | callback(null , hash === validHash); 15 | } 16 | 17 | function generateSalt(len) { 18 | var set = '0123456789abcdefghijklmnopqurstuvwxyzABCDEFGHIJKLMNOPQURSTUVWXYZ', 19 | setLen = set.length, 20 | salt = ''; 21 | for (var i = 0; i < len; i++) { 22 | var p = Math.floor(Math.random() * setLen); 23 | salt += set[p]; 24 | } 25 | return salt; 26 | } 27 | 28 | function md5(string) { 29 | return crypto.createHash('md5').update(string).digest('hex'); 30 | } 31 | 32 | module.exports = { 33 | 'hash': createHash, 34 | 'validate': validateHash 35 | }; -------------------------------------------------------------------------------- /models/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var fs = require('fs'); 4 | var path = require('path'); 5 | var Sequelize = require('sequelize'); 6 | var basename = path.basename(module.filename); 7 | var env = process.env.NODE_ENV || 'development'; 8 | var config = require(__dirname + '/../config/config.json')[env]; 9 | var sequelize = new Sequelize(config.database, config.username, config.password, config); 10 | var db = {}; 11 | 12 | fs 13 | .readdirSync(__dirname) 14 | .filter(function(file) { 15 | return (file.indexOf('.') !== 0) && (file !== basename); 16 | }) 17 | .forEach(function(file) { 18 | if (file.slice(-3) !== '.js') return; 19 | var model = sequelize['import'](path.join(__dirname, file)); 20 | db[model.name] = model; 21 | }); 22 | 23 | Object.keys(db).forEach(function(modelName) { 24 | if (db[modelName].associate) { 25 | db[modelName].associate(db); 26 | } 27 | }); 28 | 29 | db.sequelize = sequelize; 30 | db.Sequelize = Sequelize; 31 | 32 | module.exports = db; 33 | -------------------------------------------------------------------------------- /models/message.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = function (sequelize, DataTypes) { 3 | var Message = sequelize.define('Message', { 4 | name: DataTypes.STRING, 5 | userId: DataTypes.INTEGER, 6 | uuid: DataTypes.STRING, 7 | status: DataTypes.STRING 8 | }, { 9 | classMethods: { 10 | associate: function (models) { 11 | } 12 | } 13 | }); 14 | 15 | return Message; 16 | }; 17 | -------------------------------------------------------------------------------- /models/user.js: -------------------------------------------------------------------------------- 1 | var config = require('config'); 2 | var encrypt = require('./encrypt/' + config.get('encrypt')); 3 | var uuid = require('uuid'); 4 | 5 | 'use strict'; 6 | module.exports = function (sequelize, DataTypes) { 7 | function hashPasswordHook(user, options, done) { 8 | if (!user.changed('password')) { 9 | done(); 10 | } 11 | encrypt.hash(user.get('password'), function (err, hash) { 12 | if (err) { 13 | done(err); 14 | } 15 | user.set('password', hash); 16 | done(); 17 | }); 18 | } 19 | 20 | function generateUID(user, options, done) { 21 | user.set('uid', uuid.v4()); 22 | done(); 23 | } 24 | 25 | var User = sequelize.define('User', { 26 | id: { 27 | allowNull: false, 28 | autoIncrement: true, 29 | primaryKey: true, 30 | type: DataTypes.INTEGER 31 | }, 32 | name: { 33 | type: DataTypes.STRING, 34 | allowNull: false, 35 | unique: true, 36 | validate: { 37 | notEmpty: true, 38 | isUnique: function (value, next) { 39 | User.find({ 40 | where: {name: value} 41 | }).done(function (error, user) { 42 | if (error) { 43 | return next(error) 44 | } 45 | if (user) { 46 | console.log('Username is already in use!'); 47 | return next('Username is already in use!') 48 | } 49 | next() 50 | }) 51 | } 52 | } 53 | }, 54 | password: { 55 | type: DataTypes.STRING, 56 | allowNull: false, 57 | validate: { 58 | notEmpty: true 59 | } 60 | }, 61 | expiration: DataTypes.DATE, 62 | uid: { 63 | type: DataTypes.UUID, 64 | defaultValue: DataTypes.UUIDV1, 65 | primaryKey: true 66 | }, 67 | isAdmin: { 68 | type:DataTypes.BOOLEAN, 69 | allowNull: false, 70 | defaultValue: false 71 | }, 72 | phone: DataTypes.STRING, 73 | alias: DataTypes.STRING 74 | }, { 75 | classMethods: { 76 | associate: function (models) { 77 | }, 78 | findByUserId: function (userid) { 79 | return this.find({where: {id: userid}}); 80 | } 81 | }, 82 | hooks: { 83 | beforeCreate: [hashPasswordHook, generateUID], 84 | beforeUpdate: hashPasswordHook 85 | }, 86 | instanceMethods: { 87 | comparePassword: function (password, done) { 88 | return encrypt.validate(password, this.password, function (err, res) { 89 | return done(err, res); 90 | }); 91 | } 92 | } 93 | }); 94 | 95 | return User; 96 | }; 97 | -------------------------------------------------------------------------------- /modules/coap.js: -------------------------------------------------------------------------------- 1 | var Database = require('../persistence/mongo'); 2 | var db = new Database(); 3 | var authCheck = require('../auth/basic'); 4 | 5 | module.exports = function (app) { 6 | 'use strict'; 7 | return function (req, res) { 8 | var deviceRex = /^\/(.*)\/(.*)\?/; 9 | 10 | var other = function () { 11 | res.code = '4.05'; 12 | res.end(JSON.stringify({method: 'not support'})); 13 | }; 14 | 15 | if (!req.options) { 16 | return other(); 17 | } 18 | var existBlock = false; 19 | var uriPathAuth = ''; 20 | for (var i = 1; i < req.options.length; i++) { 21 | if (req.options[i].name === 'Uri-Query') { 22 | uriPathAuth = req.options[i].value.toString(); 23 | existBlock = true; 24 | } 25 | } 26 | if (!existBlock) { 27 | return other(); 28 | } 29 | var username = uriPathAuth.split(':')[0]; 30 | var password = uriPathAuth.split(':')[1]; 31 | var userInfo = { 32 | password: password, 33 | name: username 34 | }; 35 | 36 | var errorCB = function () { 37 | res.code = '4.03'; 38 | res.end({}); 39 | }; 40 | 41 | var handlerGet = function () { 42 | var successCB = function (user) { 43 | var payload = {name: userInfo.name, token: user.uid}; 44 | if ((deviceRex.test(req.url))) { 45 | var deviceID = deviceRex.exec(req.url)[1]; 46 | payload[deviceID] = deviceRex.exec(req.url)[2]; 47 | } 48 | db.query(payload, function (dbResult) { 49 | res.code = '2.05'; 50 | res.end(JSON.stringify({result: dbResult})); 51 | }); 52 | }; 53 | authCheck(userInfo, errorCB, successCB, errorCB); 54 | }; 55 | 56 | var handPut = function () { 57 | if (!(deviceRex.test(req.url))) { 58 | res.code = '4.04'; 59 | res.end(JSON.stringify({topic: 'no exist'})); 60 | } 61 | var successCB = function (user) { 62 | var payload = {name: user.name, token: user.uid, data: req.payload.toString()}; 63 | var deviceID = payload[deviceRex.exec(req.url)[1]]; 64 | payload[deviceID] = deviceRex.exec(req.url)[2]; 65 | 66 | db.update(payload); 67 | res.code = '2.01'; 68 | res.end(JSON.stringify({method: 'PUT'})); 69 | }; 70 | 71 | authCheck(userInfo, errorCB, successCB, errorCB); 72 | }; 73 | 74 | var handPost = function () { 75 | var successCB = function (user) { 76 | var payload = {name: user.name, token: user.uid, data: req.payload.toString()}; 77 | if ((deviceRex.test(req.url))) { 78 | var deviceID = deviceRex.exec(req.url)[1]; 79 | payload[deviceID] = deviceRex.exec(req.url)[2]; 80 | } 81 | 82 | db.insert(payload); 83 | res.code = '2.01'; 84 | res.end(JSON.stringify({method: 'POST'})); 85 | }; 86 | 87 | authCheck(userInfo, errorCB, successCB, errorCB); 88 | }; 89 | 90 | switch (req.method) { 91 | case 'GET': 92 | handlerGet(); 93 | break; 94 | case 'PUT': 95 | handPut(); 96 | break; 97 | case 'POST': 98 | handPost(); 99 | break; 100 | default: 101 | return other(); 102 | } 103 | }; 104 | } 105 | ; 106 | -------------------------------------------------------------------------------- /modules/http.js: -------------------------------------------------------------------------------- 1 | var Database = require('../persistence/mongo'); 2 | var db = new Database(); 3 | var authCheck = require('../auth/basic'); 4 | var getAuthInfo = require('./utils/getAuth'); 5 | 6 | module.exports = function (app) { 7 | 'use strict'; 8 | app.get(/^\/(.*)\/(.*)$/, function (req, res) { 9 | if (!req.headers.authorization) { 10 | res.statusCode = 401; 11 | res.setHeader('WWW-Authenticate', 'Basic realm="Secure Area"'); 12 | return res.end('Unauthorized'); 13 | } 14 | 15 | var userInfo = getAuthInfo(req); 16 | 17 | var errorCB = function () { 18 | res.sendStatus(403); 19 | }; 20 | 21 | var successCB = function (user) { 22 | var options = {name: userInfo.name, token: user.uid}; 23 | options[req.param[0]] = req.params[1]; 24 | db.query(options, function (dbResult) { 25 | return res.json({username: userInfo.name, topic: dbResult}); 26 | }); 27 | }; 28 | 29 | authCheck(userInfo, errorCB, successCB, errorCB); 30 | }); 31 | 32 | function update(req, res) { 33 | if (!req.headers.authorization) { 34 | return res.sendStatus(403); 35 | } 36 | var userInfo = getAuthInfo(req); 37 | 38 | var errorCB = function () { 39 | res.sendStatus(403); 40 | }; 41 | 42 | var successCB = function (user) { 43 | var payload = {name: user.name, token: user.uid, data: req.body}; 44 | payload[req.param[0]] = req.params[1]; 45 | db.insert(payload); 46 | res.sendStatus(204); 47 | }; 48 | 49 | authCheck(userInfo, errorCB, successCB, errorCB); 50 | } 51 | 52 | app.post(/^\/(.*)\/(.*)$/, function (req, res) { 53 | return update(req, res); 54 | }); 55 | 56 | return app.put(/^\/(.*)\/(.*)$/, function (req, res) { 57 | return update(req, res); 58 | }); 59 | }; 60 | -------------------------------------------------------------------------------- /modules/mqtt.js: -------------------------------------------------------------------------------- 1 | var Database = require('../persistence/mongo'); 2 | var db = new Database(); 3 | var authCheck = require('../auth/basic'); 4 | 5 | module.exports = function (app) { 6 | 'use strict'; 7 | return function (client) { 8 | var userInfo = {}; 9 | var self = this; 10 | 11 | if (!self.clients) { 12 | self.clients = {}; 13 | } 14 | 15 | client.on('connect', function (packet) { 16 | client.id = packet.client; 17 | client.subscriptions = []; 18 | if (packet.username === undefined || packet.password === undefined) { 19 | return client.connack({ 20 | returnCode: -1 21 | }); 22 | } 23 | client.id = packet.client; 24 | var reqUserInfo = { 25 | name: packet.username, 26 | password: packet.password.toString() 27 | }; 28 | 29 | var errorCB = function () { 30 | return client.connack({ 31 | returnCode: -1 32 | }); 33 | }; 34 | 35 | var successCB = function (user) { 36 | self.clients[packet.clientId] = client; 37 | userInfo = user; 38 | client.connack({ 39 | returnCode: 0 40 | }); 41 | }; 42 | 43 | authCheck(reqUserInfo, errorCB, successCB, errorCB); 44 | }); 45 | 46 | client.on('subscribe', function (packet) { 47 | var granted = []; 48 | var i; 49 | for (i = 0; i < packet.subscriptions.length; i++) { 50 | var qos = packet.subscriptions[i].qos; 51 | var topic = packet.subscriptions[i].topic; 52 | var reg = new RegExp(topic.replace('+', '[^\/]+').replace('#', '.+') + '$'); 53 | 54 | granted.push(qos); 55 | client.subscriptions.push(reg); 56 | } 57 | 58 | client.suback({messageId: packet.messageId, granted: granted}); 59 | 60 | db.subscribe({name: userInfo.name, token: userInfo.uid}, function (result) { 61 | return client.publish({ 62 | topic: userInfo.name.toString(), 63 | payload: JSON.stringify(result) 64 | }); 65 | }); 66 | }); 67 | client.on('publish', function (packet) { 68 | var k; 69 | var i; 70 | var payload = { 71 | name: userInfo.name, 72 | token: userInfo.uid, 73 | data: packet.payload.toString() 74 | }; 75 | db.insert(payload); 76 | 77 | for (k in self.clients) { 78 | var _client = self.clients[k]; 79 | 80 | for (i = 0; i < _client.subscriptions.length; i++) { 81 | var subscription = _client.subscriptions[i]; 82 | 83 | if (subscription.test(packet.topic)) { 84 | _client.publish({ 85 | topic: packet.topic, 86 | payload: packet.payload.toString() 87 | }); 88 | break; 89 | } 90 | } 91 | } 92 | }); 93 | 94 | client.on('pingreq', function (packet) { 95 | return client.pingresp(); 96 | }); 97 | client.on('disconnect', function () { 98 | return client.stream.end(); 99 | }); 100 | client.on('error', function (error) { 101 | return client.stream.end(); 102 | }); 103 | client.on('close', function (err) { 104 | delete self.clients[client.id]; 105 | }); 106 | return client.on('unsubscribe', function (packet) { 107 | return client.unsuback({ 108 | messageId: packet.messageId 109 | }); 110 | }); 111 | }; 112 | }; 113 | -------------------------------------------------------------------------------- /modules/utils/getAuth.js: -------------------------------------------------------------------------------- 1 | module.exports = function (req) { 2 | 'use strict'; 3 | var encoded = req.headers.authorization.split(' ')[1]; 4 | var decoded = new Buffer(encoded, 'base64').toString('utf8'); 5 | 6 | var username = decoded.split(':')[0]; 7 | var password = decoded.split(':')[1]; 8 | return {name: username, password: password}; 9 | }; 10 | -------------------------------------------------------------------------------- /modules/websocket.js: -------------------------------------------------------------------------------- 1 | var Database = require('../persistence/mongo'); 2 | var db = new Database(); 3 | var authCheck = require('../auth/basic'); 4 | var getAuthInfo = require('./utils/getAuth'); 5 | 6 | module.exports = function (app) { 7 | 'use strict'; 8 | return function (server) { 9 | server.on('connection', function (socket) { 10 | if (!socket.upgradeReq.headers.authorization || socket.upgradeReq.headers.authorization === undefined) { 11 | socket.send(JSON.stringify({error: 'no auth'})); 12 | return socket.close(); 13 | } 14 | var userInfo = getAuthInfo(socket.upgradeReq); 15 | var authInfo = {}; 16 | 17 | var noUserCB = function () { 18 | socket.send(JSON.stringify({error: 'auth failure'})); 19 | socket.close(); 20 | }; 21 | 22 | var errorCB = function () { 23 | socket.send(JSON.stringify({error: 'auth failure'})); 24 | socket.close(); 25 | }; 26 | 27 | var successCB = function (result) { 28 | socket.send('connection'); 29 | authInfo = result; 30 | }; 31 | 32 | authCheck(userInfo, noUserCB, successCB, errorCB); 33 | 34 | socket.on('subscribe', function (topic) { 35 | db.subscribe({name: authInfo.name, token: authInfo.uid}, function (dbResult) { 36 | socket.send(JSON.stringify(dbResult)); 37 | }); 38 | }); 39 | return socket.on('disconnect', function () { 40 | console.log('disconnect'); 41 | }); 42 | }); 43 | }; 44 | }; 45 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lan", 3 | "version": "0.2.0", 4 | "description": "Internet of Things Server Layer with CoAP, WebSocket, MQTT, HTTP Protocol.", 5 | "homepage": "https://github.com/phodal/lan", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/phodal/lan.git" 9 | }, 10 | "author": "Phodal Huang", 11 | "files": [ 12 | "dist" 13 | ], 14 | "main": "server.js", 15 | "keywords": [ 16 | "iot", 17 | "lan" 18 | ], 19 | "scripts": { 20 | "test": "gulp test", 21 | "start": "node server.js" 22 | }, 23 | "license": "MIT", 24 | "bugs": { 25 | "url": "https://github.com/phodal/lan/issues" 26 | }, 27 | "dependencies": { 28 | "body-parser": "~1.13.2", 29 | "coap": "^0.21.0", 30 | "config": "^1.15.0", 31 | "cookie-parser": "~1.3.5", 32 | "cron": "^1.0.9", 33 | "debug": "~2.2.0", 34 | "express": "~4.15.3", 35 | "express-session": "^1.11.3", 36 | "pug": "2.0.0-beta6", 37 | "jade": "^1.11.0", 38 | "mongodb": "^2.0.42", 39 | "morgan": "~1.6.1", 40 | "mqtt": "^1.11.0", 41 | "passport": "^0.2.2", 42 | "passport-local": "^1.0.0", 43 | "sequelize": "^3.5.1", 44 | "serve-favicon": "~2.3.0", 45 | "sqlite3": "^3.0.9", 46 | "underscore": "^1.8.3", 47 | "uuid": "^3.0.0", 48 | "ws": "^1.1.1" 49 | }, 50 | "devDependencies": { 51 | "sequelize-cli": "^1.9.1", 52 | "async": "^1.4.0", 53 | "babel-core": "^5.5.0", 54 | "bl": "^1.0.0", 55 | "bluebird": "^2.9.34", 56 | "chai": "^3.2.0", 57 | "codeclimate-test-reporter": "^0.1.0", 58 | "gulp": "^3.6.0", 59 | "gulp-babel": "^5.1.0", 60 | "gulp-coveralls": "^0.1.0", 61 | "gulp-exclude-gitignore": "^1.0.0", 62 | "gulp-istanbul": "^0.9.0", 63 | "gulp-jscs": "^1.1.0", 64 | "gulp-jshint": "^1.5.3", 65 | "gulp-mocha": "^2.0.0", 66 | "gulp-nsp": "^0.4.5", 67 | "gulp-plumber": "^1.0.0", 68 | "jshint-stylish": "^1.0.0", 69 | "mocha": "^2.2.5", 70 | "mysql": "^2.8.0", 71 | "shelljs": "^0.5.1", 72 | "sinon": "^1.15.4", 73 | "request": "^2.60.0", 74 | "supertest": "^1.0.1", 75 | "should": "^7.0.4" 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /persistence/mongo.js: -------------------------------------------------------------------------------- 1 | var mongo = require('mongodb'); 2 | var MongoClient = mongo.MongoClient; 3 | 4 | var config = require('config'); 5 | 6 | var url = config.get('db_url'); 7 | 8 | function MongoPersistence() { 9 | 10 | } 11 | 12 | MongoPersistence.prototype.insert = function (payload) { 13 | 'use strict'; 14 | MongoClient.connect(url, function (err, db) { 15 | var insertDocuments = function (db, callback) { 16 | var collection = db.collection(config.get('db_collection')); 17 | collection.insert(payload, function (err, result) { 18 | callback(result); 19 | }); 20 | }; 21 | insertDocuments(db, function () { 22 | db.close(); 23 | }); 24 | }); 25 | }; 26 | 27 | MongoPersistence.prototype.update = function (payload) { 28 | 'use strict'; 29 | MongoClient.connect(url, function (err, db) { 30 | var updateDocument = function (db, callback) { 31 | var collection = db.collection(config.get('db_collection')); 32 | var oldPayload = {name: payload.name, topic: payload.topic, token: payload.token}; 33 | collection.update(oldPayload, {$set: {data: payload.data}}, function (err, result) { 34 | callback(result); 35 | }); 36 | }; 37 | updateDocument(db, function () { 38 | db.close(); 39 | }); 40 | }); 41 | }; 42 | 43 | MongoPersistence.prototype.query = function (queryOptions, queryCB) { 44 | 'use strict'; 45 | MongoClient.connect(url, function (err, db) { 46 | var findDocuments = function (db, query, callback) { 47 | var collection = db.collection(config.get('db_collection')); 48 | collection.find(query, {data: true, _id: false}).toArray(function (err, docs) { 49 | callback(docs); 50 | }); 51 | }; 52 | 53 | findDocuments(db, queryOptions, function (result) { 54 | db.close(); 55 | queryCB(result); 56 | }); 57 | }); 58 | }; 59 | 60 | MongoPersistence.prototype.subscribe = function (queryOptions, queryCB) { 61 | 'use strict'; 62 | MongoClient.connect(url, function (err, db) { 63 | var subDocuments = function (db, query, callback) { 64 | var collection = db.collection(config.get('db_collection')); 65 | collection.find(query).sort({$natural: 1}).limit(1).toArray(function (err, doc) { 66 | callback(doc); 67 | }); 68 | }; 69 | 70 | subDocuments(db, queryOptions, function (result) { 71 | db.close(); 72 | queryCB(result); 73 | }); 74 | }); 75 | }; 76 | 77 | module.exports = MongoPersistence; 78 | -------------------------------------------------------------------------------- /prepare.sh: -------------------------------------------------------------------------------- 1 | npm install; 2 | bower install; 3 | npm install -g sequelize-cli; 4 | sequelize db:migrate; 5 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | var configure=require("./app").configure; 2 | var _ = require('underscore'); 3 | var mqtt = require("mqtt"); 4 | var coap = require("coap"); 5 | var WebSocketServer = require('ws').Server; 6 | var config = require('config'); 7 | 8 | start = function (opts, callback) { 9 | var app = configure(); 10 | 11 | app.listen(config.get('port.http'), function () { 12 | console.log("http server run on port %d", config.get('port.http')); 13 | }); 14 | 15 | 16 | if (_.include(app.config.get('modules'), 'websocket')) { 17 | var webSocketServer = new WebSocketServer({port: config.get('port.websocket')}); 18 | app.websocket(webSocketServer); 19 | console.log("websocket server listening on port %d", config.get('port.websocket')); 20 | } 21 | 22 | if (_.include(app.config.get('modules'), 'coap')) { 23 | coap.createServer(app.coap).listen(config.get('port.coap'), function () { 24 | console.log("coap server listening on port %d", config.get('port.coap')); 25 | }); 26 | } 27 | 28 | if (_.include(app.config.get('modules'), 'mqtt')) { 29 | mqtt.MqttServer(app.mqtt).listen(config.get('port.mqtt'), function () { 30 | console.log("mqtt server listening on port %d", config.get('port.mqtt')); 31 | }); 32 | } 33 | return app; 34 | }; 35 | 36 | if (require.main.filename === __filename) { 37 | start(); 38 | } 39 | 40 | module.exports.start = start; 41 | -------------------------------------------------------------------------------- /server/index.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var router = express.Router(); 3 | var passport = require('passport'); 4 | var models = require('../models/'); 5 | var authCheck = require('../auth/basic'); 6 | 7 | router.get('/', function (req, res) { 8 | 'use strict'; 9 | res.render('index', { 10 | title: 'Welcome to Lan' 11 | }); 12 | }); 13 | 14 | router.get('/login', function (req, res) { 15 | 'use strict'; 16 | if (!req.isAuthenticated()) { 17 | res.render('login/index', {title: 'Lan Login'}); 18 | } else { 19 | res.render('login/success', { 20 | title: 'Already login ' + req.user.name, 21 | uid: req.user.uid, 22 | userName: req.user.name, 23 | phone: req.user.phone, 24 | alias: req.user.alias 25 | }); 26 | } 27 | }); 28 | 29 | router.post('/login', function (req, res) { 30 | 'use strict'; 31 | 32 | var userInfo = { 33 | name: req.body.name, 34 | password: req.body.password 35 | }; 36 | var errorCB = function () { 37 | req.session.messages = 'not such user'; 38 | return res.redirect('/login'); 39 | }; 40 | 41 | var successCB = function (user) { 42 | passport.authenticate('local')(req, res, function () { 43 | req.logIn(user, function (err) { 44 | req.session.messages = 'Login successfully'; 45 | return res.render('login/success', { 46 | title: 'Welcome ' + user.name, 47 | uid: user.uid, 48 | userName: user.name, 49 | phone: user.phone, 50 | alias: user.alias 51 | }); 52 | }); 53 | }); 54 | }; 55 | 56 | var failureCB = function () { 57 | return res.sendStatus(404); 58 | }; 59 | 60 | authCheck(userInfo, errorCB, successCB, failureCB); 61 | }); 62 | 63 | router.get('/logout', function (req, res) { 64 | 'use strict'; 65 | if (req.isAuthenticated()) { 66 | req.logout(); 67 | req.session.messages = 'Log out successfully'; 68 | } 69 | res.redirect('/'); 70 | }); 71 | 72 | router.get(/^\/users\/(.+)$/, function (req, res) { 73 | 'use strict'; 74 | if (!req.isAuthenticated()) { 75 | return res.redirect('/login'); 76 | } 77 | models.User.findOne({where: {name: req.params[0]}}).then(function (user) { 78 | if (!user) { 79 | return res.sendStatus(403); 80 | } 81 | 82 | return res.render('user/index', { 83 | title: user.name + '\'s Profile', 84 | user: user 85 | }); 86 | }); 87 | }); 88 | 89 | router.post('/register', function (req, res) { 90 | 'use strict'; 91 | var userInfo = { 92 | name: req.body.name, 93 | password: req.body.password, 94 | phone: req.body.phone, 95 | alias: req.body.alias 96 | }; 97 | 98 | models.User.build(userInfo) 99 | .validate() 100 | .then(function (err) { 101 | if (err) { 102 | return res.render('user/register', {user: userInfo, title: 'Something Error', errors: err.errors}); 103 | } 104 | models.User.create(userInfo).then(function (user, err) { 105 | if (err) { 106 | return res.redirect('/'); 107 | } 108 | 109 | models.Message.create({ 110 | name: user.name, 111 | userId: user.id, 112 | uuid: user.uid, 113 | status: 'create' 114 | }).then(function (message, err) { 115 | passport.authenticate('local')(req, res, function () { 116 | res.render('success', { 117 | title: 'Create Success,' + user.name, 118 | account: user, 119 | uid: user.uid 120 | }); 121 | }); 122 | }); 123 | }); 124 | }); 125 | }); 126 | 127 | router.get('/register', function (req, res) { 128 | 'use strict'; 129 | res.render('user/register', {title: 'Lan Account Manager', errors: ''}); 130 | }); 131 | 132 | module.exports = router; 133 | -------------------------------------------------------------------------------- /server/public/stylesheets/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | font: 14px "Lucida Grande", Helvetica, Arial, sans-serif; 3 | } 4 | /* Sticky footer styles 5 | -------------------------------------------------- */ 6 | html { 7 | position: relative; 8 | min-height: 100%; 9 | } 10 | body { 11 | /* Margin bottom by footer height */ 12 | margin-bottom: 60px; 13 | } 14 | .footer { 15 | position: absolute; 16 | bottom: 0; 17 | width: 100%; 18 | /* Set the fixed height of the footer here */ 19 | height: 60px; 20 | background-color: #f5f5f5; 21 | } 22 | 23 | 24 | /* Custom page CSS 25 | -------------------------------------------------- */ 26 | /* Not required for template or sticky footer method. */ 27 | 28 | body > .container { 29 | padding: 60px 15px 0; 30 | } 31 | .container .text-muted { 32 | margin: 20px 0; 33 | } 34 | 35 | .footer > .container { 36 | padding-right: 15px; 37 | padding-left: 15px; 38 | } 39 | 40 | code { 41 | font-size: 80%; 42 | } 43 | -------------------------------------------------------------------------------- /server/views/error.jade: -------------------------------------------------------------------------------- 1 | extends layout 2 | 3 | block content 4 | h1= message 5 | h2= error.status 6 | pre #{error.stack} 7 | -------------------------------------------------------------------------------- /server/views/footer.jade: -------------------------------------------------------------------------------- 1 | footer(class="footer") 2 | div.container 3 | p.text-muted © 2015 4 | a(href="http://www.phodal.com") Phodal Huang 5 | span . This code is distributed under the MIT license. 6 | -------------------------------------------------------------------------------- /server/views/index.jade: -------------------------------------------------------------------------------- 1 | extends layout 2 | 3 | block content 4 | div.container 5 | h2 #{title} 6 | h3 A CoAP,MQTT,HTTP,WebSocket Server of Internet of Things. 7 | -------------------------------------------------------------------------------- /server/views/layout.jade: -------------------------------------------------------------------------------- 1 | doctype html 2 | html 3 | head 4 | title= title 5 | link(rel='stylesheet', href='/assets/bower/bootstrap/dist/css/bootstrap.css') 6 | link(rel='stylesheet', href='/assets/bower/bootstrap/dist/css/bootstrap-theme.css') 7 | link(rel='stylesheet', href='/stylesheets/style.css') 8 | script(src='/assets/bower/jquery/dist/jquery.js') 9 | script(src='/assets/bower/bootstrap/dist/js/bootstrap.js') 10 | body 11 | include ./nav.jade 12 | block content 13 | include ./footer.jade 14 | -------------------------------------------------------------------------------- /server/views/login/index.jade: -------------------------------------------------------------------------------- 1 | extends ../layout 2 | title #{title} 3 | block content 4 | div.container 5 | h2 #{title} 6 | form(role='form', method="post", action="/login") 7 | div(class="form-group") 8 | label(from="name") Name 9 | input(type="text", name="name", class="form-control") 10 | 11 | div(class="form-group") 12 | label(from="password") Password 13 | input(type="password", name="password", class="form-control") 14 | 15 | div.actions 16 | br 17 | input(type="submit", value="login", class="btn btn-primary") 18 | 19 | -------------------------------------------------------------------------------- /server/views/login/success.jade: -------------------------------------------------------------------------------- 1 | extends ../layout 2 | title #{title} 3 | 4 | block content 5 | div.container 6 | div(class='panel panel-default') 7 | div(class='panel-heading') 8 | h3 #{title} 9 | div(class="panel-body") 10 | p User Name: #{userName} 11 | p Alias Name: #{alias} 12 | p Phone: #{phone} 13 | p UUID: #{uid} 14 | 15 | -------------------------------------------------------------------------------- /server/views/nav.jade: -------------------------------------------------------------------------------- 1 | nav(class="navbar navbar-default navbar-fixed-top") 2 | div.container 3 | a.navbar-brand Lan IoT 4 | div#navbar(class="collapse navbar-collapse") 5 | ul(class="nav navbar-nav") 6 | li(role="presentation") 7 | a(href="/") Home 8 | li(role="presentation") 9 | a(href="/login") Login 10 | li(role="presentation") 11 | a(href="/register") Register 12 | li(role="presentation") 13 | a(href="/logout") Logout 14 | -------------------------------------------------------------------------------- /server/views/success.jade: -------------------------------------------------------------------------------- 1 | extends layout 2 | 3 | block content 4 | div(class='panel panel-defaul') 5 | div(class='panel-heading') 6 | h3 #{title} 7 | div(class="panel-body") 8 | p #{account.name} 9 | p #{uid} 10 | 11 | -------------------------------------------------------------------------------- /server/views/user/index.jade: -------------------------------------------------------------------------------- 1 | extends ../layout 2 | block content 3 | div.container 4 | div(class='panel panel-default') 5 | div(class='panel-heading') 6 | h3 #{title} 7 | div(class="panel-body") 8 | p User Name: #{user.name} 9 | p Alias Name: #{user.alias} 10 | p Phone: #{user.phone} 11 | p UUID: #{user.uid} 12 | -------------------------------------------------------------------------------- /server/views/user/register.jade: -------------------------------------------------------------------------------- 1 | extends ../layout 2 | 3 | block content 4 | div.container 5 | h2 #{title} 6 | #{errors.message} 7 | form(role='form', method="post", action="/register") 8 | div(class="form-group") 9 | label(from="name") Name 10 | input(type="text", name="name", class="form-control") 11 | 12 | div(class="form-group") 13 | label(from="password") Password 14 | input(type="password", name="password", class="form-control") 15 | 16 | div(class="form-group") 17 | label(from="phone") Phone 18 | input(type="phone", name="phone", class="form-control") 19 | 20 | div(class="form-group") 21 | label(from="alias") Alias 22 | input(type="alias", name="alias", class="form-control") 23 | 24 | 25 | div.actions 26 | br 27 | input(type="submit", value="add", class="btn btn-primary") 28 | 29 | -------------------------------------------------------------------------------- /test/mocha.opts: -------------------------------------------------------------------------------- 1 | --require chai 2 | --require sinon 3 | --ui bdd 4 | --growl 5 | --colors 6 | --globals s 7 | --require test/spec_helper 8 | -------------------------------------------------------------------------------- /test/models/user_spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Bluebird = require('bluebird'); 4 | var expect = require('chai').expect; 5 | 6 | describe('User Model', function () { 7 | var models = null; 8 | beforeEach(function () { 9 | models = require('../../models'); 10 | 11 | return Bluebird.all([ 12 | models.User.destroy({truncate: true}) 13 | ]); 14 | }); 15 | 16 | it('should create user successful', function (done) { 17 | models.User.create({ 18 | name: 'root', 19 | password: 'root', 20 | expiration: '2016-03-03', 21 | uuid: '84e824cb-bfae-4d95-a76d-51103c556057', 22 | phone: '12345678901', 23 | alias: 'fengda' 24 | }).then(function () { 25 | models.User.findOne({where: {name: 'root'}}).then(function(user){ 26 | if(user.name === 'root') { 27 | done(); 28 | } 29 | }) 30 | }) 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /test/server/app_spec.js: -------------------------------------------------------------------------------- 1 | var helper = require('../spec_helper'); 2 | var mqtt = require('mqtt'); 3 | var request = require('request'); 4 | var coap = require('coap'); 5 | var website = "http://localhost:8899/"; 6 | var assert = require('chai').assert; 7 | var should = require('should'); 8 | var supertest = require('supertest'); 9 | var WebSocket = require('ws'); 10 | var WebSocketServer = WebSocket.Server; 11 | var env = require("../../app.js"); 12 | 13 | describe('Application Services Test', function () { 14 | var app, server; 15 | app = env.configure(); 16 | 17 | before(function () { 18 | var models = require('../../models'); 19 | models.User.create({ 20 | name: 'phodal', 21 | password: 'phodal', 22 | expiration: '2016-03-03', 23 | uuid: '84e824cb-bfae-4d95-a76d-51103c556057', 24 | phone: '12345678901', 25 | alias: 'fengda' 26 | }); 27 | 28 | server = app.listen(8899, function () { 29 | }); 30 | }); 31 | 32 | after(function () { 33 | server.close(); 34 | }); 35 | 36 | var agent = supertest.agent(app); 37 | var agent2 = supertest.agent(app); 38 | 39 | it("should able load the home page", function (done) { 40 | agent 41 | .get('/') 42 | .expect('Content-Type', /html/) 43 | .expect(200, done); 44 | }); 45 | 46 | it("should able goto register page", function (done) { 47 | agent 48 | .get('/register') 49 | .expect('Content-Type', /html/) 50 | .expect(200, done); 51 | }); 52 | 53 | it("should redirect when visit profile without login", function (done) { 54 | agent 55 | .get('/users/root') 56 | .expect(302, done); 57 | }); 58 | 59 | it("should able to register with lan", function (done) { 60 | agent 61 | .post('/register') 62 | .send({name: 'lan', password: 'lan', phone: '1234567890', alias: "something"}) 63 | .expect(200, done); 64 | }); 65 | 66 | it("should unable to register with lan", function (done) { 67 | agent 68 | .post('/register') 69 | .send({name: 'root', password: 'root', phone: '1234567890', alias: "something"}) 70 | .end(function (err, res) { 71 | done(); 72 | }) 73 | }); 74 | 75 | it("should able to login with lan", function (done) { 76 | agent 77 | .post('/login') 78 | .send({name: 'lan', password: 'lan'}) 79 | .expect(200, done); 80 | }); 81 | 82 | 83 | it("should unable to login with lanting", function (done) { 84 | agent 85 | .post('/login') 86 | .send({name: 'lanting', password: 'lan'}) 87 | .expect(302, done); 88 | }); 89 | 90 | it("should able to visit user profile", function (done) { 91 | agent 92 | .get('/users/lan') 93 | .end(function (err, res) { 94 | res.statusCode.should.be.equal(200); 95 | done(); 96 | }); 97 | }); 98 | 99 | it("should redirect to homepage", function (done) { 100 | agent2 101 | .get('/logout') 102 | .expect(302, done); 103 | }); 104 | }); 105 | -------------------------------------------------------------------------------- /test/server/coap_spec.js: -------------------------------------------------------------------------------- 1 | var helper = require('../spec_helper'); 2 | var mqtt = require('mqtt'); 3 | var request = require('request'); 4 | var coap = require('coap'); 5 | var website = "http://localhost:8899/"; 6 | var assert = require('chai').assert; 7 | var should = require('should'); 8 | var supertest = require('supertest'); 9 | var WebSocket = require('ws'); 10 | var WebSocketServer = WebSocket.Server; 11 | var env = require("../../app.js"); 12 | var bl = require('bl'); 13 | 14 | describe('CoAP Services Test', function () { 15 | var app, server, coapServer; 16 | app = env.configure(); 17 | 18 | before(function () { 19 | coapServer = coap.createServer(app.coap).listen(5683, function () { 20 | }); 21 | }); 22 | 23 | after(function () { 24 | coapServer.close(); 25 | }); 26 | 27 | it('should able connect to coap server', function (done) { 28 | var request = coap.request; 29 | var req = request({hostname: 'localhost', port: 5683, pathname: 'device', method: 'GET', query: 'root:root'}); 30 | 31 | req.on('response', function (res) { 32 | if (res.code === '2.05') { 33 | done(); 34 | } 35 | }); 36 | 37 | req.end(); 38 | }); 39 | 40 | it('should unable connect to coap server when username error', function (done) { 41 | var req = coap.request('coap://localhost/device?phodal1:phodal'); 42 | 43 | req.on('response', function (res) { 44 | if (res.code === '4.03') { 45 | done(); 46 | } 47 | }); 48 | 49 | req.end(); 50 | }); 51 | 52 | it('should unable connect to coap server when not exist auth', function (done) { 53 | var req = coap.request('coap://localhost/hello'); 54 | 55 | req.on('response', function (res) { 56 | if (res.code === '4.05') { 57 | done(); 58 | } 59 | }); 60 | 61 | req.end(); 62 | }); 63 | 64 | it('should unable connect to coap server when password error', function (done) { 65 | var req = coap.request('coap://localhost/device?phodal:root'); 66 | 67 | req.on('response', function (res) { 68 | if (res.code === '4.03') { 69 | done(); 70 | } 71 | }); 72 | 73 | req.end(); 74 | }); 75 | 76 | it('should return not support when try delete method', function (done) { 77 | var request = coap.request; 78 | var req = request({hostname: 'localhost', port: 5683, pathname: '', method: 'DELETE'}); 79 | 80 | req.on('response', function (res) { 81 | if (JSON.parse(res.payload.toString()).method === "not support") { 82 | done(); 83 | } 84 | }); 85 | 86 | req.end(); 87 | }); 88 | 89 | it('should able to post data with auth', function (done) { 90 | var request = coap.request; 91 | var req = request({hostname: 'localhost', port: 5683, pathname: 'device/phodal', method: 'POST', query: 'phodal:phodal'}); 92 | 93 | var payload = { 94 | title: 'this is a test payload', 95 | body: 'containing nothing useful' 96 | }; 97 | 98 | req.setHeader("Accept", "application/json"); 99 | req.write(JSON.stringify(payload)); 100 | req.on('response', function (res) { 101 | if (res.code === '2.01') { 102 | done(); 103 | } 104 | }); 105 | 106 | req.end(); 107 | }); 108 | 109 | it('should not able to post data with auth', function (done) { 110 | var request = coap.request; 111 | var req = request({hostname: 'localhost', port: 5683, pathname: 'device/phodal', method: 'POST', query: 'phodal:root'}); 112 | 113 | var payload = { 114 | title: 'this is a test payload', 115 | body: 'containing nothing useful' 116 | }; 117 | 118 | req.setHeader("Accept", "application/json"); 119 | req.write(JSON.stringify(payload)); 120 | req.on('response', function (res) { 121 | if (res.code === '4.03') { 122 | done(); 123 | } 124 | }); 125 | 126 | req.end(); 127 | }); 128 | 129 | it('should able to POST data with auth', function (done) { 130 | var request = coap.request; 131 | var req = request({hostname: 'localhost', port: 5683, pathname: 'device/2', method: 'POST', query: 'phodal:phodal'}); 132 | 133 | var payload = { 134 | device: "this is device 2" 135 | }; 136 | 137 | req.setHeader("Accept", "application/json"); 138 | req.write(JSON.stringify(payload)); 139 | req.on('response', function (res) { 140 | if (res.code === '2.01') { 141 | done(); 142 | } 143 | }); 144 | 145 | req.end(); 146 | }); 147 | 148 | it('should able to POST data with auth', function (done) { 149 | var request = coap.request; 150 | var req = request({hostname: 'localhost', port: 5683, pathname: 'topic/2', method: 'POST', query: 'phodal:phodal'}); 151 | 152 | var payload = { 153 | topic: "this is topic 2" 154 | }; 155 | 156 | req.setHeader("Accept", "application/json"); 157 | req.write(JSON.stringify(payload)); 158 | req.on('response', function (res) { 159 | if (res.code === '2.01') { 160 | done(); 161 | } 162 | }); 163 | 164 | req.end(); 165 | }); 166 | 167 | it('should unable to POST data with username error', function (done) { 168 | var request = coap.request; 169 | var req = request({hostname: 'localhost', port: 5683, pathname: 'device/1', method: 'POST', query: 'root:phodal'}); 170 | 171 | var payload = { 172 | device: 'this is a device 1' 173 | }; 174 | 175 | req.setHeader("Accept", "application/json"); 176 | req.write(JSON.stringify(payload)); 177 | req.on('response', function (res) { 178 | if (res.code === '4.03') { 179 | done(); 180 | } 181 | }); 182 | 183 | req.end(); 184 | }); 185 | 186 | it('should return phodal"s device 2 result', function (done) { 187 | var request = coap.request; 188 | var req = request({hostname: 'localhost', port: 5683, pathname: 'device/2', method: 'GET', query: 'phodal:phodal'}); 189 | 190 | req.on('response', function (res) { 191 | var topicResult = JSON.parse(res.payload.toString()).result[0].data; 192 | if (JSON.parse(topicResult).device === "this is device 2") { 193 | done(); 194 | } 195 | }); 196 | 197 | req.end(); 198 | }); 199 | 200 | it('should not return root"s device 2 result', function (done) { 201 | var request = coap.request; 202 | var req = request({hostname: 'localhost', port: 5683, pathname: 'device/2', method: 'GET', query: 'root:root'}); 203 | 204 | req.on('response', function (res) { 205 | var topicResult = JSON.parse(res.payload.toString()).result; 206 | if (topicResult.toString() === "") { 207 | done(); 208 | } 209 | }); 210 | 211 | req.end(); 212 | }); 213 | 214 | it('should able to update data with auth', function (done) { 215 | var request = coap.request; 216 | var req = request({hostname: 'localhost', port: 5683, pathname: 'device/2', method: 'PUT', query: 'phodal:phodal'}); 217 | req.write(JSON.stringify({topic: "this is device UPDATE"})); 218 | req.on('response', function (res) { 219 | if (res.code === '2.01') { 220 | done(); 221 | } 222 | }); 223 | req.end(); 224 | }); 225 | }); 226 | -------------------------------------------------------------------------------- /test/server/http_spec.js: -------------------------------------------------------------------------------- 1 | var helper = require('../spec_helper'); 2 | var mqtt = require('mqtt'); 3 | var request = require('request'); 4 | var coap = require('coap'); 5 | var website = "http://localhost:8899/"; 6 | var assert = require('chai').assert; 7 | var should = require('should'); 8 | var supertest = require('supertest'); 9 | var WebSocket = require('ws'); 10 | var WebSocketServer = WebSocket.Server; 11 | var env = require("../../app.js"); 12 | 13 | describe('HTTP Services Test', function () { 14 | var app, server; 15 | app = env.configure(); 16 | 17 | before(function () { 18 | server = app.listen(8899, function () { 19 | }); 20 | }); 21 | 22 | after(function () { 23 | server.close(); 24 | }); 25 | 26 | it('should able open homepage', function (done) { 27 | request.get('http://localhost:8899/', 28 | function (error, response) { 29 | if (response.statusCode === 200) { 30 | done(); 31 | } 32 | }) 33 | }); 34 | 35 | it('should able connect to http server', function (done) { 36 | request.get('http://localhost:8899/topics/test', { 37 | 'auth': { 38 | 'username': 'root', 39 | 'password': 'root' 40 | } 41 | }, 42 | function (error, response) { 43 | if (response.statusCode === 200) { 44 | done(); 45 | } 46 | }) 47 | }); 48 | 49 | it('should unable connect to http server when username error', function (done) { 50 | request.get('http://localhost:8899/topics/test', { 51 | 'auth': { 52 | 'username': 'root1', 53 | 'password': 'phodal' 54 | } 55 | }, 56 | function (error, response) { 57 | if (response.statusCode === 403) { 58 | done(); 59 | } 60 | }) 61 | }); 62 | 63 | it('should unable connect to http server when password error', function (done) { 64 | request.get('http://localhost:8899/topics/test', { 65 | 'auth': { 66 | 'username': 'root', 67 | 'password': 'phodal' 68 | } 69 | }, 70 | function (error, response) { 71 | if (response.statusCode === 403) { 72 | done(); 73 | } 74 | }) 75 | }); 76 | 77 | it('should return 401 when user not auth', function (done) { 78 | request('http://localhost:8899/topics/test', 79 | function (error, response) { 80 | if (response.statusCode === 401) { 81 | done(); 82 | } 83 | }) 84 | }); 85 | 86 | 87 | it('should able put response', function (done) { 88 | request({ 89 | uri: 'http://phodal:phodal@localhost:8899/topics/test', 90 | method: 'PUT', 91 | multipart: [ 92 | { 93 | 'content-type': 'application/json', 94 | body: JSON.stringify({ 95 | foo: 'bar', 96 | _attachments: {'message.txt': {follows: true, length: 18, 'content_type': 'text/plain'}} 97 | }) 98 | }, 99 | {body: 'I am an attachment'}] 100 | }, function (error, response) { 101 | if (response.statusCode) { 102 | done(); 103 | } 104 | }) 105 | }); 106 | 107 | it('should able put response', function (done) { 108 | request.put('http://localhost:8899/topics/test', { 109 | 'auth': { 110 | 'username': 'phodal', 111 | 'password': 'phodal', 112 | 'sendImmediately': true 113 | } 114 | }, function (error, response) { 115 | if (response.statusCode === 204) { 116 | done(); 117 | } 118 | }); 119 | }); 120 | 121 | it('should able put response when username error', function (done) { 122 | request.put('http://localhost:8899/topics/test', { 123 | 'auth': { 124 | 'username': 'phodal1', 125 | 'password': 'phodal', 126 | 'sendImmediately': true 127 | } 128 | }, function (error, response) { 129 | if (response.statusCode === 403) { 130 | done(); 131 | } 132 | }) 133 | }); 134 | 135 | it('should not able put response when password error', function (done) { 136 | request.put('http://localhost:8899/topics/test', { 137 | 'auth': { 138 | 'username': 'phodal', 139 | 'password': 'root', 140 | 'sendImmediately': true 141 | } 142 | }, function (error, response) { 143 | if (response.statusCode === 403) { 144 | done(); 145 | } 146 | }) 147 | }); 148 | 149 | it('should able post response', function (done) { 150 | request({ 151 | uri: 'http://localhost:8899/topics/test', 152 | method: 'POST', 153 | 'auth': { 154 | 'username': 'phodal', 155 | 'password': 'phodal', 156 | 'sendImmediately': true 157 | } 158 | }, function (error, response) { 159 | if (response.statusCode === 204) { 160 | done(); 161 | } 162 | }) 163 | }); 164 | 165 | it('should able post response', function (done) { 166 | request({ 167 | uri: 'http://localhost:8899/device/test', 168 | method: 'POST', 169 | 'auth': { 170 | 'username': 'phodal', 171 | 'password': 'phodal', 172 | 'sendImmediately': true 173 | } 174 | }, function (error, response) { 175 | if (response.statusCode === 204) { 176 | done(); 177 | } 178 | }) 179 | }); 180 | }); 181 | -------------------------------------------------------------------------------- /test/server/mqtt_spec.js: -------------------------------------------------------------------------------- 1 | var mqtt = require('mqtt'); 2 | var request = require('request'); 3 | var should = require('should'); 4 | var supertest = require('supertest'); 5 | var env = require("../../app.js"); 6 | 7 | describe('MQTT Services Test', function () { 8 | var app, server, mqttServer; 9 | app = env.configure(); 10 | 11 | beforeEach(function () { 12 | mqttServer = mqtt.MqttServer(app.mqtt).listen(1883, function () { 13 | }); 14 | }); 15 | 16 | afterEach(function () { 17 | mqttServer.close(); 18 | }); 19 | 20 | describe('connect test', function () { 21 | it('should able connect to mqtt server', function (done) { 22 | var client = mqtt.connect('http://127.0.0.1', { 23 | username: 'root', 24 | password: 'root' 25 | }); 26 | 27 | client.on('connect', function (packet) { 28 | if (packet.returnCode === 0) { 29 | client.end(); 30 | done(); 31 | } 32 | }); 33 | }); 34 | 35 | //it('should unable connect to mqtt server when lost password', function (done) { 36 | // var client = mqtt.connect('http://127.0.0.1', { 37 | // username: 'root' 38 | // }); 39 | // 40 | // client.on('error', function () { 41 | // client.end(); 42 | // done(); 43 | // }); 44 | //}); 45 | // 46 | //it('should unable connect to mqtt server when username & password error', function (done) { 47 | // var client = mqtt.connect('http://127.0.0.1', { 48 | // username: 'root', 49 | // password: 'password' 50 | // }); 51 | // 52 | // client.on('error', function () { 53 | // client.end(); 54 | // done(); 55 | // }); 56 | //}); 57 | }); 58 | 59 | it('should able to sub/pub message', function (done) { 60 | var client = mqtt.connect('http://127.0.0.1', { 61 | username: 'root', 62 | password: 'root' 63 | }); 64 | 65 | client.on('connect', function () { 66 | client.subscribe('device/1'); 67 | var payload = '{"temperature": 3"}'; 68 | client.publish('device/1', payload); 69 | client.on('message', function (topic, message) { 70 | if ((message.toString()) === payload) { 71 | client.end(); 72 | done(); 73 | } 74 | }); 75 | }); 76 | }); 77 | }); 78 | -------------------------------------------------------------------------------- /test/server/ws_spec.js: -------------------------------------------------------------------------------- 1 | var helper = require('../spec_helper'); 2 | var mqtt = require('mqtt'); 3 | var request = require('request'); 4 | var coap = require('coap'); 5 | var website = "http://localhost:8899/"; 6 | var assert = require('chai').assert; 7 | var should = require('should'); 8 | var supertest = require('supertest'); 9 | var WebSocket = require('ws'); 10 | var WebSocketServer = WebSocket.Server; 11 | var env = require("../../app.js"); 12 | 13 | describe('WebSocket Services Test', function () { 14 | var app, server, webSocketServer; 15 | app = env.configure(); 16 | 17 | before(function () { 18 | server = app.listen(8899, function () { 19 | }); 20 | webSocketServer = new WebSocketServer({port: 8898}); 21 | app.websocket(webSocketServer); 22 | }); 23 | 24 | after(function () { 25 | webSocketServer.close(); 26 | server.close(); 27 | }); 28 | 29 | it('basic connection', function (done) { 30 | var ws = new WebSocket('ws://root:root@localhost:8898/'); 31 | 32 | ws.on('open', function open() { 33 | ws.send('something'); 34 | }); 35 | 36 | ws.on('message', function (data) { 37 | if (data === "connection") { 38 | done(); 39 | } 40 | }); 41 | }); 42 | 43 | it('auth failure', function (done) { 44 | var ws = new WebSocket('ws://localhost:8898/'); 45 | 46 | ws.on('open', function open() { 47 | ws.send('something'); 48 | }); 49 | 50 | ws.on('message', function (data) { 51 | if (data === '{"error":"no auth"}') { 52 | done(); 53 | } 54 | }); 55 | }); 56 | 57 | it('auth failure when user not exist', function (done) { 58 | var ws = new WebSocket('ws://phodal1:phodal@localhost:8898/'); 59 | 60 | ws.on('open', function open() { 61 | ws.send('something'); 62 | }); 63 | 64 | ws.on('message', function (data) { 65 | if (data === '{"error":"auth failure"}') { 66 | done(); 67 | } 68 | }); 69 | }); 70 | 71 | it('auth failure when password incorrect', function (done) { 72 | var ws = new WebSocket('ws://phodal:phodal1@localhost:8898/'); 73 | 74 | ws.on('open', function open() { 75 | ws.send('something'); 76 | }); 77 | 78 | ws.on('message', function (data) { 79 | if (data === '{"error":"auth failure"}') { 80 | done(); 81 | } 82 | }); 83 | }); 84 | }); 85 | -------------------------------------------------------------------------------- /test/spec_helper.js: -------------------------------------------------------------------------------- 1 | var async, env; 2 | 3 | env = require("../app.js"); 4 | 5 | function createUser() { 6 | 7 | } 8 | module.exports.globalSetup = function() { 9 | if (this.app != null) { 10 | return; 11 | } 12 | 13 | this.app = env.app; 14 | return env.configure(); 15 | }; 16 | -------------------------------------------------------------------------------- /test_scripts/coap_get_test.js: -------------------------------------------------------------------------------- 1 | var coap = require('coap'); 2 | var request = coap.request; 3 | var bl = require('bl'); 4 | var req = request({hostname: 'localhost', port: 5683, pathname: 'topic', method: 'GET', query: 'root:root'}); 5 | 6 | req.setHeader("Accept", "application/json"); 7 | req.on('response', function (res) { 8 | res.pipe(bl(function(err, data) { 9 | console.log(data.toString()); 10 | process.exit(0); 11 | })); 12 | }); 13 | 14 | req.end(); 15 | -------------------------------------------------------------------------------- /test_scripts/coap_post_test.js: -------------------------------------------------------------------------------- 1 | var coap = require('coap'); 2 | var request = coap.request; 3 | var bl = require('bl'); 4 | var req = request({hostname: 'localhost',port:5683,pathname: 'topic/zero',method: 'POST', query: 'phodal:phodal'}); 5 | 6 | var payload = { 7 | title: 'this is a test payload', 8 | body: 'containing nothing useful' 9 | }; 10 | 11 | req.setHeader("Accept", "application/json"); 12 | req.setOption('Block2', [new Buffer('phodal'), new Buffer('phodal')]); 13 | req.write(JSON.stringify(payload)); 14 | req.on('response', function(res) { 15 | console.log(res.payload.toString); 16 | res.pipe(bl(function(err, data) { 17 | console.log(data); 18 | process.exit(0); 19 | })); 20 | 21 | }); 22 | 23 | req.end(); 24 | -------------------------------------------------------------------------------- /test_scripts/ws_test.js: -------------------------------------------------------------------------------- 1 | var WebSocket = require('ws'); 2 | var ws = new WebSocket('ws://127.0.0.1:8898/topics'); 3 | 4 | ws.on('open', function open() { 5 | ws.send('something'); 6 | console.log('open'); 7 | }); 8 | 9 | ws.on('message', function (data) { 10 | console.log(data); 11 | process.exit(1); 12 | ws.close(); 13 | }); 14 | -------------------------------------------------------------------------------- /utils/common.js: -------------------------------------------------------------------------------- 1 | function isJson (str) { 2 | try { 3 | JSON.parse(str); 4 | } catch (e) { 5 | return false; 6 | } 7 | return true; 8 | } 9 | 10 | module.exports.isJson = isJson; 11 | --------------------------------------------------------------------------------