├── .bash_profile ├── .bowerrc ├── .gitattributes ├── .gitignore ├── .jshintrc ├── .nodemonignore ├── Gruntfile.js ├── LICENCE.md ├── Makefile ├── README.md ├── bower.json ├── karma-e2e.conf.js ├── karma.conf.js ├── node-inspector.js ├── package.json ├── protractor.conf.js ├── server ├── config │ ├── config-sample.js │ ├── disposableEmailProviders.txt │ └── errorMessages.js ├── controllers │ ├── authService.js │ ├── loggerService.js │ ├── mailerService.js │ ├── userService.js │ └── validationService.js ├── index.js ├── lib │ ├── emailTemplates │ │ ├── invite │ │ │ ├── html.ejs │ │ │ └── style.css │ │ ├── password_reset │ │ │ ├── html.ejs │ │ │ └── style.css │ │ └── unknown_user │ │ │ ├── html.ejs │ │ │ └── style.css │ ├── errors.js │ └── passport.js ├── models │ ├── loginToken.js │ ├── oAuthProvider.js │ └── user.js ├── routes │ └── index.js └── test │ ├── coverage │ └── blanket.js │ └── unit │ ├── account.js │ ├── server.js │ └── user.js ├── src ├── .buildignore ├── .htaccess ├── 404.tpl.html ├── apple-touch-icon-114x114-precomposed.png ├── apple-touch-icon-144x144-precomposed.png ├── apple-touch-icon-57x57-precomposed.png ├── apple-touch-icon-72x72-precomposed.png ├── apple-touch-icon-precomposed.png ├── apple-touch-icon.png ├── favicon.ico ├── font │ ├── FontAwesome.otf │ ├── fontawesome-webfont.eot │ ├── fontawesome-webfont.svg │ ├── fontawesome-webfont.ttf │ └── fontawesome-webfont.woff ├── home.tpl.html ├── images │ ├── facebook.png │ ├── glyphicons-halflings-white.png │ ├── glyphicons-halflings.png │ └── google.png ├── index.html ├── robots.txt ├── scripts │ ├── app.js │ ├── common │ │ ├── account │ │ │ ├── account.js │ │ │ ├── assets │ │ │ │ └── templates │ │ │ │ │ ├── account.tpl.html │ │ │ │ │ ├── accountLinkedAccounts.tpl.html │ │ │ │ │ ├── accountMenu.tpl.html │ │ │ │ │ ├── accountPassword.tpl.html │ │ │ │ │ ├── accountSecurity.tpl.html │ │ │ │ │ ├── accountShow.tpl.html │ │ │ │ │ ├── benefits.tpl.html │ │ │ │ │ ├── changeForgottenPassword.tpl.html │ │ │ │ │ ├── forgotPassword.tpl.html │ │ │ │ │ ├── register.tpl.html │ │ │ │ │ ├── registerShow.tpl.html │ │ │ │ │ ├── resendActivation.tpl.html │ │ │ │ │ └── terms.tpl.html │ │ │ ├── register.js │ │ │ └── tests │ │ │ │ ├── account.spec.js │ │ │ │ └── register.spec.js │ │ ├── security │ │ │ ├── authorization.js │ │ │ ├── index.js │ │ │ ├── interceptor.js │ │ │ ├── login │ │ │ │ ├── LoginFormController.js │ │ │ │ ├── assets │ │ │ │ │ └── templates │ │ │ │ │ │ ├── form.tpl.html │ │ │ │ │ │ ├── login.tpl.html │ │ │ │ │ │ └── toolbar.tpl.html │ │ │ │ ├── login.js │ │ │ │ ├── tests │ │ │ │ │ ├── login-form.spec.js │ │ │ │ │ └── login-toolbar.spec.js │ │ │ │ └── toolbar.js │ │ │ ├── retryQueue.js │ │ │ ├── security.js │ │ │ └── tests │ │ │ │ ├── authorization.spec.js │ │ │ │ ├── interceptor.spec.js │ │ │ │ ├── retry-queue.spec.js │ │ │ │ └── security.spec.js │ │ └── services │ │ │ ├── breadcrumbs.js │ │ │ ├── exceptionHandler.js │ │ │ ├── httpRequestTracker.js │ │ │ ├── i18nNotifications.js │ │ │ ├── localizedMessages.js │ │ │ ├── notifications.js │ │ │ ├── tests │ │ │ ├── breadcrumbs.spec.js │ │ │ ├── exceptionHandler.spec.js │ │ │ ├── httpRequestTracker.spec.js │ │ │ ├── i18nNotifications.spec.js │ │ │ ├── localizedMessages.spec.js │ │ │ └── notifications.spec.js │ │ │ └── titleService.js │ ├── systemMessages.js │ └── templates.js └── styles │ ├── bootstrap.css │ ├── font-awesome.css │ └── main.scss └── test ├── account └── register.js ├── index.html ├── runner.html └── scenarios.js /.bash_profile: -------------------------------------------------------------------------------- 1 | 2 | export JAVA_HOME=$(/usr/libexec/java_home) 3 | -------------------------------------------------------------------------------- /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "src/bower_components" 3 | } 4 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | .tmp 4 | .sass-cache 5 | src/bower_components 6 | server/config/config.js 7 | server/coverage.html 8 | dump.rdb 9 | .DS_Store 10 | *.log 11 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "curly": false, 3 | "node": true, 4 | "browser": true, 5 | "esnext": true, 6 | "bitwise": true, 7 | "camelcase": false, 8 | "eqeqeq": true, 9 | "immed": true, 10 | "indent": 2, 11 | "latedef": true, 12 | "newcap": true, 13 | "noarg": true, 14 | "quotmark": "true", 15 | "regexp": true, 16 | "undef": true, 17 | "unused": true, 18 | "strict": true, 19 | "laxcomma": true, 20 | "trailing": true, 21 | "smarttabs": true, 22 | "globals": { 23 | "angular": false, 24 | "describe": false, 25 | "it": false, 26 | "beforeEach": false, 27 | "before": false, 28 | "after": false, 29 | "afterEach": false, 30 | "expect": false, 31 | "inject": false, 32 | "spyOn": false, 33 | "jasmine": false 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /.nodemonignore: -------------------------------------------------------------------------------- 1 | # Generated by grunt-nodemon 2 | README.md 3 | Gruntfile.js 4 | node-inspector.js 5 | karma.conf.js 6 | /.git/ 7 | /node_modules/ 8 | /src/ 9 | /dist/ 10 | /test/ 11 | /.tmp 12 | /.sass-cache 13 | *.txt 14 | -------------------------------------------------------------------------------- /LICENCE.md: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2014 Rory Madden 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | REPORTER = spec 2 | TESTS = $(shell find server/test/unit -name "*.js") 3 | 4 | test: 5 | @NODE_ENV=test mocha $(TESTS) \ 6 | --reporter $(REPORTER) 7 | 8 | test-brk: 9 | @NODE_ENV=test mocha --debug-brk $(TESTS) \ 10 | --reporter $(REPORTER) 11 | 12 | test-cov: lib-cov 13 | @LIB_COV=1 $(MAKE) test REPORTER=html-cov > coverage.html 14 | 15 | lib-cov: 16 | @jscoverage lib lib-cov 17 | 18 | clean: 19 | rm -f coverage.html 20 | rm -fr lib-cov 21 | 22 | docs: docclean gendocs 23 | 24 | gendocs: 25 | dox-foundation --source lib --target docs 26 | 27 | 28 | docclean: 29 | rm -f ./docs/*.{1,html,json} 30 | rm -f ./docs/*/*.{1,html,json} 31 | 32 | .PHONY: test test-cov clean docs docclean gendocs -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Angular - Neo4j 2 | 3 | Sample single page web app application (AngularJS front end, node.js server and Neo4j database) with authentication functionality: 4 | 5 | 1. Register a new user 6 | 2. Registration 7 | 3. Emailed activation code 8 | 4. Re-send activation code 9 | 5. Activation handling 10 | 6. Forgot Password - email link 11 | 7. Reset Password 12 | 2. Login functionality 13 | 3. Username and password 14 | 4. Facebook 15 | 5. Google 16 | 6. Persistent login (remember me) 17 | 3. Account pages 18 | 4. View / Update profile 19 | 5. Update Password 20 | 6. View login cookies (if logged in from multiple computers) 21 | 7. Remove login cookies 22 | 8. View linked social media accounts 23 | 24 | ## Getting Started 25 | 26 | You need to have your development environment set up for this code to run. 27 | 28 | ### Redis 29 | First you need a redis-server. Redis is used for session management. If you want ot swap out the redis server for a different session managemetn then change the server/index.html file. 30 | 31 | To install redis follow the instructions on http://redis.io/download 32 | 33 | Start the redis server (`redis-server` command) 34 | 35 | ### Neo4j 36 | The database used is Neo4j. This is the leading open source graph database. To install it follow the instructions at http://www.neo4j.org/download. This code is tested against version 2.0 M04. For Mac users I find brew very useful "brew install neo4j". You will need to run a separate test environment. For this you will need to download the same version that you have installed and follow the instructions at http://docs.neo4j.org/chunked/stable/server-installation.html#_multiple_server_instances_on_one_machine. If you have already installed a first instance then just follow along with the second instance instructions. For the second instance use port 7475. 37 | 38 | Start your two neo4j instances 39 | 40 | ### Repo 41 | 42 | Now clone this repo to your workspace and install the dependencies 43 | 44 | 1. 'cd angular-neo4j' 45 | 2. `npm install` 46 | 47 | There is an issue with the 'angular-ui-router' bower install at the time of writing (This may be fixed by the time you read this). You need to build the repository: 48 | 49 | 'cd src/bower_components/angular-ui-router' 50 | 'npm install' 51 | 'grunt' 52 | 'cd ../../../' 53 | 54 | 55 | ### Configuration 56 | Finally set up the config file. You will need to copy the file server/config/config-sample.js file to server/config/config.js. Fill in all of the details as appropriate. 57 | 58 | The final step is to run `grunt build`. 59 | 60 | Start the server with `grunt server` 61 | 62 | ### Email Templates 63 | If you try to register it should work but you'll notice a very generic email response. If you want to customise the email templates then navigate to server/lib/emailTemplates 64 | 65 | ### Issues 66 | There are rough edges. Have a look at the existing bugs and if you can offer any assistance or advice that would also be appreciated. 67 | 68 | If you come across and defects or have some changes please raise an issue along with as much detail as possible. Pull requests are always welcome. 69 | 70 | Good luck 71 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angularNeo4j", 3 | "version": "0.0.0", 4 | "dependencies": { 5 | "angular": "~1.0.7", 6 | "json3": "~3.2.4", 7 | "bootstrap-sass": "~2.3.1", 8 | "es5-shim": "~2.0.8", 9 | "angular-resource": "~1.0.7", 10 | "angular-cookies": "~1.0.7", 11 | "angular-sanitize": "~1.0.7", 12 | "angular-bootstrap": "~0.3.0", 13 | "angular-date-dropdowns": "latest", 14 | "angular-ui-router": "~0.0.1", 15 | "font-awesome": "~3.2.1" 16 | }, 17 | "devDependencies": { 18 | "angular-mocks": "~1.0.7", 19 | "angular-scenario": "~1.0.7", 20 | "jquery": "~1.8.2" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /karma-e2e.conf.js: -------------------------------------------------------------------------------- 1 | // Karma E2E configuration 2 | // 3 | // TODO: works with version 0.3 of grunt-karma but not with newer ones. Wait for protractor 4 | 5 | // base path, that will be used to resolve files and exclude 6 | basePath = ''; 7 | 8 | // list of files / patterns to load in the browser 9 | files = [ 10 | ANGULAR_SCENARIO, 11 | ANGULAR_SCENARIO_ADAPTER, 12 | 'test/*.js', 13 | 'test/**/*.js' 14 | ]; 15 | 16 | // list of files to exclude 17 | exclude = []; 18 | 19 | // test results reporter to use 20 | // possible values: dots || progress || growl 21 | reporters = ['progress']; 22 | 23 | // web server port 24 | port = 8080; 25 | 26 | // cli runner port 27 | runnerPort = 9100; 28 | 29 | // enable / disable colors in the output (reporters and logs) 30 | colors = true; 31 | 32 | // level of logging 33 | // possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG 34 | logLevel = LOG_INFO; 35 | 36 | // enable / disable watching file and executing tests whenever any file changes 37 | autoWatch = false; 38 | 39 | // Start these browsers, currently available: 40 | // - Chrome 41 | // - ChromeCanary 42 | // - Firefox 43 | // - Opera 44 | // - Safari (only Mac) 45 | // - PhantomJS 46 | // - IE (only Windows) 47 | browsers = ['Chrome']; 48 | 49 | // If browser does not capture in given timeout [ms], kill it 50 | captureTimeout = 5000; 51 | 52 | // Continuous Integration mode 53 | // if true, it capture browsers, run tests and exit 54 | singleRun = false; 55 | 56 | proxies = { 57 | '/': 'http://localhost:9000/' 58 | }; 59 | urlRoot = '_karma_'; 60 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration 2 | 3 | // base path, that will be used to resolve files and exclude 4 | basePath = ''; 5 | 6 | // list of files / patterns to load in the browser 7 | files = [ 8 | JASMINE, 9 | JASMINE_ADAPTER, 10 | 'src/bower_components/jquery/jquery.js', 11 | 'src/bower_components/angular/angular.js', 12 | 'src/bower_components/angular-bootstrap/ui-bootstrap.js', 13 | 'src/bower_components/angular-sanitize/angular-sanitize.js', 14 | 'src/bower_components/angular-mocks/angular-mocks.js', 15 | 'src/bower_components/angular-ui-router/release/angular-ui-router.min.js', 16 | 'src/bower_components/angular-date-dropdowns/directive.js', 17 | 'src/scripts/*.js', 18 | 'src/scripts/**/*.js', 19 | // 'test/mock/**/*.js', 20 | // 'test/spec/**/*.js', 21 | // 'dist/scripts/templates.js' 22 | ]; 23 | 24 | // list of files to exclude 25 | exclude = []; 26 | 27 | // test results reporter to use 28 | // possible values: dots || progress || growl 29 | reporters = ['dots']; 30 | 31 | // web server port 32 | port = 8080; 33 | 34 | // cli runner port 35 | runnerPort = 9100; 36 | 37 | // enable / disable colors in the output (reporters and logs) 38 | colors = true; 39 | 40 | // level of logging 41 | // possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG 42 | logLevel = LOG_INFO; 43 | 44 | // enable / disable watching file and executing tests whenever any file changes 45 | autoWatch = false; 46 | 47 | // Start these browsers, currently available: 48 | // - Chrome 49 | // - ChromeCanary 50 | // - Firefox 51 | // - Opera 52 | // - Safari (only Mac) 53 | // - PhantomJS 54 | // - IE (only Windows) 55 | browsers = ['Chrome']; 56 | 57 | // If browser does not capture in given timeout [ms], kill it 58 | captureTimeout = 2000; 59 | 60 | // Continuous Integration mode 61 | // if true, it capture browsers, run tests and exit 62 | singleRun = false; 63 | -------------------------------------------------------------------------------- /node-inspector.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Dummy file for grunt-nodemon to run node-inspector task 3 | */ 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-neo4j", 3 | "version": "0.1.1", 4 | "private": true, 5 | "homepage": "http://localhost:4000", 6 | "author": "Rory Madden ", 7 | "description": "A REST enabled express server", 8 | "scripts": { 9 | "start": "nodev server/index.js", 10 | "test": "make test" 11 | }, 12 | "dependencies": { 13 | "express": "3.0.x", 14 | "connect-redis": "1.4.x", 15 | "neoprene": "1.0.x", 16 | "express-validator": "0.3.x", 17 | "winston": "0.6.x", 18 | "winston-loggly": "0.6.x", 19 | "passport": "0.1.12", 20 | "passport-local": "0.1.x", 21 | "passport-google-oauth": "0.1.x", 22 | "passport-facebook": "0.1.x", 23 | "bcrypt": ">0.5.x", 24 | "nodemailer": "0.3.x", 25 | "validator": " ~> 2.0.0", 26 | "email-templates": "0.0.x", 27 | "uuid-v4": "0.0.x", 28 | "express-useragent": "0.0.x", 29 | "superagent": "~> 3.7.0" 30 | }, 31 | "devDependencies": { 32 | "grunt": "~0.4.1", 33 | "grunt-contrib-copy": "~0.4.0", 34 | "grunt-contrib-concat": "~0.3.0", 35 | "grunt-contrib-coffee": "~0.7.0", 36 | "grunt-contrib-uglify": "~0.2.0", 37 | "grunt-contrib-compass": "~0.3.0", 38 | "grunt-contrib-jshint": "~0.6.0", 39 | "grunt-contrib-cssmin": "~0.6.0", 40 | "grunt-contrib-connect": "~0.3.0", 41 | "grunt-contrib-clean": "~0.4.1", 42 | "grunt-contrib-htmlmin": "~0.1.3", 43 | "grunt-contrib-imagemin": "~0.1.4", 44 | "grunt-contrib-watch": "~0.4.3", 45 | "grunt-usemin": "~0.1.10", 46 | "grunt-rev": "~0.1.0", 47 | "grunt-karma": "~0.4.5", 48 | "grunt-open": "~0.2.0", 49 | "grunt-concurrent": "~0.3.0", 50 | "matchdep": "~0.1.1", 51 | "grunt-google-cdn": "~0.2.1", 52 | "grunt-ngmin": "~0.0.2", 53 | "grunt-html2js": "~0.1.3", 54 | "grunt-svgmin": "~0.1.0", 55 | "grunt-mocha-test": "0.4.x", 56 | "grunt-http": "0.0.2", 57 | "grunt-nodemon": "0.0.2", 58 | "grunt-env": "0.4.x", 59 | "mocha": "1.6.x", 60 | "should": "1.2.x", 61 | "supertest": "0.7.x", 62 | "blanket": "1.1.x" 63 | }, 64 | "engines": { 65 | "node": ">=0.8.0" 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /protractor.conf.js: -------------------------------------------------------------------------------- 1 | // An example configuration file. 2 | exports.config = { 3 | // ----- How to setup Selenium 4 | // There are three ways to specify how to use Selenium. Specify one of the 5 | // following: 6 | // 1. seleniumServerJar - to start Selenium Standalone locally. 7 | // 2. seleniumAddress - to connect to a Selenium server which is already 8 | // running. 9 | // 3. sauceUser/sauceKey - to use remote Selenium servers via SauceLabs. 10 | 11 | // The location of the selenium standalone server .jar file. 12 | seleniumServerJar: '~/Dropbox/Sites/dependencies/selenium-server-standalone-2.33.0.jar', 13 | // The port to start the selenium server on, or null if the server should 14 | // find its own unused port. 15 | seleniumPort: null, 16 | // Chromedriver location is used to help the selenium standalone server 17 | // find chromedriver. This will be passed to the selenium jar as 18 | // the system property webdriver.chrome.driver. If null, selenium will 19 | // attempt to find chromedriver using PATH. 20 | chromeDriver: '~/Dropbox/Sites/dependencies/chromedriver', 21 | // Additional command line options to pass to selenium. For example, 22 | // if you need to change the browser timeout, use 23 | // seleniumArgs: [-browserTimeout=60], 24 | seleniumArgs: [], 25 | 26 | // If sauceUser and sauceKey are specified, seleniumServerJar will be ignored. 27 | sauceUser: null, 28 | sauceKey: null, 29 | 30 | // The address of a running selenium server. 31 | seleniumAddress: 'http://localhost:4444/wd/hub', 32 | 33 | // Spec patterns are relative to the current working directly when 34 | // protractor is called. 35 | specs: ['test/account/register.js'], 36 | 37 | // ----- Capabilities to be passed to the webdriver instance. 38 | // For a full list of available capabilities, see 39 | // https://code.google.com/p/selenium/wiki/DesiredCapabilities 40 | capabilities: { 41 | 'browserName': 'chrome' 42 | }, 43 | 44 | // A base URL for your application under test. Calls to protractor.get() 45 | // with relative paths will be prepended with this. 46 | baseUrl: 'http://localhost:8000', 47 | 48 | // ----- Options to be passed to minijasminenode. 49 | jasmineNodeOpts: { 50 | // onComplete will be called before the driver quits. 51 | // specs: ['test/**/*.js'], 52 | onComplete: null, 53 | isVerbose: false, 54 | showColors: true, 55 | includeStackTrace: true 56 | } 57 | }; -------------------------------------------------------------------------------- /server/config/config-sample.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | module.exports = { 3 | development: { 4 | appName: "Test", 5 | email: { 6 | registration: "no_reply@test.com", 7 | info: "info@test.com" 8 | }, 9 | 10 | node: { 11 | host: "localhost", 12 | distFolder: path.resolve(__dirname, '../../app'), 13 | port: 4000 14 | }, 15 | 16 | NOT_USED_loggly: { 17 | subdomain: "REPLACE WITH LOGGLY SUBDOMAIN", 18 | inputToken: "REPLACE WITH LOGGLY INPUT TOKEN", 19 | auth: { 20 | username: "REPLACE WITH LOGGLY USERNAME", 21 | password: "REPLACE WITH LOGGLY PASSWORD" 22 | }, 23 | json: true 24 | }, 25 | 26 | NOT_USED_airbrake: { 27 | apiKey: "REPLACE WITH AIRBRAKE API" 28 | }, 29 | 30 | neo4j: { 31 | host: "localhost", 32 | port: 7474, 33 | // user: , 34 | // password: 35 | }, 36 | 37 | sess: { 38 | secret: "REPLACE WITH SECRET" 39 | }, 40 | 41 | cookie: { 42 | secret: "REPLACE WITH SECRET" 43 | }, 44 | 45 | redis: { 46 | host: "localhost", 47 | port: 6379, 48 | // pass: , 49 | // db: , 50 | ttl: 1728000 51 | }, 52 | 53 | smtp: { 54 | service: "Gmail", 55 | user: "REPLACE WITH EMAIL", 56 | pass: "REPLACE WITH PASSWORD" 57 | }, 58 | 59 | NOT_USED_amazon: { 60 | AWSAccessKeyID: "REPLACE WITH AWSAccessKeyID", 61 | AWSSecretKey: "REPLACE WITH AWSSecretKey", 62 | ServiceUrl: "REPLACE WITH YOUR AWS Service URL" 63 | }, 64 | 65 | facebook: { 66 | appId: "REPLACE WITH APP ID", 67 | appSecret: "REPLACE WITH APP SECRET", 68 | callbackURL: "http://localhost:4000/auth/facebook/callback" 69 | }, 70 | 71 | google: { 72 | clientID: "REPLACE WITH CLIENT ID", 73 | clientSecret: "REPLACE WITH CLIENT SECRET", 74 | callbackURL: "http://localhost:4000/auth/google/callback" 75 | } 76 | }, 77 | test: { 78 | appName: "Test", 79 | email: { 80 | registration: "no_reply@test.com", 81 | info: "info@test.com" 82 | }, 83 | 84 | node: { 85 | host: "localhost", 86 | distFolder: path.resolve(__dirname, '../../app'), 87 | port: 4000 88 | }, 89 | 90 | NOT_USED_loggly: { 91 | subdomain: "REPLACE WITH LOGGLY SUBDOMAIN", 92 | inputToken: "REPLACE WITH LOGGLY INPUT TOKEN", 93 | auth: { 94 | username: "REPLACE WITH LOGGLY USERNAME", 95 | password: "REPLACE WITH LOGGLY PASSWORD" 96 | }, 97 | json: true 98 | }, 99 | 100 | airbrake: { 101 | apiKey: "REPLACE WITH AIRBRAKE API" 102 | }, 103 | 104 | neo4j: { 105 | host: "localhost", 106 | port: 7475, 107 | // user: , 108 | // password: 109 | }, 110 | 111 | sess: { 112 | secret: "REPLACE WITH SECRET" 113 | }, 114 | 115 | cookie: { 116 | secret: "REPLACE WITH SECRET" 117 | }, 118 | 119 | redis: { 120 | host: "localhost", 121 | port: 6379, 122 | // pass: , 123 | // db: , 124 | ttl: 1728000 125 | }, 126 | 127 | smtp: { 128 | service: "Gmail", 129 | user: "REPLACE WITH EMAIL", 130 | pass: "REPLACE WITH PASSWORD" 131 | }, 132 | 133 | NOT_USED_amazon: { 134 | AWSAccessKeyID: "REPLACE WITH AWSAccessKeyID", 135 | AWSSecretKey: "REPLACE WITH AWSSecretKey", 136 | ServiceUrl: "REPLACE WITH YOUR AWS Service URL" 137 | }, 138 | 139 | facebook: { 140 | appId: "REPLACE WITH APP ID", 141 | appSecret: "REPLACE WITH APP SECRET", 142 | callbackURL: "http://localhost:4000/auth/facebook/callback" 143 | }, 144 | 145 | google: { 146 | clientID: "REPLACE WITH CLIENT ID", 147 | clientSecret: "BgdvBWmZJBT3RJdax3WMTBvP", 148 | callbackURL: "http://localhost:4000/auth/google/callback" 149 | } 150 | } 151 | }; -------------------------------------------------------------------------------- /server/config/disposableEmailProviders.txt: -------------------------------------------------------------------------------- 1 | ############################################################# 2 | ## The Throwaway Email Blacklist 3 | ## https://github.com/johnduhart/throwaway-email-blacklist 4 | ## 5 | ## File listing domains that are sending email from disposable accounts 6 | ## Copyright (C) 2012 John Du Hart 7 | ## 8 | ## This program is free software; you can redistribute it and/or 9 | ## modify it under the terms of the GNU General Public License 10 | ## as published by the Free Software Foundation; either version 2 11 | ## of the License, or (at your option) any later version. 12 | ## 13 | ## This program is distributed in the hope that it will be useful, 14 | ## but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | ## GNU General Public License for more details. 17 | ## 18 | ## You should have received a copy of the GNU General Public License 19 | ## along with this program; if not, write to the Free Software 20 | ## Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 21 | ############################################################# 22 | 23 | # Mailinator - http://www.mailinator.com/ 24 | # 25 | # Source: http://www.mailinator.com/get_alt_domain 26 | mailinator.com 27 | mailinator2.com 28 | mailinator.net 29 | chammy.info 30 | binkmail.com 31 | bobmail.info 32 | devnullmail.com 33 | gawab.com 34 | letthemeatspam.com 35 | notmailinator.com 36 | putthisinyourspamdatabase.com 37 | tempimbox.com 38 | thisisnotmyrealemail.com 39 | tradermail.info 40 | safetymail.info 41 | sendspamhere.com 42 | sogetthis.com 43 | spam.la 44 | spamherelots.com 45 | spamthisplease.com 46 | supergreatmail.com 47 | suremail.info 48 | veryrealemail.com 49 | zippymail.info 50 | 51 | # Guerrilla Mail - http://www.guerrillamail.com/ 52 | guerrillamail.com 53 | guerrillamail.org 54 | guerrillamail.net 55 | guerrillamailblock.com 56 | sharklasers.com 57 | 58 | # 10 Minute mail - http://10minutemail.com/ 59 | nwldx.com 60 | 61 | # 10 Minute mail.net - http://10minutemail.net/ 62 | ms9.mailslite.com 63 | 64 | # Fake Mail Generator http://fakemailgenerator.com/ 65 | fakemailgenerator.com 66 | teleworm.com 67 | 68 | # OneWayMail - http://onewaymail.com/ 69 | onewaymail.com 70 | mobi.web.id 71 | ag.us.to 72 | 73 | # Shitmail - http://shitmail.com/ 74 | shitmail.org 75 | crapmail.org 76 | 77 | # SoodoNims - http://soodonims.com/ 78 | 1ce.us 79 | big1.us 80 | garliclife.com 81 | irish2me.com 82 | lifebyfood.com 83 | lr7.us 84 | lr78.com 85 | luv2.us 86 | soodomail.com 87 | soodonims.com 88 | winemaven.info 89 | xoxox.cc 90 | yogamaven.com 91 | 92 | # Spambox - http://www.spambox.us/ 93 | # Note: There are a bunch of broken domains on this service. Check DNS before adding. 94 | spambox.us 95 | trillianpro.com 96 | nospam.wins.com.br 97 | 98 | # TrashMail - https://ssl.trashmail.net/ 99 | kurzepost.de 100 | objectmail.com 101 | proxymail.eu 102 | rcpt.at 103 | trash-mail.at 104 | trashmail.at 105 | trashmail.me 106 | trashmail.net 107 | wegwerfmail.de 108 | wegwerfmail.net 109 | wegwerfmail.org 110 | 111 | # YOPmail - http://www.yopmail.com/en/ 112 | yopmail.com 113 | yopmail.fr 114 | yopmail.net 115 | cool.fr.nf 116 | courriel.fr.nf 117 | jetable.fr.nf 118 | nospam.ze.tc 119 | nomail.xl.cx 120 | mega.zik.dj 121 | moncourrier.fr.nf 122 | monemail.fr.nf 123 | monmail.fr.nf 124 | speed.1s.fr 125 | 126 | # My trash mail - http://www.mytrashmail.com/ 127 | mailmetrash.com 128 | 129 | # TempInbox - http://www.tempimbox.com/ 130 | tempinbox.com 131 | beefmilk.com 132 | dingbone.com 133 | fatflap.com 134 | fudgerub.com 135 | lookugly.com 136 | smellfear.com 137 | 138 | # Instant Email Address - http://instantemailaddress.com/ 139 | instantemailaddress.com 140 | dacoolest.com 141 | junk1e.com 142 | throwawayemailaddress.com 143 | 144 | # Tempail - http://www.tempail.com/temp-email 145 | tempail.com 146 | iroid.com 147 | 148 | # Dudmail - http://dudmail.com/ 149 | dudmail.com 150 | scatmail.com 151 | trayna.com 152 | 153 | # SpamDecoy - http://spamdecoy.net/ 154 | spamdecoy.net 155 | dharmatel.net 156 | slave-auctions.net 157 | deadchildren.org 158 | 159 | # ThrowAwayMail.com - http://throwawaymail.com/ 160 | throwawaymail.com 161 | tempmail2.com 162 | 163 | # The rest are from the same domain 164 | 12minutemail.com 165 | 24hourmail.com 166 | anonymbox.com 167 | deadaddress.com 168 | dispostable.com 169 | eyepaste.com 170 | filzmail.com 171 | fornow.eu 172 | freemail.ms 173 | incognitomail.com 174 | incognitomail.org 175 | jetable.org 176 | keepmymail.com 177 | mailcatch.com 178 | mailme24.com 179 | mailexpire.com 180 | meltmail.com 181 | no-spam.ws 182 | nospamfor.us 183 | nowmymail.com 184 | sneakemail.com 185 | spam.su 186 | spam-be-gone.com 187 | spamavert.com 188 | spamfree24.org 189 | spamgourmet.com 190 | sofimail.com 191 | tempemail.net -------------------------------------------------------------------------------- /server/config/errorMessages.js: -------------------------------------------------------------------------------- 1 | var Errors = require('../lib/errors'); 2 | 3 | module.exports = { 4 | // Database errors 5 | failedToLogin: new Errors.DatabaseError(20370, 'Failed to login user'), 6 | failedToSave: new Errors.DatabaseError(10510, 'Failed to save user'), 7 | failedToRemoveToken: new Errors.DatabaseError(10510, 'Failed to remove cookie'), 8 | 9 | 10 | // account validation 11 | invalidFirstName: new Errors.ApiError(20010, 'First Name is required'), 12 | invalidLastName: new Errors.ApiError(20020, 'Last Name is required'), 13 | invalidEmail: new Errors.ApiError(20030, 'Valid email required'), 14 | invalidEmailDisposable: new Errors.ApiError(20040, 'We are a community here. Please do not use disposable email addresses'), 15 | invalidPassword: new Errors.ApiError(20050, 'Password is required and must be between 6 and 128 characters'), 16 | invalidGender: new Errors.ApiError(20060, 'Gender is required'), 17 | // invalidTerms: new Errors.ApiError(20080, 'Please confirm that you agree to the Terms and Conditions'), 18 | 19 | //registration 20 | userRegisteredAndActive: new Errors.ApiError(20150, 'The email address entered is already active.'), 21 | userRegisteredNotActive: new Errors.ApiError(20160, 'The email address entered is already registered.'), 22 | invalidActivationKey: new Errors.ApiError(20170, 'Thats not a valid activation link!'), 23 | usedActivationKey: new Errors.ApiError(20180, 'Your activation link has already been used. Please Login'), 24 | 25 | // Login 26 | userNotActive: new Errors.ApiError(20300, 'The email address entered is not active.'), 27 | userNotRegistered: new Errors.ApiError(20310, 'The email address entered is not registered. Please Sign Up'), 28 | incorrectPassword: new Errors.ApiError(20320, 'The password entered is incorrect.'), 29 | accountSuspended: new Errors.ApiError(20330, 'Your account has been locked due to too many incorrect login attempts. Please try again in 5 minutes'), 30 | userDeactivated: new Errors.ApiError(20340, 'The email address entered is associated with a deactivated account.'), 31 | 32 | // Forgot Password 33 | invalidPasswordKey: new Errors.ApiError(20500, 'Cannot find user for selected password reset link. Click on the link in your email again or request a new one below'), 34 | usedPasswordKey: new Errors.ApiError(20510, 'Your password reset link has already been used. Please request a new one below'), 35 | expiredPasswordKey: new Errors.ApiError(20520, 'Your password reset link has expired.'), 36 | passwordsDoNotMatch: new Errors.ApiError(20530, 'The passwords that you entered do not match. Please try again'), 37 | 38 | //loginTokens 39 | tokenError: new Errors.ApiError(20610, 'Error retrieving other logins. Please try again'), 40 | 41 | // linked accounts 42 | oAuthProviderError: new Errors.ApiError(20620, 'Error retrieving linked accounts. Please try again'), 43 | 44 | // generic 45 | loginRequired: new Errors.ApiError(20360, 'You must be logged in to access this part of the application.') 46 | 47 | }; -------------------------------------------------------------------------------- /server/controllers/loggerService.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | //Logging config 4 | 5 | var winston = require('winston') 6 | , env = process.env.NODE_ENV || 'development' 7 | , config = require('../config/config')[env]; 8 | 9 | 10 | if (typeof(config.loggly) !== 'undefined') { 11 | //Use loggly 12 | require('winston-loggly'); 13 | var options = { 14 | subdomain: config.loggly.subdomain, 15 | inputToken: config.loggly.inputToken, 16 | auth: { 17 | username: config.loggly.username, 18 | password: config.loggly.password 19 | }, 20 | json: config.loggly.json 21 | }; 22 | winston.add(winston.transports.Loggly, options); 23 | exports.logger = winston; 24 | } 25 | else { 26 | //use a log file 27 | var logger = new (winston.Logger)({ 28 | transports: [ 29 | new (winston.transports.File)( 30 | { filename: 'traces.log', level: 'info', json: true }) 31 | ] 32 | }); 33 | exports.logger = logger; 34 | } 35 | 36 | // enable web server logging; pipe those log messages through winston 37 | var winstonStream = { 38 | write: function(message, encoding) { 39 | winston.info(message); 40 | } 41 | }; 42 | 43 | exports.loggerStream = winstonStream; 44 | -------------------------------------------------------------------------------- /server/controllers/mailerService.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var nodemailer = require('nodemailer') 4 | , emailTemplates = require('email-templates') 5 | , path = require('path') 6 | , templatesDir = path.resolve(__dirname, '../lib/', 'emailTemplates') 7 | , env = process.env.NODE_ENV || 'development' 8 | , config = require('../config/config')[env] 9 | , logger = require('./loggerService.js').logger; 10 | 11 | var transport; 12 | // Create a transport object 13 | // mock out the test transport 14 | if(env === 'test'){ 15 | transport = { 16 | emails: [] 17 | }; 18 | } 19 | else if (typeof(config.amazon) !== 'undefined') { 20 | 21 | // Create an Amazon SES transport object 22 | transport = nodemailer.createTransport('SES', { 23 | AWSAccessKeyID: config.amazon.AWSAccessKeyID, 24 | AWSSecretKey: config.amazon.AWSSecretKey, 25 | ServiceUrl: config.amazon.ServiceUrl // optional 26 | }); 27 | } 28 | else { 29 | var transport = nodemailer.createTransport('SMTP', { 30 | service: config.smtp.service, 31 | auth: { 32 | user: config.smtp.user, 33 | pass: config.smtp.pass 34 | } 35 | }); 36 | } 37 | 38 | /** 39 | * Send a single email 40 | * @param {object} options options.template = required email template 41 | * Need a folder in lib/emailTemplates. 42 | * options.from = from email address 43 | * "First Last ". 44 | * options.subject = subject line. 45 | * @param {object} data Data to be populated in the email 46 | * e.g data.email, data.name. 47 | * @param {Function} callback needs variables err and template. 48 | * Either error out or pass through to the 49 | * template generation. 50 | */ 51 | exports.sendMail = function(options, data, callback) { 52 | 53 | // mock for testing 54 | if(env === 'test'){ 55 | transport.emails.push(data); 56 | } 57 | 58 | else{ 59 | emailTemplates(templatesDir, function(err, template) { 60 | if (err) { 61 | callback(err); 62 | } else { 63 | // Send a single email 64 | template(options.template, data, function(err, html, text) { 65 | if (err) { 66 | logger.error('unable to send activation mail to user : ' + 67 | data.email + '. Error: ' + err); 68 | callback(err); 69 | } else { 70 | transport.sendMail({ 71 | from: options.from, 72 | to: data.email, 73 | subject: options.subject, 74 | html: html, 75 | // generateTextFromHTML: true, 76 | text: text 77 | }, function(err, responseStatus) { 78 | if (err) { 79 | logger.error('unable to send activation mail to user : ' + 80 | data.email + '. Error: ' + err); 81 | callback(err, responseStatus); 82 | } else { 83 | callback(null, responseStatus); 84 | } 85 | }); 86 | } 87 | }); 88 | } 89 | }); 90 | } 91 | }; 92 | -------------------------------------------------------------------------------- /server/controllers/userService.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var LoginToken = require('../models/loginToken') 4 | , OAuthProvider = require('../models/oAuthProvider') 5 | , errorMessages = require('../config/errorMessages'); 6 | 7 | /* 8 | * Account Routes 9 | */ 10 | 11 | /** 12 | * Edit Account 13 | * @param {object} req Request. 14 | * @param {object} res Response. 15 | * @return {render} 412, with error: Validation Failed 16 | * First name, Last name, Email format 17 | * Gender 18 | * 200: User updated. 19 | */ 20 | exports.editAccount = function(req, res) { 21 | //validate formInput inputs 22 | req.assert('email', errorMessages.invalidEmail).isEmail(); 23 | req.assert('password', errorMessages.invalidPassword).len(6, 128); 24 | var errors = req.validationErrors(); 25 | if(errors){ 26 | return res.json(412, errors); 27 | } 28 | else { 29 | req.user.checkPassword(req.body.password, function(err, isMatch){ 30 | if (err) { return res.json(400, err); } 31 | if(!isMatch) { 32 | return res.json(412, errorMessages.incorrectPassword); 33 | } 34 | else { 35 | var updates = { 36 | first: req.body.first, 37 | last: req.body.last, 38 | email: req.body.email.toLowerCase().trim(), 39 | gender: req.body.gender, 40 | birthday: req.body.birthday 41 | }; 42 | 43 | req.user.update(updates, function(err){ 44 | if(err) { return res.json(412, err); } 45 | return res.json(200); 46 | }); 47 | } 48 | }); 49 | } 50 | }; 51 | 52 | /** 53 | * Change a user password. User is validated by email token or existing password 54 | * Called from changeForgottenPassword as well as from account page 55 | * @param {object} req Request. 56 | * @param {object} res Response. 57 | * @param {function} next Middleware. 58 | * @return {JSON} 412, with error: Validation failed 59 | * Existing password is incorrect 60 | * New password does not meet policy 61 | * New Password and confirmation do not match 62 | * 200: Password Changed. 63 | */ 64 | exports.changePassword = function(req, res) { 65 | 66 | // validate inputs 67 | req.assert('newPassword', errorMessages.invalidPassword).len(6, 128); 68 | req.assert('passwordConfirm', errorMessages.invalidPassword).len(6, 128); 69 | var errors = req.validationErrors(); 70 | if(errors){ 71 | return res.json(412, errors); 72 | } 73 | // validate that the passwords match 74 | if (req.body.newPassword !== req.body.passwordConfirm) { 75 | return res.json(412, errorMessages.passwordsDoNotMatch); 76 | } 77 | else { 78 | req.user.checkPassword(req.body.currentPassword, function(err, isMatch){ 79 | if (err) { return res.json(400, err); } 80 | if(!isMatch) { 81 | return res.json(412, errorMessages.incorrectPassword); 82 | } 83 | else { 84 | req.user.createHash(req.body.newPassword, function(err, hash){ 85 | if(err) return res.json(400, err); 86 | var updates = { 87 | hashed_password: hash 88 | }; 89 | req.user.update(updates, function(err, user){ 90 | if (err) { return res.json(400, err); } 91 | return res.json(200); 92 | }); 93 | }); 94 | } 95 | }); 96 | } 97 | }; 98 | 99 | /** 100 | * Get Security Tokens 101 | * @param {object} req Request. 102 | * @param {object} res Response. 103 | * @return {render} Renders the specified template. 104 | */ 105 | exports.getLoginTokens = function(req, res) { 106 | // LoginToken.getTokens(req.user.email, function(err, cookies){ 107 | req.user.getIncomingRelationships('AUTHORISES', function(err, cookies){ 108 | if(err) { return res.json(400, err); } 109 | return res.json(200, cookies.nodes); 110 | }); 111 | }; 112 | 113 | /** 114 | * Remove Cookie 115 | * @param {object} req Request. 116 | * @param {object} res Response. 117 | * @return {render} 400, with error: Failed to remove cookie from database 118 | * 200: Cookie removed. 119 | */ 120 | exports.removeLoginToken = function(req, res) { 121 | // could use findOneAndRemove but this query is more efficient 122 | LoginToken.findByIdAndRemove(req.params.id, {remove: {force: true }}, function(err){ 123 | if(err) { return res.json(412, errorMessages.failedToRemoveToken); } 124 | return res.json(200); 125 | }); 126 | }; 127 | 128 | /** 129 | * Get linked social accounts 130 | * @param {object} req Request. 131 | * @param {object} res Response. 132 | * @return {JSON} 400. Error occured. 133 | * 200. List of providers. 134 | */ 135 | exports.getLinkedAccounts = function(req, res) { 136 | // OAuthProvider.getAccounts(req.user.id, function (err, providers){ 137 | req.user.getIncomingRelationships('OAUTHORISES', function(err, providers){ 138 | if(err) return res.json(400, err); 139 | return res.json(200, providers.nodes); 140 | }); 141 | }; 142 | 143 | /** 144 | * Remove a linked social account 145 | * @param {object} req Request. 146 | * @param {object} res Response. 147 | * @return {JSON} 400. Error occured. 148 | * 200. Account removed. 149 | */ 150 | exports.removeLinkedAccount = function(req, res) { 151 | OAuthProvider.findByIdAndRemove(req.params.id, {remove: {force: true }}, function (err){ 152 | if(err) return res.json(412, err); 153 | return res.json(200); 154 | }); 155 | }; 156 | 157 | /** 158 | * Deactivate Account 159 | * @param {object} req Request. 160 | * @param {object} res Response. 161 | * @return {render} 400, with error: Failed to update user in database 162 | * 200: Account deactivated. 163 | */ 164 | exports.deactivateAccount = function(req, res) { 165 | var updates = { 166 | active: false, 167 | accountDeactivated: true 168 | }; 169 | 170 | req.user.update(updates, function (err, user) { 171 | if (err) { return res.json(400, err); } 172 | return res.json(200, user); 173 | }); 174 | }; 175 | 176 | 177 | -------------------------------------------------------------------------------- /server/controllers/validationService.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var fs = require('fs'); 4 | 5 | /** 6 | * Reads disposable email file and validates email 7 | * @param {[type]} email [description] 8 | * @param {Function} callback [description] 9 | * @return {[type]} [description] 10 | */ 11 | exports.disposableEmail = function(email, callback){ 12 | var badEmailsTxt = 'config/disposableEmailProviders.txt'; 13 | 14 | var badEmails = []; 15 | var emailParts=email.split("@"); 16 | var array = fs.readFileSync(badEmailsTxt).toString().split("\n"); 17 | var disposable = true; 18 | for(var i in array) { 19 | if(array[i].charAt(0) && array[i].charAt(0) !== '#'){ 20 | badEmails.push(array[i]); 21 | } 22 | } 23 | if(badEmails.indexOf(emailParts[1]) < 0 ) { 24 | disposable = false; 25 | } 26 | return callback(null, disposable); 27 | }; 28 | -------------------------------------------------------------------------------- /server/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var express = require('express'); 4 | var http = require('http'); 5 | var app = express(); 6 | // var neoprene = require('neoprene') 7 | var RedisStore = require('connect-redis')(express); 8 | var expressValidator = require('express-validator'); 9 | var passport = require('passport'); 10 | var mailerService = require('./controllers/mailerService'); 11 | 12 | var env = process.env.NODE_ENV || 'development'; 13 | var config = require('./config/config')[env]; 14 | // var loggerStream = require('./').loggerStream; 15 | // var logger = require('./').logger; 16 | // var airbrake = require('airbrake').createClient(config.airbrake.apiKey) 17 | // var errors = require('./').errors 18 | 19 | // bootstrap passport config 20 | require('./lib/passport').boot(passport, config); 21 | 22 | var app = module.exports = app; 23 | 24 | app.configure(function() { 25 | app.set('port', config.node.port); 26 | app.use(express.favicon()); 27 | 28 | if (app.get('env') === 'development') { 29 | app.use(express.logger('dev')); 30 | } 31 | app.use(express.bodyParser()); 32 | app.use(express.methodOverride()); 33 | app.use(expressValidator); 34 | app.use(express.cookieParser(config.cookie.secret)); 35 | 36 | // Configure session to use Redis as store 37 | app.use(express.session({ 38 | key: 'express.sid', 39 | secret: config.sess.secret, 40 | store: new RedisStore({ 41 | host: config.redis.host, 42 | port: config.redis.port, 43 | db: config.redis.db, 44 | pass: config.redis.pass, 45 | ttl: config.redis.ttl 46 | }) 47 | })); 48 | 49 | app.use(passport.initialize()); 50 | app.use(passport.session()); 51 | 52 | //csrf protection 53 | // add a check for the csrf token in the req.headers['x-xsrf-token'] - angular places it here 54 | // all other checks are the default express behaviour 55 | if(env !== 'test') { 56 | var csrfValue = function(req) { 57 | var token = (req.body && req.body._csrf) 58 | || (req.query && req.query._csrf) 59 | || (req.headers['x-csrf-token']) 60 | || (req.headers['x-xsrf-token']); 61 | return token; 62 | }; 63 | app.use(express.csrf({value: csrfValue})); 64 | // put the csrf token from the header into the cookie for angular to pickup 65 | app.use(function(req, res, next) { 66 | res.cookie('XSRF-TOKEN', req.session._csrf); 67 | next(); 68 | }); 69 | } 70 | 71 | 72 | app.use(express.compress()); 73 | 74 | // staticCache has been deprecated. 75 | // TODO: investigate varnish / nginx for caching 76 | // app.use(express.staticCache()); 77 | 78 | 79 | // host dev files if in dev mode 80 | if (app.get('env') === 'development' || app.get('env') === 'test') { 81 | app.use(express.static(__dirname + '/../.tmp')); 82 | app.use(express.static(__dirname + '/../src')); 83 | } else { 84 | app.use(express.static(__dirname + '/../dist'), {maxAge: 86400000}); 85 | } 86 | 87 | // Need to be careful for similar filenames in router as 88 | // static files will be loaded first 89 | app.use(app.router); 90 | 91 | // Since this is the last non-error-handling 92 | // middleware use()d, we assume 404, as nothing else 93 | // responded. 94 | 95 | // $ curl http://localhost:3000/notfound 96 | // $ curl http://localhost:3000/notfound -H "Accept: application/json" 97 | // $ curl http://localhost:3000/notfound -H "Accept: text/plain" 98 | app.use(function(req, res, next) { 99 | res.status(404); 100 | 101 | // Respond with html page: 102 | if (req.accepts('html')) { 103 | res.render('404', { url: req.url }); 104 | return; 105 | } 106 | // Respond with JSON: 107 | if (req.accepts('json')) { 108 | res.send({ error: 'Not found'}); 109 | return; 110 | } 111 | 112 | // Default to plain text: 113 | res.type('txt').send('Not found'); 114 | }); 115 | }); 116 | 117 | 118 | // TODO: better error handling and logging 119 | app.configure('development', function () { 120 | app.use(express.errorHandler({ dumpExceptions: true, showStack: true })); 121 | }); 122 | 123 | app.configure('test', function () { 124 | app.get('/emails', function(req, res){ 125 | res.send(JSON.stringify(mailerService.transport.emails)); 126 | }); 127 | }); 128 | 129 | app.configure('production', function () { 130 | app.use(express.errorHandler()); 131 | }); 132 | 133 | 134 | // Bootstrap routes 135 | require('./routes')(app, passport); 136 | 137 | http.createServer(app).listen(app.get('port'), function() { 138 | console.log('Listening on ' + config.node.host + ':' + app.get('port')); 139 | }); -------------------------------------------------------------------------------- /server/lib/emailTemplates/invite/html.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 |

Hi there <%= name %>

4 |

Thank you for registering for <%= appName %>, 5 |

Please click on the following link to confirm your email and activate your account:
6 | Activate your Account 7 |

Regards,
8 | <%= appName %> 9 | 10 | -------------------------------------------------------------------------------- /server/lib/emailTemplates/invite/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: white; 3 | color: black; 4 | } 5 | h1 { 6 | text-align: center; 7 | } -------------------------------------------------------------------------------- /server/lib/emailTemplates/password_reset/html.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 |

Hi there <%= name %>

4 |

Sorry about the trouble with the password, 5 |

Please click on the following link to confirm your email and set a new password:
6 | Reset Password 7 |

Regards,
8 | <%= appName %> 9 | 10 | -------------------------------------------------------------------------------- /server/lib/emailTemplates/password_reset/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: white; 3 | color: black; 4 | } 5 | h1 { 6 | text-align: center; 7 | } -------------------------------------------------------------------------------- /server/lib/emailTemplates/unknown_user/html.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 |

Whoops! You are <%= email %> is not registered with

4 |

You requested a password for <%= appName %> but we do not have any records for <%= email %> on our system.

5 |

Please create a new account. 6 |

Regards,
7 | <%= appName %> 8 | 9 | -------------------------------------------------------------------------------- /server/lib/emailTemplates/unknown_user/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: white; 3 | color: black; 4 | } 5 | h1 { 6 | text-align: center; 7 | } -------------------------------------------------------------------------------- /server/lib/errors.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var util = require('util'); 4 | 5 | /** 6 | * Application wide error codes. 7 | * @type {ErrorCodes} 8 | */ 9 | 10 | // From http://dustinsenos.com/articles/customErrorsInNode 11 | // Create a new Abstract Error constructor 12 | var AbstractError = function (code, msg, constr) { 13 | // If defined, pass the constr property to V8's 14 | // captureStackTrace to clean up the output 15 | // Error.captureStackTrace(this, constr || this); 16 | 17 | // If defined, store a custom error message 18 | this.message = msg || 'Error'; 19 | 20 | // if defined store the code 21 | this.code = code; 22 | }; 23 | util.inherits(AbstractError, Error); 24 | AbstractError.prototype.name = 'Abstract Error'; 25 | AbstractError.prototype.code = 0; 26 | 27 | // Create the ApiError 28 | var ApiError = function (code, msg) { 29 | ApiError.super_.call(this, code, msg, this.constructor); 30 | }; 31 | util.inherits(ApiError, AbstractError); 32 | ApiError.prototype.name = 'API Error'; 33 | 34 | // Create the DatabaseError 35 | var DatabaseError = function (code, msg) { 36 | DatabaseError.super_.call(this, code, msg, this.constructor); 37 | }; 38 | util.inherits(DatabaseError, AbstractError); 39 | DatabaseError.prototype.name = 'Database Error'; 40 | 41 | // Create the MailerError 42 | var MailerError = function (msg) { 43 | MailerError.super_.call(this, code, msg, this.constructor); 44 | }; 45 | util.inherits(MailerError, AbstractError); 46 | MailerError.prototype.name = 'Mailer Error'; 47 | 48 | module.exports = { 49 | ApiError: ApiError, 50 | DatabaseError: DatabaseError, 51 | MailerError: MailerError 52 | }; -------------------------------------------------------------------------------- /server/lib/passport.js: -------------------------------------------------------------------------------- 1 | 2 | var LocalStrategy = require('passport-local').Strategy 3 | , FacebookStrategy = require('passport-facebook').Strategy 4 | , GoogleStrategy = require('passport-google-oauth').OAuth2Strategy 5 | 6 | // models 7 | , User = require('../models/user') 8 | 9 | // controllers 10 | , authService = require('../controllers/authService'); 11 | 12 | /** 13 | * Boot the passport settings into the server 14 | * @param {object} passport Passport object. 15 | * @param {object} config Config object. 16 | */ 17 | exports.boot = function (passport, config){ 18 | passport.serializeUser(function(user, done) { 19 | done(null, user._id); 20 | }); 21 | 22 | passport.deserializeUser(function(id, done) { 23 | User.findById(id, function (err, user){ 24 | done(err, user); 25 | }); 26 | }); 27 | 28 | passport.use(new LocalStrategy({ 29 | usernameField: 'email', 30 | passwordField: 'password' 31 | }, 32 | function(email, password, done) { 33 | authService.authenticate(email, password, function(err, user, info) { 34 | if (err) { return done(err); } 35 | if (!user) { return done(null, false, info); } 36 | return done(null, user, info); 37 | }); 38 | } 39 | )); 40 | 41 | // use facebook strategy 42 | passport.use(new FacebookStrategy({ 43 | clientID: config.facebook.appId 44 | , clientSecret: config.facebook.appSecret 45 | , callbackURL: config.facebook.callbackURL 46 | }, 47 | function(accessToken, refreshToken, profile, done) { 48 | authService.loginOrCreate('facebook', profile, done); 49 | } 50 | )); 51 | 52 | // use google strategy 53 | passport.use(new GoogleStrategy({ 54 | clientID: config.google.clientID 55 | , clientSecret: config.google.clientSecret 56 | , callbackURL: config.google.callbackURL 57 | , passReqToCallback: true 58 | }, 59 | function(req, accessToken, refreshToken, profile, done) { 60 | authService.loginOrCreate('google', profile, done); 61 | } 62 | )); 63 | }; 64 | -------------------------------------------------------------------------------- /server/models/loginToken.js: -------------------------------------------------------------------------------- 1 | var neoprene = require('neoprene') 2 | , Schema = neoprene.Schema 3 | , uuid = require('uuid-v4') 4 | , env = process.env.NODE_ENV || 'development' 5 | , config = require('../config/config')[env]; 6 | 7 | var errorMessages = require('../config/errorMessages'); 8 | 9 | var neo4jURI = 'http://' + config.neo4j.user + ':' + 10 | config.neo4j.password + '@' + config.neo4j.host + ':' + 11 | config.neo4j.port; 12 | neoprene.connect(neo4jURI); 13 | 14 | var LoginTokenSchema = new Schema({ 15 | autoIndexSeries: {type: String, index: true} 16 | , token: {type: String, index: true } 17 | , ip: { type: String } 18 | , country: { type: String } 19 | , city: { type: String } 20 | , browser: { type: String } 21 | , os: { type: String } 22 | , created: { type: Date } 23 | , userAgent: {} 24 | , location: {} 25 | }); 26 | 27 | LoginTokenSchema 28 | .virtual('cookieValue') 29 | .get(function() { 30 | return JSON.stringify( 31 | { autoIndexSeries: this.autoIndexSeries, token: this.token}); 32 | }); 33 | 34 | 35 | LoginTokenSchema.pre('create', function(next) { 36 | // Automatically create the token 37 | this.token = uuid(); 38 | this.created = new Date(); 39 | next(); 40 | }); 41 | 42 | // get the value of the loginToken for setting in the cookie 43 | LoginTokenSchema.methods.getCookieValue = function(){ 44 | return JSON.stringify( 45 | { autoIndexSeries: this.autoIndexSeries, token: this.token}); 46 | }; 47 | 48 | // remove a series if one of the items has become compromised 49 | LoginTokenSchema.statics.removeTokenSeries = function(autoIndexSeries, callback) { 50 | if (!autoIndexSeries) { 51 | return callback(errorMessages.tokenError, null); 52 | } 53 | else { 54 | // var query = [ 55 | // 'START token=node:node_auto_index(autoIndexSeries={autoIndexSeries})', 56 | // 'MATCH token-[r]->()', 57 | // 'DELETE r, token' 58 | // ].join('\n'); 59 | var query = 'MATCH (n:LoginToken)-[r]-() WHERE n.autoIndexSeries = { autoIndexSeries } DELETE r, n'; 60 | 61 | var params = { 62 | autoIndexSeries: autoIndexSeries 63 | }; 64 | 65 | neoprene.query(query, params, function(err, results) { 66 | if (err) return callback(err); 67 | return callback(null); 68 | }); 69 | } 70 | }; 71 | 72 | // load the relationship used for this model 73 | // neoprene.model('relationship', 'AUTHORISES', new Schema()); 74 | 75 | module.exports = neoprene.model('LoginToken', LoginTokenSchema); 76 | 77 | -------------------------------------------------------------------------------- /server/models/oAuthProvider.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var neoprene = require('neoprene') 4 | , env = process.env.NODE_ENV || 'development' 5 | , config = require('../config/config')[env] 6 | , Schema = neoprene.Schema; 7 | 8 | var neo4jURI = 'http://' + config.neo4j.user + ':' + 9 | config.neo4j.password + '@' + config.neo4j.host + ':' + 10 | config.neo4j.port; 11 | neoprene.connect(neo4jURI); 12 | 13 | // strict false means that other values can be added without needing to be specified 14 | var OAuthProviderSchema = new Schema({ 15 | id: {type: String, index: true}, 16 | first: ({type: String}), 17 | last: ({type: String}), 18 | middle: ({type: String}), 19 | email: ({type: String}), 20 | gender: ({type: String}), 21 | // password: ({type: String}), 22 | active: ({type: Boolean}), 23 | birthday: ({type: String}), 24 | provider: { type: String }, 25 | locale: {type: String }, 26 | timezone: {type: String }, 27 | picture: {type: String} 28 | }, {strict: false}); 29 | 30 | 31 | // load the relationship used for this model 32 | // neoprene.model('relationship', 'OAUTHORISES', new Schema()); 33 | 34 | module.exports = neoprene.model('OAuthProvider', OAuthProviderSchema); 35 | -------------------------------------------------------------------------------- /server/models/user.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var neoprene = require('neoprene'); 4 | var bcrypt = require('bcrypt'); 5 | var Schema = neoprene.Schema; 6 | 7 | var env = process.env.NODE_ENV || 'development'; 8 | var errorMessages = require('../config/errorMessages') 9 | , config = require('../config/config')[env]; 10 | 11 | var neo4jURI = 'http://' + config.neo4j.user + ':' + 12 | config.neo4j.password + '@' + config.neo4j.host + ':' + 13 | config.neo4j.port; 14 | neoprene.connect(neo4jURI); 15 | 16 | var GENDER = ['unknown', 'male', 'female']; 17 | var MAX_LOGIN_ATTEMPTS = 5; 18 | var LOCK_TIME = 5 * 60 * 1000; 19 | 20 | 21 | var UserSchema = new Schema({ 22 | first:{ type: String, required: true, trim: true }, 23 | last:{ type: String, required: true, trim: true }, 24 | email: { type: String, required: true, lowercase: true, trim: true, index: {unique: true }}, 25 | gender: { type: String, required: true, default: 'unknown', enum: GENDER }, 26 | birthday: { type: Date }, 27 | active:{ type: Boolean, default: false }, 28 | 29 | //sensitive fields 30 | hashed_password: { type: String }, 31 | activationKey: { type: String, index: true }, 32 | activationKeyUsed: { type: Boolean, default: false }, 33 | passwordResetKey: { type: String, index: true}, 34 | passwordResetDate: { type: Date }, 35 | passwordResetUsed: { type: Boolean }, 36 | loginAttempts: { type: Number, required: true, default: 0 }, 37 | lockUntil: { type: Date }, 38 | accountDeactivated: { type: Boolean, default: false } 39 | }); 40 | 41 | // overwrite the toJSON method to exclude some of the sensitive fields 42 | UserSchema.methods.toJSON = function() { 43 | var obj = this.toObject(); 44 | obj.hasPassword = obj.hashed_password ? true: false; 45 | delete obj.hashed_password; 46 | delete obj.activationKey; 47 | delete obj.activationKeyUsed; 48 | delete obj.passwordResetKey; 49 | delete obj.passwordResetUsed; 50 | delete obj.passwordResetDate; 51 | delete obj.loginAttempts; 52 | delete obj.lockUntil; 53 | delete obj.accountDeactivated; 54 | delete obj.__v; 55 | return obj; 56 | } 57 | 58 | // add some easy access virtuals 59 | UserSchema 60 | .virtual('name') 61 | .get(function () { 62 | return this.first +" "+this.last; 63 | }); 64 | 65 | UserSchema 66 | .virtual('password') 67 | .get(function () { 68 | return this.hashed_password; 69 | }) 70 | .set(function(value) { 71 | this._doc.hashed_password = value; 72 | }); 73 | 74 | UserSchema 75 | .virtual('isLocked') 76 | .get(function() { 77 | // check for a future lockUntil timestamp 78 | return !!(this.lockUntil && this.lockUntil > Date.now()); 79 | }); 80 | 81 | 82 | /** 83 | * Instance Methods 84 | */ 85 | 86 | UserSchema.methods.createHash = function(password, callback){ 87 | bcrypt.genSalt(10, function(err, salt) { 88 | if (err) { return callback(err); } 89 | bcrypt.hash(password, salt, function(err, hash) { 90 | if (err) { return callback(err); } 91 | // this.hashed_password = hash; 92 | // console.log(this._doc) 93 | return callback(null, hash); 94 | }); 95 | }); 96 | }; 97 | 98 | UserSchema.methods.checkPassword = function (password, callback) { 99 | bcrypt.compare(password, this.hashed_password, function(err, isMatch){ 100 | if (err) { return callback(err); } 101 | else { return callback(null, isMatch); } 102 | }); 103 | }; 104 | 105 | UserSchema.methods.incLoginAttempts = function(callback) { 106 | // if we have a previous lock that has expired, restart at 1 107 | if (this.lockUntil && this.lockUntil < Date.now()) { 108 | return this.update({ 109 | "loginAttempts": 0, 110 | "lockUntil": null 111 | }, callback); 112 | } 113 | // otherwise we're incrementing 114 | var updates = { "loginAttempts": this.loginAttempts + 1 }; 115 | 116 | // lock the account if we've reached max attempts and it's not locked already 117 | if (this.loginAttempts + 1 >= MAX_LOGIN_ATTEMPTS && !this.isLocked) { 118 | updates.lockUntil = Date.now() + LOCK_TIME; 119 | } 120 | return this.update(updates, callback); 121 | }; 122 | 123 | /** 124 | * Pre Validate 125 | */ 126 | // UserSchema.pre('validate', function (next) { 127 | // var user = this; 128 | 129 | // //Hash Password 130 | // // only hash the password if it has been set 131 | // if (!user._password) { 132 | // next(); 133 | // return; 134 | // } 135 | // // Encrypt the password with bcrypt 136 | // // Encrypting here, rather than earlier in case other validation fails 137 | // bcrypt.genSalt(10, function(err, salt) { 138 | // if (err) { return next(err); } 139 | // bcrypt.hash(user._password, salt, function(err, hash) { 140 | // if (err) { return next(err); } 141 | // user.hashed_password = hash; 142 | // user._password = null; 143 | // next(); 144 | // }); 145 | // }); 146 | // }); 147 | 148 | 149 | module.exports = neoprene.model('User', UserSchema); -------------------------------------------------------------------------------- /server/routes/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var authService = require('../controllers/authService') 4 | , userService = require('../controllers/userService') 5 | // , projectService = require('../controllers/projectService') 6 | , env = process.env.NODE_ENV || 'development' 7 | , config = require('../config/config')[env] 8 | , path = require('path'); 9 | 10 | /** 11 | * Boot for application routes 12 | * @param {Object} app The express server application 13 | * @param {Object} passport The passport authentication object 14 | */ 15 | module.exports = function(app, passport) { 16 | 17 | // login user from cookie regardless of entry point into app 18 | app.all('*', authService.loginFromCookie); 19 | app.get('/api/user/current-user', authService.getCurrentUser); 20 | 21 | /* 22 | * oAuth providers 23 | */ 24 | app.get('/auth/facebook', passport.authenticate('facebook', 25 | { scope: 'email, user_birthday' })); 26 | app.get('/auth/facebook/callback', passport.authenticate('facebook', 27 | { failureRedirect: '/login', successRedirect: '/' })); 28 | app.get('/auth/google', passport.authenticate('google', 29 | { scope: ['https://www.googleapis.com/auth/userinfo.profile', 30 | 'https://www.googleapis.com/auth/userinfo.email'] })); 31 | app.get('/auth/google/callback', passport.authenticate('google', 32 | { failureRedirect: '/login', successRedirect: '/' })); 33 | 34 | /* 35 | * Auth Routes 36 | */ 37 | app.post('/api/user/register', authService.register); 38 | app.post('/api/user/activate', authService.activate, authService.loginUser); 39 | app.post('/api/user/resendActivation', authService.resendActivationLink); 40 | app.post('/api/user/forgotPassword', authService.sendPasswordLink); 41 | app.post('/api/user/validatePasswordReset', authService.validatePasswordReset, authService.respondValidated); 42 | app.post('/api/user/resetPassword', 43 | authService.validatePasswordReset, authService.changeForgottenPassword, 44 | authService.loginUser); 45 | 46 | 47 | //user login routes 48 | app.post('/api/user/login', function(req, res, next) { 49 | passport.authenticate('local', function(err, user, info) { 50 | if(err) return res.json(400, err); 51 | if(!user && info) return res.json(412, info); 52 | 53 | req.newUser = user; 54 | return next(); 55 | })(req, res, next); 56 | }, authService.loginUser); 57 | 58 | app.post('/api/user/logout', authService.logout); 59 | 60 | 61 | /* 62 | * User routes. 63 | */ 64 | app.all('/api/account/*', authService.isLoggedIn); 65 | app.put('/api/account', authService.isLoggedIn, userService.editAccount); 66 | app.put('/api/account/editPassword', userService.changePassword); 67 | app.get('/api/account/linkedAccounts', userService.getLinkedAccounts); 68 | app.delete('/api/account/linkedAccounts/:id', userService.removeLinkedAccount); 69 | app.get('/api/account/security', userService.getLoginTokens); 70 | app.delete('/api/account/security/:id', userService.removeLoginToken); 71 | app.get('/api/account/deactivate', userService.deactivateAccount); 72 | 73 | /* 74 | * Project routes. 75 | */ 76 | // app.get('/api/projects', projectService.list); 77 | // app.get('/api/project/:pid', projectService.show); 78 | // app.post('/api/projects', projectService.create); 79 | // app.put('/api/project/:pid', projectService.update); 80 | // app.delete('/api/project/:pid', projectService.remove); 81 | 82 | 83 | // // redirect all others to the index (HTML5 history) 84 | app.get('*', function(req, res){ 85 | res.sendfile('index.html', { root: config.node.distFolder }); 86 | }); 87 | 88 | }; 89 | -------------------------------------------------------------------------------- /server/test/coverage/blanket.js: -------------------------------------------------------------------------------- 1 | require('blanket')({ 2 | // Only files that match the pattern will be instrumented 3 | pattern: 'server/' 4 | }); -------------------------------------------------------------------------------- /server/test/unit/account.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Account Unit Test 4 | // 5 | 6 | // MODULE DEPENDENCIES 7 | // ------------------- 8 | 9 | var app = require('../../index') 10 | , should = require('should') 11 | , supertest = require('supertest') 12 | , request = supertest(app) 13 | , User = require('../../models/user') 14 | , authService = require('../../controllers/authService') 15 | , neoprene = require('neoprene') 16 | , superagent = require('superagent') 17 | , agent = superagent.agent() 18 | , agent2 = superagent.agent() 19 | , agent3 = superagent.agent() 20 | , fakeUser 21 | ; 22 | 23 | var env = process.env.NODE_ENV || 'development'; 24 | var config = require('../../config/config')[env]; 25 | 26 | var neo4jURI = 'http://' + config.neo4j.user + ':' + 27 | config.neo4j.password + '@' + config.neo4j.host + ':' + 28 | config.neo4j.port; 29 | neoprene.connect(neo4jURI); 30 | 31 | supertest.Test.prototype.agent = function(agent){ 32 | agent.attachCookies(this); 33 | return this; 34 | }; 35 | 36 | var cookie; 37 | 38 | // TESTS 39 | describe('account pages:', function () { 40 | 41 | before(function (done) { 42 | // Drop the database. 43 | var query = 'START n=node(*) MATCH n-[r?]-() WHERE ID(n) <> 0 DELETE n,r'; 44 | var params = {}; 45 | 46 | neoprene.query(query, params, function(err, results) { 47 | fakeUser = { 48 | first: 'Fake', 49 | last: 'User', 50 | email: 'TestUser@test.com', 51 | password: 'TestPassword', 52 | gender: 'male' 53 | }; 54 | 55 | request 56 | .post('/api/user/register') 57 | .send(fakeUser) 58 | .end(function(err, res){ 59 | User.findOne({email: fakeUser.email.toLowerCase()}, function(err, user){ 60 | request 61 | .post('/api/user/activate/') 62 | .send({activationKey: user.activationKey}) 63 | .end(function(err, res){ 64 | agent.saveCookies(res); 65 | User.findOne({email: fakeUser.email.toLowerCase()}, function(err, user){ 66 | user.active.should.be.ok; 67 | user.activationKeyUsed.should.be.ok; 68 | done(); 69 | }); 70 | }); 71 | }); 72 | }); 73 | }); 74 | }); 75 | 76 | it('should return account details', function(done){ 77 | request 78 | .get('/api/user/current-user') 79 | .agent(agent) 80 | .end(function(err, res){ 81 | should.exist(res.body); 82 | res.body.first.should.equal(fakeUser.first); 83 | res.body.email.should.equal(fakeUser.email.toLowerCase()); 84 | done(); 85 | }); 86 | }); 87 | 88 | 89 | it('should allow an account to be updated', function(done){ 90 | request 91 | .put('/api/account') 92 | .agent(agent) 93 | .send({first: 'New', last: 'Name', email: 'NewName@test.com', gender: 'male', password:'TestPassword'}) 94 | .end(function(err, res){ 95 | User.findOne({email: fakeUser.email.toLowerCase()}, function(err, user){ 96 | should.not.exist(err); 97 | should.not.exist(user); 98 | User.findOne({email: 'newname@test.com'}, function(err, user){ 99 | fakeUser.email = 'newname@test.com'; 100 | user.first.should.not.equal(fakeUser.first); 101 | user.first.should.equal('New'); 102 | user.last.should.equal('Name'); 103 | user.email.should.equal('newname@test.com'); 104 | user.gender.should.equal('male'); 105 | done(); 106 | }); 107 | }); 108 | }); 109 | }); 110 | 111 | it('should fail account update if required data is missing', function(done){ 112 | request 113 | .put('/api/account') 114 | .agent(agent) 115 | .send({first: '', last: 'Name', email: 'NewName@test.com', gender: 'male'}) 116 | .end(function(err, res){ 117 | should.not.exist(err); 118 | res.status.should.equal(412); 119 | done(); 120 | }); 121 | }); 122 | 123 | it('should fail account update if password is not supplied', function(done){ 124 | request 125 | .put('/api/account') 126 | .agent(agent) 127 | .send({first: 'New', last: 'Name', email: 'NewName@test.com', gender: 'male'}) 128 | .end(function(err, res){ 129 | should.not.exist(err); 130 | res.status.should.equal(412); 131 | done(); 132 | }); 133 | }); 134 | 135 | it('should fail account update if password is blank', function(done){ 136 | request 137 | .put('/api/account') 138 | .agent(agent) 139 | .send({first: 'New', last: 'Name', email: 'NewName@test.com', gender: 'male', password: ''}) 140 | .end(function(err, res){ 141 | should.not.exist(err); 142 | res.status.should.equal(412); 143 | done(); 144 | }); 145 | }); 146 | 147 | it('should allow password change if passwords match', function(done){ 148 | request 149 | .put('/api/account/editPassword') 150 | .agent(agent) 151 | .send({currentPassword: fakeUser.password, newPassword: 'Testing', passwordConfirm: 'Testing'}) 152 | .end(function(err, res){ 153 | User.findOne({email: fakeUser.email.toLowerCase()}, function(err, user){ 154 | user.checkPassword('Testing', function(err, isMatch){ 155 | fakeUser.password = 'Testing'; 156 | isMatch.should.be.ok; 157 | done(); 158 | }); 159 | }); 160 | }); 161 | }); 162 | 163 | it('should fail password change if passwords do not match', function(done){ 164 | request 165 | .put('/api/account/editPassword') 166 | .agent(agent) 167 | .send({currentPassword: fakeUser.password, newPassword: 'Testing', passwordConfirm: 'TEsting'}) 168 | .end(function(err, res){ 169 | User.findOne({email: fakeUser.email.toLowerCase()}, function(err, user){ 170 | user.checkPassword(fakeUser.password, function(err, isMatch){ 171 | isMatch.should.be.ok; 172 | done(); 173 | }); 174 | }); 175 | }); 176 | }); 177 | 178 | it('should fail password change if password does not meet criteria', function(done){ 179 | request 180 | .put('/api/account/editPassword') 181 | .agent(agent) 182 | .send({currentPassword: fakeUser.password, newPassword: 'Test', passwordConfirm: 'Test'}) 183 | .end(function(err, res){ 184 | User.findOne({email: fakeUser.email.toLowerCase()}, function(err, user){ 185 | user.checkPassword(fakeUser.password, function(err, isMatch){ 186 | isMatch.should.be.ok; 187 | done(); 188 | }); 189 | }); 190 | }); 191 | }); 192 | 193 | it('should fail password change if password is blank', function(done){ 194 | request 195 | .put('/api/account/editPassword') 196 | .agent(agent) 197 | .send({currentPassword: '', newPassword: 'Test', passwordConfirm: 'Test'}) 198 | .end(function(err, res){ 199 | User.findOne({email: fakeUser.email.toLowerCase()}, function(err, user){ 200 | user.checkPassword(fakeUser.password, function(err, isMatch){ 201 | isMatch.should.be.ok; 202 | done(); 203 | }); 204 | }); 205 | }); 206 | }); 207 | 208 | describe('login tokens: ', function(){ 209 | before(function(done){ 210 | // request 211 | // .post('/api/user/logout') 212 | // .agent(agent2) 213 | // .end(function(err, res){ 214 | request 215 | .post('/api/user/login') 216 | .agent(agent2) 217 | .send({email: fakeUser.email.toLowerCase(), password: fakeUser.password, remember_me: true}) 218 | .end(function(err, res){ 219 | agent2.saveCookies(res); 220 | done(); 221 | }); 222 | // }); 223 | }); 224 | it('should return login tokens', function(done){ 225 | request 226 | .get('/api/account/security') 227 | .agent(agent2) 228 | .end(function(err, res){ 229 | should.exist(res.body[0]); 230 | //TODO: maybe a content test 231 | res.body[0].ip.should.equal("127.0.0.1") 232 | res.body[0]._nodeType.should.equal("LoginToken") 233 | done(); 234 | }); 235 | }); 236 | }); 237 | describe('login token delete: ', function(){ 238 | beforeEach(function(done){ 239 | request 240 | .post('/api/user/login') 241 | .agent(agent3) 242 | .send({email: fakeUser.email, password: fakeUser.password, remember_me: true}) 243 | .end(function(err, res){ 244 | agent3.saveCookies(res); 245 | request 246 | .get('/api/account/security') 247 | .agent(agent3) 248 | .end(function(err, res){ 249 | cookie = res.body[0]; 250 | should.exist(res.body[0]); 251 | done(); 252 | }); 253 | }); 254 | }); 255 | it('should allow deletion of login tokens', function(done){ 256 | request 257 | .del('/api/account/security/'+ cookie._id) 258 | .agent(agent3) 259 | .end(function(err, res){ 260 | res.should.have.status(200); 261 | done(); 262 | }); 263 | }); 264 | }); 265 | 266 | describe('linked accounts: ', function(){ 267 | var facebook = { 268 | _json: { 269 | first_name: 'faceFirst', 270 | last_name: 'faceLast', 271 | email: 'newname@test.com', 272 | gender: 'male', 273 | birthday: '06/16/1982', 274 | id: "12397817" 275 | } 276 | }; 277 | var google = { 278 | _json: { 279 | given_name: 'googFirst', 280 | family_name: 'googLast', 281 | email: 'newname@test.com', 282 | gender: 'female', 283 | birthday: '1982-06-16', 284 | id: "23578291208945903329475" 285 | } 286 | }; 287 | // beforeEach(function(done){ 288 | // // TODO: Need to link a facebook / google account 289 | // authService.loginOrCreate('facebook', facebook, function(err, user){ 290 | // done(); 291 | // }); 292 | // }); 293 | 294 | // check whether we should do this here? 295 | it('should allow linking of new accounts', function(done){ 296 | authService.loginOrCreate('google', google, function(err, user){ 297 | should.exist(user); 298 | done(); 299 | }); 300 | }); 301 | 302 | // need to be able to put 303 | it('should return linked accounts', function(done){ 304 | authService.loginOrCreate('facebook', facebook, function(err, user){ 305 | request 306 | .get('/api/account/linkedAccounts') 307 | .agent(agent) 308 | .end(function(err, res){ 309 | res.body.should.be.an.array; 310 | res.body.should.have.length(2); 311 | should.exist(res.body[0]); 312 | //TODO: content test 313 | done(); 314 | }); 315 | }); 316 | }); 317 | 318 | it('should allow the removal of a linked account', function(done){ 319 | authService.loginOrCreate('facebook', facebook, function(err, user){ 320 | request 321 | .get('/api/account/linkedAccounts') 322 | .agent(agent) 323 | .end(function(err, res){ 324 | request 325 | .del('/api/account/linkedAccounts/'+res.body[0]._id) 326 | .agent(agent) 327 | .end(function(err, res){ 328 | res.should.have.status(200); 329 | done(); 330 | }); 331 | }); 332 | }); 333 | }); 334 | it('should reject the removal of a linked account with a bad id', function(done){ 335 | authService.loginOrCreate('facebook', facebook, function(err, user){ 336 | request 337 | .get('/api/account/linkedAccounts') 338 | .agent(agent) 339 | .end(function(err, res){ 340 | request 341 | .del('/api/account/linkedAccounts/1234') 342 | .agent(agent) 343 | .end(function(err, res){ 344 | res.should.have.status(412); 345 | done(); 346 | }); 347 | }); 348 | }); 349 | }); 350 | }); 351 | 352 | // All returning 500 - not sure why - but 500 works 353 | describe('logged out:', function(){ 354 | // Logged Out 355 | beforeEach(function(done){ 356 | request 357 | .post('/api/user/logout') 358 | .agent(agent) 359 | .end(function(err, res){ 360 | agent.saveCookies(res); 361 | done(); 362 | }); 363 | }); 364 | it('should not return account details if logged out', function(done){ 365 | request 366 | .get('/api/user/current-user') 367 | .agent(agent) 368 | .end(function(err, res){ 369 | res.should.have.status(200); 370 | should.not.exist(res.body.user); 371 | done(); 372 | }); 373 | }); 374 | 375 | it('should fail account update', function(done){ 376 | request 377 | .put('/api/account') 378 | .agent(agent) 379 | .send({first: 'First', last: 'Name', email: 'NewName@test.com', gender: 'male', password: 'TestPassword'}) 380 | .end(function(err, res){ 381 | should.not.exist(err); 382 | res.should.have.status(401); 383 | done(); 384 | }); 385 | }); 386 | 387 | it('should fail password update', function(done){ 388 | request 389 | .put('/api/account/editPassword') 390 | .agent(agent) 391 | .send({currentPassword: fakeUser.password, newPassword: 'Testing', passwordConfirm: 'Testing'}) 392 | .end(function(err, res){ 393 | res.should.have.status(401); 394 | User.findOne({email: fakeUser.email.toLowerCase()}, function(err, user){ 395 | user.checkPassword(fakeUser.password, function(err, isMatch){ 396 | isMatch.should.be.true; 397 | done(); 398 | }); 399 | }); 400 | }); 401 | }); 402 | 403 | it('should not return login tokens', function(done){ 404 | request 405 | .get('/api/account/security') 406 | .agent(agent) 407 | .end(function(err, res){ 408 | should.not.exist(res.body[0]); 409 | res.should.have.status(401); 410 | done(); 411 | }); 412 | }); 413 | 414 | // it('should fail login token delete', function(done){ 415 | // test 416 | // done(); 417 | // }); 418 | 419 | it('should not return linked accounts', function(done){ 420 | request 421 | .get('/api/account/linkedAccounts') 422 | .agent(agent) 423 | .end(function(err, res){ 424 | should.not.exist(res.body[0]); 425 | done(); 426 | }); 427 | }); 428 | }); 429 | }); -------------------------------------------------------------------------------- /server/test/unit/server.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var app = require('../../index') 4 | , should = require('should') 5 | , express = require('express') 6 | , RedisStore = require("connect-redis")(express); 7 | 8 | describe('app', function(){ 9 | it('should expose app settings', function(done){ 10 | var obj = app.locals.settings; 11 | obj.should.have.property('env', 'test'); 12 | done(); 13 | }); 14 | }); 15 | 16 | describe('sessions', function(){ 17 | var store = new RedisStore(); 18 | var sessionData = { 19 | cookie: { 20 | maxAge: 2000 21 | }, 22 | name: 'tj' 23 | }; 24 | // TODO: Find out why this before statement times out 25 | // Is there a risk that the store will not be instantiated when the tests run 26 | // before(function(done){ 27 | // store.client.on('connect', function(){ 28 | // done(); 29 | // }); 30 | // }); 31 | it('should be able to store sessions', function(done){ 32 | store.set('123', sessionData, function(err, ok){ 33 | should.not.exist(err); 34 | should.exist(ok); 35 | done(); 36 | }); 37 | }); 38 | it('should be able to retrieve sessions', function(done){ 39 | store.get('123', function(err, data){ 40 | should.not.exist(err); 41 | data.should.be.a('object').and.have.property('name', sessionData.name); 42 | data.cookie.should.be.a('object').and.have.property('maxAge', sessionData.cookie.maxAge ); 43 | done(); 44 | }); 45 | }); 46 | after(function(done){ 47 | store.set('123', sessionData, function(){ 48 | store.destroy('123', function(){ 49 | store.client.end(); 50 | done(); 51 | }); 52 | }); 53 | }); 54 | }); 55 | 56 | // describe('in development', function(){ 57 | // // it('should enable "view cache"', function(){ 58 | // // process.env.NODE_ENV = 'development'; 59 | // // app.enabled('view cache').should.be.false; 60 | // // process.env.NODE_ENV = 'test'; 61 | // // }), 62 | // // This doesn't work because the variable is only being 63 | // // changed now. Would need to change it before instantiating app 64 | // it('should enable verbose errors', function(){ 65 | // process.env.NODE_ENV = 'development'; 66 | // app.enabled('verbose errors').should.be.true; 67 | // process.env.NODE_ENV = 'test'; 68 | // }) 69 | // }); -------------------------------------------------------------------------------- /src/.buildignore: -------------------------------------------------------------------------------- 1 | *.coffee -------------------------------------------------------------------------------- /src/404.tpl.html: -------------------------------------------------------------------------------- 1 |

This is the 404 page!

-------------------------------------------------------------------------------- /src/apple-touch-icon-114x114-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rorymadden/angular-neo4j/d8d68a398a27c3dd787fdad4fd28910f5f8a60ce/src/apple-touch-icon-114x114-precomposed.png -------------------------------------------------------------------------------- /src/apple-touch-icon-144x144-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rorymadden/angular-neo4j/d8d68a398a27c3dd787fdad4fd28910f5f8a60ce/src/apple-touch-icon-144x144-precomposed.png -------------------------------------------------------------------------------- /src/apple-touch-icon-57x57-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rorymadden/angular-neo4j/d8d68a398a27c3dd787fdad4fd28910f5f8a60ce/src/apple-touch-icon-57x57-precomposed.png -------------------------------------------------------------------------------- /src/apple-touch-icon-72x72-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rorymadden/angular-neo4j/d8d68a398a27c3dd787fdad4fd28910f5f8a60ce/src/apple-touch-icon-72x72-precomposed.png -------------------------------------------------------------------------------- /src/apple-touch-icon-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rorymadden/angular-neo4j/d8d68a398a27c3dd787fdad4fd28910f5f8a60ce/src/apple-touch-icon-precomposed.png -------------------------------------------------------------------------------- /src/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rorymadden/angular-neo4j/d8d68a398a27c3dd787fdad4fd28910f5f8a60ce/src/apple-touch-icon.png -------------------------------------------------------------------------------- /src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rorymadden/angular-neo4j/d8d68a398a27c3dd787fdad4fd28910f5f8a60ce/src/favicon.ico -------------------------------------------------------------------------------- /src/font/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rorymadden/angular-neo4j/d8d68a398a27c3dd787fdad4fd28910f5f8a60ce/src/font/FontAwesome.otf -------------------------------------------------------------------------------- /src/font/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rorymadden/angular-neo4j/d8d68a398a27c3dd787fdad4fd28910f5f8a60ce/src/font/fontawesome-webfont.eot -------------------------------------------------------------------------------- /src/font/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rorymadden/angular-neo4j/d8d68a398a27c3dd787fdad4fd28910f5f8a60ce/src/font/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /src/font/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rorymadden/angular-neo4j/d8d68a398a27c3dd787fdad4fd28910f5f8a60ce/src/font/fontawesome-webfont.woff -------------------------------------------------------------------------------- /src/home.tpl.html: -------------------------------------------------------------------------------- 1 |

This is the home page!

-------------------------------------------------------------------------------- /src/images/facebook.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rorymadden/angular-neo4j/d8d68a398a27c3dd787fdad4fd28910f5f8a60ce/src/images/facebook.png -------------------------------------------------------------------------------- /src/images/glyphicons-halflings-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rorymadden/angular-neo4j/d8d68a398a27c3dd787fdad4fd28910f5f8a60ce/src/images/glyphicons-halflings-white.png -------------------------------------------------------------------------------- /src/images/glyphicons-halflings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rorymadden/angular-neo4j/d8d68a398a27c3dd787fdad4fd28910f5f8a60ce/src/images/glyphicons-halflings.png -------------------------------------------------------------------------------- /src/images/google.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rorymadden/angular-neo4j/d8d68a398a27c3dd787fdad4fd28910f5f8a60ce/src/images/google.png -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Title 11 | 12 | 13 | 14 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 25 | 26 | 30 | 31 | 32 |
33 | 64 |
65 | 73 |
74 |
75 | 76 | 77 |
78 |
79 | 80 | 81 | 82 |
83 |
84 | 85 |
86 | 87 | 88 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 124 | 125 | 126 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | -------------------------------------------------------------------------------- /src/robots.txt: -------------------------------------------------------------------------------- 1 | # robotstxt.org 2 | 3 | User-agent: * 4 | -------------------------------------------------------------------------------- /src/scripts/app.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var app = angular.module('angularNeo4jApp', [ 4 | 'ui.state', 5 | 'ui.bootstrap', 6 | 'security', 7 | 'account', 8 | 'system.messages', 9 | 'services.breadcrumbs', 10 | 'services.i18nNotifications', 11 | 'services.httpRequestTracker', 12 | 'services.titleService', 13 | 'templates-main' 14 | ]); 15 | 16 | app.config(['$stateProvider', '$locationProvider', 'securityAuthorizationProvider', '$urlRouterProvider', function ($stateProvider, $locationProvider, securityAuthorizationProvider, $urlRouterProvider) { 17 | $locationProvider.html5Mode(true); 18 | $stateProvider 19 | .state('home', { 20 | url: '/', 21 | resolve: app.defaultHome, 22 | templateUrl: 'home.tpl.html' 23 | }) 24 | .state('404', { 25 | url: '/404', 26 | templateUrl: '404.tpl.html' 27 | }); 28 | $urlRouterProvider 29 | .otherwise('/404'); 30 | }]); 31 | 32 | // redirect authenticated user to home page if accessing a page that is for anonymous users 33 | app.defaultHome = { 34 | authenticated : ['security', '$state', '$q', function(security, $state, $q){ 35 | var deferred = $q.defer(); 36 | security.requestCurrentUser().then(function(user){ 37 | if(!user) $state.transitionTo('register.show'); 38 | deferred.resolve(); 39 | }); 40 | return deferred.promise; 41 | }] 42 | }; 43 | 44 | // Get the current user when the application starts 45 | // (in case they are still logged in from a previous session) 46 | app.run(['security', function (security) { 47 | security.requestCurrentUser(); 48 | }]); 49 | 50 | app.run(['titleService', function (titleService) { 51 | // used to set the page title which will be outside of each route controller 52 | titleService.setPrefix('Angular Neo4j | ' ); 53 | }]); 54 | 55 | 56 | // the AppCtrl handles the management of notifications. 57 | // if there is ever an error there will be a generic error 58 | // if there is a successful route change then the notifications for that url will be requested 59 | app.controller('AppCtrl', ['$scope', 'i18nNotifications', '$state', function ($scope, i18nNotifications, $state) { 60 | $scope.loading = false; 61 | // handle the notifications for 62 | $scope.notifications = i18nNotifications; 63 | 64 | $scope.removeNotification = function (notification) { 65 | i18nNotifications.remove(notification); 66 | }; 67 | 68 | $scope.$on("$stateChangeStart", function () { 69 | i18nNotifications.removeAll(); 70 | //show spinner 71 | $scope.loading = true; 72 | }); 73 | 74 | $scope.$on("$stateChangeError", function (event, toState, toParams, fromState, fromParams, error) { 75 | $state.transitionTo(fromState.name, {}, true); 76 | i18nNotifications.removeAll(); 77 | i18nNotifications.pushForCurrentRoute('generic.routeError', 'error', {}, {error: error}); 78 | }); 79 | 80 | $scope.$on('$stateChangeSuccess', function(){ 81 | // remove spinner 82 | $scope.loading = false; 83 | 84 | // get any messages that have been set for this route 85 | i18nNotifications.getCurrent(); 86 | }); 87 | }]); 88 | 89 | // the HeaderCtrl keeps track of were the user is and changes the links accordingly 90 | app.controller('HeaderCtrl', function ($scope, $state, security, breadcrumbs, notifications, httpRequestTracker) { 91 | $scope.location = $state; 92 | $scope.menuOpen = false; 93 | 94 | $scope.isAuthenticated = security.isAuthenticated; 95 | $scope.isAdmin = security.isAdmin; 96 | 97 | $scope.home = function () { 98 | if (security.isAuthenticated()) { 99 | $state.transitionTo('home'); 100 | } else { 101 | $state.transitionTo('register.show'); 102 | } 103 | }; 104 | 105 | $scope.toggleMenu = function() { 106 | $scope.menuOpen = !$scope.menuOpen; 107 | }; 108 | 109 | $scope.closeMenu = function() { 110 | $scope.menuOpen = false; 111 | }; 112 | 113 | // close the menu when a route is changed 114 | $scope.$watch('$stateChangeStart', function() { 115 | $scope.closeMenu(); 116 | }); 117 | // close the menu when a route is changed 118 | $scope.$on('$stateChangeSuccess', function() { 119 | $scope.breadcrumbs = breadcrumbs.getAll(); 120 | }); 121 | 122 | $scope.isNavbarActive = function (navBarPath) { 123 | return navBarPath === breadcrumbs.getParentRoute(); 124 | }; 125 | 126 | $scope.hasPendingRequests = function () { 127 | return httpRequestTracker.hasPendingRequests(); 128 | }; 129 | }); -------------------------------------------------------------------------------- /src/scripts/common/account/account.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var accountModule = angular.module('account', ['account.register', 'ui.bootstrap.dialog', 'rorymadden.date-dropdowns']); 4 | 5 | accountModule.controller('AccountCtrl', ['$scope', 'security', function ($scope, security){ 6 | security.requestCurrentUser().then(function(user){ 7 | $scope.user = angular.copy(user); 8 | $scope.account = angular.copy(user); 9 | }); 10 | 11 | $scope.$on('UserUpdateCancelled', function(event, account){ 12 | $scope.account = account; 13 | }); 14 | 15 | $scope.$watch(function () { 16 | return security.currentUser; 17 | }, function (currentUser) { 18 | $scope.user = currentUser; 19 | }); 20 | }]); 21 | 22 | accountModule.controller('AccountViewCtrl', ['$scope', '$http', 'i18nNotifications', 'titleService', 'security', function ($scope, $http, i18nNotifications, titleService, security) { 23 | titleService.setTitle('Account'); 24 | 25 | $scope.editable = false; 26 | $scope.genders = [ 27 | { name: 'Male', value: 'male' }, 28 | { name: 'Female', value: 'female' } 29 | ]; 30 | 31 | $scope.edit = function(){ 32 | // toggle the disabled value 33 | $scope.editable = true; 34 | }; 35 | 36 | $scope.cancelEdit = function(){ 37 | $scope.account = angular.copy($scope.user); 38 | $scope.editable = false; 39 | $scope.$emit('UserUpdateCancelled', $scope.account); 40 | }; 41 | 42 | $scope.editCheck = function(){ 43 | return $scope.editable; 44 | }; 45 | 46 | $scope.update = function(){ 47 | $http.put('/api/account', $scope.account) 48 | .success(function(){ 49 | i18nNotifications.removeAll(); 50 | i18nNotifications.pushForCurrentRoute('common.account.updated', 'success', {}, {}); 51 | delete $scope.account.password; 52 | 53 | // set the currentUser to the updated value 54 | // triggers watches 55 | security.currentUser = angular.copy($scope.account); 56 | 57 | // toggle uneditable again 58 | $scope.editable = false; 59 | }) 60 | .error(function(data) { 61 | $scope.account.password = null; 62 | i18nNotifications.removeAll(); 63 | i18nNotifications.pushForCurrentRoute(data, 'error', {}, {}); 64 | }); 65 | }; 66 | }]); 67 | 68 | var AccountLinkedCtrl = accountModule.controller('AccountLinkedCtrl', ['$scope', '$http', 'i18nNotifications', 'titleService', 'localizedMessages', '$dialog', '$window', 'linkedAccounts', function ($scope, $http, i18nNotifications, titleService, localizedMessages, $dialog, $window, linkedAccounts) { 69 | titleService.setTitle('Account: Linked Accounts'); 70 | 71 | //process resolved data 72 | var data = linkedAccounts.data; 73 | var i, len = data.length; 74 | for(i=0; i 2 && arr[i][attr] === value )){ 181 | arr.splice(i,1); 182 | } 183 | } 184 | return arr; 185 | }; 186 | 187 | removeByAttr($scope.cookies, '_id', id); 188 | }) 189 | .error(function(data){ 190 | i18nNotifications.removeAll(); 191 | i18nNotifications.pushForCurrentRoute(data, 'error', {}, {}); 192 | }); 193 | }; 194 | }]); 195 | 196 | // resolve cookies for accounts, to ensure that the page doesn't show up without information 197 | AccountGetLoginTokensCtrl.getLoginTokens = { 198 | cookies: ['$http', function($http) { 199 | return $http({ 200 | method: 'GET', 201 | url: '/api/account/security' 202 | }); 203 | }] 204 | }; 205 | 206 | 207 | 208 | accountModule.config(['$stateProvider', 'securityAuthorizationProvider', function ($stateProvider, securityAuthorizationProvider) { 209 | $stateProvider 210 | // need an abstract account as even though I will navigate there. I need it to behave as a parent 211 | .state('account', { 212 | url: '/account', 213 | resolve: securityAuthorizationProvider.requireAuthenticatedUser, 214 | abstract: true, 215 | templateUrl: 'scripts/common/account/assets/templates/account.tpl.html', 216 | controller: 'AccountCtrl' 217 | }) 218 | .state('account.show', { 219 | url: '', 220 | templateUrl: 'scripts/common/account/assets/templates/accountShow.tpl.html', 221 | controller: 'AccountViewCtrl' 222 | }) 223 | .state('account.editpassword', { 224 | url: '/editPassword', 225 | templateUrl: 'scripts/common/account/assets/templates/accountPassword.tpl.html', 226 | controller: 'AccountPasswordCtrl' 227 | }) 228 | .state('account.security', { 229 | url: '/security', 230 | templateUrl: 'scripts/common/account/assets/templates/accountSecurity.tpl.html', 231 | controller: 'AccountGetLoginTokensCtrl', 232 | resolve: AccountGetLoginTokensCtrl.getLoginTokens 233 | }) 234 | .state('account.linkedaccounts', { 235 | url: '/linkedAccounts', 236 | templateUrl: 'scripts/common/account/assets/templates/accountLinkedAccounts.tpl.html', 237 | controller: 'AccountLinkedCtrl', 238 | resolve: AccountLinkedCtrl.getLinkedAccounts 239 | }); 240 | // .when('/account/deactivate', { 241 | // templateUrl: 'views/account/accountDeactivate.html', 242 | // controller: 'AccountCtrl' 243 | // }) 244 | }]); 245 | 246 | // accountModule.config(['$routeProvider', function($routeProvider){ 247 | // $routeProvider 248 | // .when('/account', { 249 | // templateUrl: 'scripts/common/account/assets/templates/account.tpl.html', 250 | // controller: 'AccountViewCtrl' 251 | // }) 252 | // .when('/account/editPassword', { 253 | // templateUrl: 'scripts/common/account/assets/templates/accountPassword.tpl.html', 254 | // controller: 'AccountPasswordCtrl' 255 | // }) 256 | // }]); 257 | 258 | -------------------------------------------------------------------------------- /src/scripts/common/account/assets/templates/account.tpl.html: -------------------------------------------------------------------------------- 1 |

{{account.first}} {{account.last}}

2 | 3 |
4 |
5 |
6 |
7 |
-------------------------------------------------------------------------------- /src/scripts/common/account/assets/templates/accountLinkedAccounts.tpl.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

Facebook 4 | 5 | 6 | 7 | 8 |

9 | 10 |
11 |
12 |

Link your Facebook Account

13 | 14 | 15 |   Facebook     16 | 17 |
18 |
19 | 20 |
21 |
22 |

Google 23 | 24 | 25 | 26 | 27 | 28 |

29 | 30 |
31 |
32 |

Link your Google Account

33 | 34 | 35 |   Google         36 | 37 |
38 |
39 | -------------------------------------------------------------------------------- /src/scripts/common/account/assets/templates/accountMenu.tpl.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/scripts/common/account/assets/templates/accountPassword.tpl.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 5 |
6 | 7 |
8 |
9 | 10 |
11 | 12 |
13 | 14 |
15 | 16 |
17 |
18 | 19 |
20 | 21 |
22 | 23 |
24 |
25 | 26 | 27 |
28 |
29 |
30 |

Request a password

31 |

As you have registered through a social site you have not setup a password for this site yet. Click on the button below and we'll send you out a link to create a password.

32 | 33 |
-------------------------------------------------------------------------------- /src/scripts/common/account/assets/templates/accountSecurity.tpl.html: -------------------------------------------------------------------------------- 1 |
2 |

Logged In

3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 27 | 28 | 29 |
#CountryCityBrowserOSDateRemove
{{$index + 1}}{{cookie.country}}{{cookie.city}}{{cookie.browser}}{{cookie.os}}{{cookie.created | date:'dd MMMM yyyy hh:mm a'}} 24 | 25 | 26 |
30 |
31 |
32 |

There are no cookies in use

33 |
-------------------------------------------------------------------------------- /src/scripts/common/account/assets/templates/accountShow.tpl.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 | 6 | First name is required. 7 |
8 |
9 | 10 |
11 | 12 |
13 | 14 | Last name is required. 15 |
16 |
17 | 18 |
19 | 20 |
21 | 22 | Email is required. 23 |
24 |
25 | 26 | 27 |
28 | 29 |
30 | 34 | Please select a gender. 35 |
36 |
37 | 38 |
39 | 40 |
41 | 42 | All date fields must be entered. 43 |
44 |
45 | 46 |
47 |
48 |

You need to enter your password to confirm these changes

49 | 50 |
51 | 52 |
53 | 54 |
55 |
56 |
57 | 58 | 59 | 60 | or cancel 61 |
62 |
63 |

As you have registered through a social site you have not setup a password for this site yet. If you want to edit any profile details please click on the button below to request a password.

64 | 65 |
-------------------------------------------------------------------------------- /src/scripts/common/account/assets/templates/benefits.tpl.html: -------------------------------------------------------------------------------- 1 |
2 |

Welcome to App!

3 |

Some promotional message goes here. Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit.

4 |

Explore »

5 |
-------------------------------------------------------------------------------- /src/scripts/common/account/assets/templates/changeForgottenPassword.tpl.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

Reset Password

4 | 5 | 6 | 7 |
8 | 9 | 10 | Password must be a minimum of 6 characters 11 |
12 | 13 |
14 | 15 | 16 | Passwords do not match 17 |
18 | 19 | 20 | or 21 | 22 |
23 |
-------------------------------------------------------------------------------- /src/scripts/common/account/assets/templates/forgotPassword.tpl.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

Forgot Password

4 | 5 | 6 | 7 |
8 | 9 | 10 | Please enter a valid email. 11 |
12 | 13 | 14 | or 15 | 16 |
17 |
-------------------------------------------------------------------------------- /src/scripts/common/account/assets/templates/register.tpl.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
-------------------------------------------------------------------------------- /src/scripts/common/account/assets/templates/registerShow.tpl.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

Sign Up

4 | 7 |
8 | 9 |
10 |
11 | 12 |
13 |
14 | 15 | Please enter a valid email. 16 |
17 |
18 | 19 | Password must be a minimum of 6 characters. 20 |
21 |
22 | 26 |
27 | 28 | 29 | 30 | 31 |

32 |

Or sign up with
33 | 34 | 35 |

36 | 37 |

By clicking Sign Up, you confirm that you agree to our User Agreement.

38 |
39 |
-------------------------------------------------------------------------------- /src/scripts/common/account/assets/templates/resendActivation.tpl.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

Resend Activation

4 | 5 |
6 | 7 | 9 | Please enter a valid email. 10 |
11 | 12 | 13 | or 14 | 15 |
16 |
-------------------------------------------------------------------------------- /src/scripts/common/account/assets/templates/terms.tpl.html: -------------------------------------------------------------------------------- 1 | 4 | 86 | -------------------------------------------------------------------------------- /src/scripts/common/account/register.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var registerModule = angular.module('account.register', ['services.i18nNotifications', 'ui.bootstrap.dialog']); 4 | 5 | registerModule.controller('RegisterCtrl', ['$scope', '$http', 'i18nNotifications', 'titleService', '$dialog', '$window', '$state', function ($scope, $http, i18nNotifications, titleService, $dialog, $window, $state) { 6 | $scope.user = {}; 7 | titleService.setTitle('Register'); 8 | 9 | $scope.genders = [ 10 | { name: 'Male', value: 'male' }, 11 | { name: 'Female', value: 'female' } 12 | ]; 13 | 14 | $scope.registerUser = function () { 15 | $http.post('/api/user/register', $scope.user) 16 | .success(function() { 17 | $state.transitionTo('login'); 18 | i18nNotifications.pushForNextRoute('common.register.success', 'success', {}, {}); 19 | $scope.user = null; 20 | }) 21 | // .error(function(data, status, headers, config) { 22 | .error(function(data) { 23 | i18nNotifications.pushForCurrentRoute(data, 'error', {}, {}); 24 | }); 25 | }; 26 | 27 | $scope.oAuth = function(provider){ 28 | //refresh screen to hit the server route instead of angular 29 | $window.location.href = '/auth/' + provider; 30 | }; 31 | 32 | var dialogBox = null; 33 | function openDialog(url, controller) { 34 | if ( !dialogBox ) { 35 | dialogBox = $dialog.dialog(); 36 | } 37 | dialogBox.open(url, controller).then(onDialogClose); 38 | } 39 | 40 | function onDialogClose() { 41 | angular.noop(); 42 | } 43 | 44 | $scope.terms = function(){ 45 | openDialog('scripts/common/account/assets/templates/terms.tpl.html'); 46 | }; 47 | }]); 48 | 49 | registerModule.controller('ResendActivationCtrl', ['$scope', '$http', 'i18nNotifications', 'titleService', '$state', function ($scope, $http, i18nNotifications, titleService, $state) { 50 | titleService.setTitle('Resend Activation'); 51 | $scope.user = {}; 52 | 53 | $scope.resendActivation = function () { 54 | $http.post('/api/user/resendActivation', $scope.user) 55 | .success(function() { 56 | $state.transitionTo('login'); 57 | i18nNotifications.pushForNextRoute('common.register.activationKeyResent', 'success', {}, {}); 58 | $scope.user = null; 59 | }) 60 | .error(function(data) { 61 | i18nNotifications.pushForCurrentRoute(data, 'error', {}, {}); 62 | }); 63 | }; 64 | }]); 65 | 66 | registerModule.controller('ForgotPasswordCtrl', ['$scope', '$http', '$state', 'i18nNotifications', 'titleService', function ($scope, $http, $state, i18nNotifications, titleService) { 67 | titleService.setTitle('Forgot Password'); 68 | $scope.user = {}; 69 | 70 | $scope.forgotPassword = function(){ 71 | $http.post('/api/user/forgotPassword', $scope.user) 72 | .success(function(){ 73 | i18nNotifications.pushForCurrentRoute('common.password.passwordResetLinkSent', 'success', {}, {}); 74 | $scope.user = null; 75 | }) 76 | .error(function(data){ 77 | $scope.user = null; 78 | $state.transitionTo('register.show'); 79 | i18nNotifications.pushForNextRoute(data, 'error', {}, {}); 80 | }); 81 | }; 82 | }]); 83 | 84 | registerModule.controller('ChangeForgottenPwdCtrl', ['$scope', '$http', '$state', '$stateParams', 'i18nNotifications', 'titleService', 'security', function ($scope, $http, $state, $stateParams, i18nNotifications, titleService, security) { 85 | $scope.user = {}; 86 | titleService.setTitle('Reset Password'); 87 | 88 | $scope.changeForgottenPassword = function(){ 89 | // copy the route params to the user object for posting to the server 90 | $scope.user.user_id = $stateParams.user_id; 91 | $scope.user.passwordResetKey = $stateParams.passwordResetKey; 92 | 93 | $http.post('/api/user/resetPassword', $scope.user) 94 | .success(function(){ 95 | $state.transitionTo('home'); 96 | // force new current user in case of setting a password for already logged in user (e.g. registered from facebook) 97 | security.requestCurrentUser(true); 98 | i18nNotifications.pushForNextRoute('common.password.passwordChangeSuccess', 'success', {}, {}); 99 | }) 100 | .error(function(data){ 101 | i18nNotifications.pushForCurrentRoute(data, 'error', {}, {}); 102 | }); 103 | }; 104 | }]); 105 | 106 | // redirect authenticated user to home page if accessing a page that is for anonymous users 107 | registerModule.ensureAnonymous = { 108 | authenticated : ['security', '$state', '$q', function(security, $state, $q){ 109 | var deferred = $q.defer(); 110 | security.requestCurrentUser().then(function(user){ 111 | // if(user) $location.path('/'); 112 | if(user) $state.transitionTo('home'); 113 | deferred.resolve(); 114 | }); 115 | return deferred.promise; 116 | }] 117 | }; 118 | 119 | // activate an account and redirecet 120 | registerModule.activate = { 121 | activate: ['$http', 'i18nNotifications', '$state', '$stateParams', function($http, i18nNotifications, $state, $stateParams) { 122 | $http.post('/api/user/activate', { activationKey: $stateParams.activationKey}) 123 | .success(function(){ 124 | i18nNotifications.pushForNextRoute('common.register.activationSuccess', 'success', {}, {}); 125 | // need to trigger the currentUser call to log in user. 126 | $state.transitionTo('home'); 127 | 128 | // return true so that the resolve function will pass 129 | return true; 130 | }) 131 | .error(function() { 132 | i18nNotifications.pushForNextRoute('common.register.activationFail', 'error', {}, {}); 133 | $state.transitionTo('register.resendActivation'); 134 | // return true so that the resolve function will pass 135 | return true; 136 | }); 137 | }] 138 | }; 139 | 140 | //copy url parameters to stateParams and redirect to nicer url 141 | registerModule.validatePasswordReset = { 142 | redirect: ['$stateParams', '$state', '$q', 'i18nNotifications', '$http', 143 | function($stateParams, $state, $q, i18nNotifications, $http){ 144 | 145 | $http.post('/api/user/validatePasswordReset', $stateParams) 146 | .success(function(){ 147 | // success 148 | // $state.transitionTo('register.changeForgottenPassword', $stateParams); 149 | return true; 150 | }) 151 | .error(function(data){ 152 | // error 153 | $state.transitionTo('register.forgotPassword'); 154 | i18nNotifications.pushForNextRoute(data, 'error', {}, {}); 155 | return true; 156 | }); 157 | }] 158 | }; 159 | 160 | registerModule.home = { 161 | redirect: ['$state', 'i18nNotifications', function($state, i18nNotifications){ 162 | console.log('called') 163 | $state.transitionTo('home'); 164 | i18nNotifications.pushForNextRoute('common.account.facebookSuccess', 'success', {}, {}); 165 | return true; 166 | }] 167 | }; 168 | 169 | registerModule.config(['$stateProvider', function ($stateProvider) { 170 | $stateProvider 171 | .state('register', { 172 | url: '/register', 173 | resolve: registerModule.ensureAnonymous, 174 | abstract:true, 175 | templateUrl: 'scripts/common/account/assets/templates/register.tpl.html', 176 | controller: 'RegisterCtrl' 177 | }) 178 | .state('register.show', { 179 | url: '', 180 | templateUrl: 'scripts/common/account/assets/templates/registerShow.tpl.html', 181 | controller: 'RegisterCtrl' 182 | }) 183 | .state('register.forgotPassword', { 184 | url: '/forgotPassword', 185 | templateUrl: 'scripts/common/account/assets/templates/forgotPassword.tpl.html', 186 | controller: 'ForgotPasswordCtrl' 187 | }) 188 | .state('register.resendActivation', { 189 | url: '/resendActivation', 190 | templateUrl: 'scripts/common/account/assets/templates/resendActivation.tpl.html', 191 | controller: 'ResendActivationCtrl' 192 | }) 193 | .state('register.resetPassword', { 194 | url: '/resetPassword/:user_id/:passwordResetKey', 195 | templateUrl: 'scripts/common/account/assets/templates/changeForgottenPassword.tpl.html', 196 | controller: 'ChangeForgottenPwdCtrl', 197 | resolve: registerModule.validatePasswordReset 198 | }) 199 | .state('register.activate', { 200 | url: '/:activationKey', 201 | resolve: registerModule.activate 202 | }); 203 | }]); -------------------------------------------------------------------------------- /src/scripts/common/security/authorization.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('security.authorization', ['security.service']) 4 | 5 | // This service provides guard methods to support AngularJS routes. 6 | // You can add them as resolves to routes to require authorization levels 7 | // before allowing a route change to complete 8 | .provider('securityAuthorization', { 9 | 10 | requireAdminUser: ['securityAuthorization', ['securityAuthorization', function (securityAuthorization) { 11 | return securityAuthorization.requireAdminUser(); 12 | }]], 13 | 14 | requireAuthenticatedUser: ['securityAuthorization', ['securityAuthorization', function (securityAuthorization) { 15 | return securityAuthorization.requireAuthenticatedUser(); 16 | }]], 17 | 18 | // isAuthenticated: ['securityAuthorization', function (securityAuthorization) { 19 | // return securityAuthorization.isAuthenticated(); 20 | // }], 21 | 22 | $get: ['security', 'securityRetryQueue', function (security, queue) { 23 | var service = { 24 | 25 | // Require that there is an authenticated user 26 | // (use this in a route resolve to prevent non-authenticated users from entering that route) 27 | requireAuthenticatedUser: function () { 28 | var promise = security.requestCurrentUser().then(function () { 29 | if (!security.isAuthenticated()) { 30 | return queue.pushRetryFn('unauthenticated-client', service.requireAuthenticatedUser); 31 | } 32 | }); 33 | return promise; 34 | }, 35 | 36 | // Require that there is an administrator logged in 37 | // (use this in a route resolve to prevent non-administrators from entering that route) 38 | requireAdminUser: function () { 39 | var promise = security.requestCurrentUser().then(function () { 40 | if (!security.isAdmin()) { 41 | return queue.pushRetryFn('unauthorized-client', service.requireAdminUser); 42 | } 43 | }); 44 | return promise; 45 | }, 46 | 47 | // Require that there is an administrator logged in 48 | // (use this in a route resolve to prevent non-administrators from entering that route) 49 | // isAuthenticated: function () { 50 | // var promise = security.requestCurrentUser().then(function () { 51 | // return security.isAuthenticated(); 52 | // }); 53 | // return promise; 54 | // } 55 | 56 | }; 57 | 58 | return service; 59 | }] 60 | }); -------------------------------------------------------------------------------- /src/scripts/common/security/index.js: -------------------------------------------------------------------------------- 1 | // Based loosely around work by Witold Szczerba - https://github.com/witoldsz/angular-http-auth 2 | angular.module('security', [ 3 | 'security.service', 4 | 'security.interceptor', 5 | 'security.login', 6 | 'security.authorization' 7 | ]); 8 | -------------------------------------------------------------------------------- /src/scripts/common/security/interceptor.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('security.interceptor', ['security.retryQueue']) 4 | 5 | // This http interceptor listens for authentication failures 6 | .factory('securityInterceptor', ['$injector', 'securityRetryQueue', function ($injector, queue) { 7 | return function (promise) { 8 | // Intercept failed requests 9 | return promise.then(null, function (originalResponse) { 10 | if (originalResponse.status === 401) { 11 | // The request bounced because it was not authorized - add a new request to the retry queue 12 | promise = queue.pushRetryFn('unauthorized-server', function retryRequest() { 13 | // We must use $injector to get the $http service to prevent circular dependency 14 | return $injector.get('$http')(originalResponse.config); 15 | }); 16 | } 17 | return promise; 18 | }); 19 | }; 20 | }]) 21 | 22 | // We have to add the interceptor to the queue as a string because the interceptor depends upon service instances that are not available in the config block. 23 | .config(['$httpProvider', function ($httpProvider) { 24 | $httpProvider.responseInterceptors.push('securityInterceptor'); 25 | }]); -------------------------------------------------------------------------------- /src/scripts/common/security/login/LoginFormController.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('security.login.form', ['services.localizedMessages', 'ngSanitize']) 4 | 5 | // The LoginFormController provides the behaviour behind a reusable form to allow users to authenticate. 6 | // This controller and its template (login/form.tpl.html) are used in a modal dialog box by the security service. 7 | .controller('LoginFormController', ['$scope', '$state', 'security', 'localizedMessages', 'titleService', 'I18N.MESSAGES', function ($scope, $state, security, localizedMessages, titleService, i18nMessages) { 8 | titleService.setTitle('Login'); 9 | 10 | // The model for this form 11 | $scope.user = {}; 12 | 13 | // Any error message from failing to login 14 | $scope.authError = null; 15 | 16 | // The reason that we are being asked to login - for instance because we tried to access something to which we are not authorized 17 | // We could do something diffent for each reason here but to keep it simple... 18 | $scope.authReason = null; 19 | if (security.getLoginReason()) { 20 | $scope.authReason = (security.isAuthenticated()) ? 21 | localizedMessages.get('common.login.notAuthorized') : 22 | localizedMessages.get('common.login.notAuthenticated'); 23 | } 24 | 25 | // Attempt to authenticate the user specified in the form's model 26 | $scope.login = function () { 27 | $scope.clicked=true; 28 | // Clear any previous security errors 29 | $scope.authError = null; 30 | 31 | // Try to login 32 | security.login($scope.user.email, $scope.user.password, $scope.user.remember_me).then(function () { 33 | 34 | if (!security.currentUser) { 35 | // If we get here then the login failed due to bad credentials 36 | $scope.authError = localizedMessages.get('common.login.loginFailed'); 37 | } 38 | else { 39 | $state.transitionTo('home'); 40 | } 41 | }, function (x) { 42 | // If we get here then there was a problem with the login request to the server 43 | var error = x.data.error || x.data; 44 | $scope.authError = localizedMessages.get(error); 45 | $scope.user.password = null; 46 | }); 47 | }; 48 | 49 | $scope.clearForm = function () { 50 | $scope.user = {}; 51 | }; 52 | 53 | $scope.cancelLogin = function () { 54 | if($state.current.name === 'login') $state.transitionTo('register.show'); 55 | else security.cancelLogin(); 56 | }; 57 | 58 | $scope.forgotPassword = function () { 59 | security.cancelLogin(); 60 | $state.transitionTo('register.forgotPassword'); 61 | }; 62 | 63 | $scope.resendActivation = function () { 64 | security.cancelLogin(); 65 | $state.transitionTo('register.resendActivation'); 66 | }; 67 | 68 | $scope.showResendLink = function() { 69 | if($scope.authError && ($scope.authError === i18nMessages[20300] || $scope.authError === i18nMessages[20340])) 70 | return true; 71 | else return false; 72 | }; 73 | }]); -------------------------------------------------------------------------------- /src/scripts/common/security/login/assets/templates/form.tpl.html: -------------------------------------------------------------------------------- 1 |
2 |

Login

3 |