diff --git a/lib/cli.js b/lib/cli.js index 18e7ada6..5fc479bf 100644 --- a/lib/cli.js +++ b/lib/cli.js @@ -194,6 +194,9 @@ program , 'session SSID (or $SSID)' , String , process.env.SSID || 'ssid') + .option('-a, --app-url ' + , 'URL to app' + , String) .option('-u, --ldap-url ' , 'LDAP server URL (or $LDAP_URL)' , String @@ -202,6 +205,10 @@ program , 'LDAP timeout (or $LDAP_TIMEOUT)' , Number , process.env.LDAP_TIMEOUT || 1000) + .option('--ldap-bind-enable ' + , 'LDAP bind DN (or $LDAP_BIND_DN)' + , String + , process.env.LDAP_BIND_DN) .option('--ldap-bind-dn ' , 'LDAP bind DN (or $LDAP_BIND_DN)' , String @@ -221,12 +228,24 @@ program .option('--ldap-search-class ' , 'LDAP search objectClass (or $LDAP_SEARCH_CLASS)' , String - , process.env.LDAP_SEARCH_CLASS || 'user') + , process.env.LDAP_SEARCH_CLASS || 'top') + .option('--ldap-search-field ' + , 'LDAP search field (or $LDAP_SEARCH_FIELD)' + , String + , process.env.LDAP_SEARCH_FIELD) .action(function(options) { + if (!options.secret) { + this.missingArgument('--secret') + } + if (!options.appUrl) { + this.missingArgument('--app-url') + } + require('./roles/auth/ldap')({ port: options.port , secret: options.secret , ssid: options.ssid + , appUrl: options.appUrl , ldap: { url: options.ldapUrl , timeout: options.ldapTimeout @@ -238,7 +257,7 @@ program dn: options.ldapSearchDn , scope: options.ldapSearchScope , objectClass: options.ldapSearchClass - , loginField: options.ldapSearchLoginField + , field: options.ldapSearchField } } }) diff --git a/lib/roles/auth/ldap.js b/lib/roles/auth/ldap.js index 87651dbd..faabb283 100644 --- a/lib/roles/auth/ldap.js +++ b/lib/roles/auth/ldap.js @@ -7,12 +7,18 @@ var logger = require('../../util/logger') var requtil = require('../../util/requtil') var ldaputil = require('../../util/ldaputil') var jwtutil = require('../../util/jwtutil') +var pathutil = require('../../util/pathutil') var urlutil = require('../../util/urlutil') module.exports = function(options) { var log = logger.createLogger('auth-ldap') , app = express() + app.set('view engine', 'jade') + app.set('views', pathutil.resource('auth-ldap/views')) + app.set('strict routing', true) + app.set('case sensitive routing', true) + app.use(express.cookieParser()) app.use(express.cookieSession({ secret: options.secret @@ -20,13 +26,34 @@ module.exports = function(options) { })) app.use(express.json()) app.use(express.urlencoded()) + app.use(express.csrf()) app.use(validator()) + app.use('/static/lib', express.static(pathutil.resource('lib'))) + app.use('/static', express.static(pathutil.resource('auth-ldap'))) - app.get('/auth', function(req, res) { - res.locals.csrf = req.csrfToken() + app.use(function(req, res, next) { + res.cookie('XSRF-TOKEN', req.csrfToken()); + next() }) - app.post('/auth', function(req, res) { + app.get('/partials/:name', function(req, res) { + var whitelist = { + 'signin': true + } + + if (whitelist[req.params.name]) { + res.render('partials/' + req.params.name) + } + else { + res.send(404) + } + }) + + app.get('/', function(req, res) { + res.render('index') + }) + + app.post('/api/v1/auth', function(req, res) { var log = logger.createLogger('auth-ldap') log.setLocalIdentifier(req.ip) switch (req.accepts(['json'])) { diff --git a/lib/util/ldaputil.js b/lib/util/ldaputil.js index e188fdc7..a13bb97d 100644 --- a/lib/util/ldaputil.js +++ b/lib/util/ldaputil.js @@ -25,14 +25,19 @@ module.exports.login = function(options, username, password) { , maxConnections: 1 }) - client.bind(options.bind.dn, options.bind.credentials, function(err) { - if (err) { - resolver.reject(err) - } - else { - resolver.resolve(client) - } - }) + if (options.bind.dn) { + client.bind(options.bind.dn, options.bind.credentials, function(err) { + if (err) { + resolver.reject(err) + } + else { + resolver.resolve(client) + } + }) + } + else { + resolver.resolve(client) + } return resolver.promise } @@ -48,7 +53,7 @@ module.exports.login = function(options, username, password) { , value: options.search.objectClass }) , new ldap.EqualityFilter({ - attribute: options.search.loginField + attribute: options.search.field , value: username }) ] diff --git a/res/auth-ldap/images/logo-128.png b/res/auth-ldap/images/logo-128.png new file mode 100644 index 00000000..e360432e Binary files /dev/null and b/res/auth-ldap/images/logo-128.png differ diff --git a/res/auth-ldap/scripts/app.js b/res/auth-ldap/scripts/app.js new file mode 100644 index 00000000..9e34d041 --- /dev/null +++ b/res/auth-ldap/scripts/app.js @@ -0,0 +1,11 @@ +define([ + 'angular' + , './controllers/index' + ] +, function(ng) { + return ng.module('app', [ + 'ngRoute' + , 'app.controllers' + ]) + } +) diff --git a/res/auth-ldap/scripts/bootstrap.js b/res/auth-ldap/scripts/bootstrap.js new file mode 100644 index 00000000..19442318 --- /dev/null +++ b/res/auth-ldap/scripts/bootstrap.js @@ -0,0 +1,11 @@ +define([ + 'require' + , 'angular' + , 'angular-route' + , 'app' + , 'routes' + ] +, function(require, ng) { + ng.bootstrap(document, ['app']) + } +) diff --git a/res/auth-ldap/scripts/controllers/SignInCtrl.js b/res/auth-ldap/scripts/controllers/SignInCtrl.js new file mode 100644 index 00000000..0e327f2b --- /dev/null +++ b/res/auth-ldap/scripts/controllers/SignInCtrl.js @@ -0,0 +1,37 @@ +define(['./module'], function(mod) { + mod.controller('SignInCtrl', ['$scope', '$http', function($scope, $http) { + $scope.error = null + + $scope.submit = function() { + var data = { + username: $scope.signin.username.$modelValue + , password: $scope.signin.password.$modelValue + } + $scope.invalid = false + $http.post('/api/v1/auth', data) + .success(function(response) { + $scope.error = null + location.replace(response.redirect) + }) + .error(function(response) { + switch (response.error) { + case 'ValidationError': + $scope.error = { + $invalid: true + } + break + case 'InvalidCredentialsError': + $scope.error = { + $incorrect: true + } + break + default: + $scope.error = { + $server: true + } + break + } + }) + } + }]) +}) diff --git a/res/auth-ldap/scripts/controllers/index.js b/res/auth-ldap/scripts/controllers/index.js new file mode 100644 index 00000000..8a4b268a --- /dev/null +++ b/res/auth-ldap/scripts/controllers/index.js @@ -0,0 +1,6 @@ +define([ + './SignInCtrl' + ] +, function() { + } +) diff --git a/res/auth-ldap/scripts/controllers/module.js b/res/auth-ldap/scripts/controllers/module.js new file mode 100644 index 00000000..dc663078 --- /dev/null +++ b/res/auth-ldap/scripts/controllers/module.js @@ -0,0 +1,3 @@ +define(['angular'], function(ng) { + return ng.module('app.controllers', []) +}) diff --git a/res/auth-ldap/scripts/main.js b/res/auth-ldap/scripts/main.js new file mode 100644 index 00000000..8fd793b2 --- /dev/null +++ b/res/auth-ldap/scripts/main.js @@ -0,0 +1,19 @@ +require.config({ + paths: { + 'angular': '../lib/angular/angular' + , 'angular-route': '../lib/angular-route/angular-route' + } +, shim: { + 'angular': { + exports: 'angular' + } + , 'angular-route': { + deps: [ + 'angular' + ] + } + } +, deps: [ + './bootstrap' + ] +}) diff --git a/res/auth-ldap/scripts/routes.js b/res/auth-ldap/scripts/routes.js new file mode 100644 index 00000000..d207b550 --- /dev/null +++ b/res/auth-ldap/scripts/routes.js @@ -0,0 +1,17 @@ +define(['./app'], function(app) { + return app.config([ + '$routeProvider' + , '$locationProvider' + , function($routeProvider, $locationProvider) { + $locationProvider.html5Mode(true) + $routeProvider + .when('/', { + templateUrl: 'partials/signin' + , controller: 'SignInCtrl' + }) + .otherwise({ + redirectTo: '/' + }) + } + ]) +}) diff --git a/res/auth-ldap/styles/login.css b/res/auth-ldap/styles/login.css new file mode 100644 index 00000000..c047b285 --- /dev/null +++ b/res/auth-ldap/styles/login.css @@ -0,0 +1,79 @@ +body { + background: #eeeeee; +} + +.login2 { + padding: 30px 0 0; + background: #eeeeee; +} + +.login2 .login-wrapper { + max-width: 420px; + margin: 0 auto; + text-align: center; +} + +.login2 .login-wrapper img { + margin: 40px auto; +} + +.login2 .login-wrapper .input-group-addon { + padding: 8px 0; + background: #f4f4f4; + min-width: 48px; + text-align: center; +} + +.login2 .login-wrapper .input-group-addon i.falock { + font-size: 18px; +} + +.login2 .login-wrapper input.form-control { + height: 48px; + font-size: 15px; + box-shadow: none; +} + +.login2 .login-wrapper .checkbox { + margin-bottom: 30px; +} + +.login2 .login-wrapper input[type="submit"] { + padding: 10px 0 12px; + margin: 20px 0 30px; +} + +.login2 .login-wrapper input[type="submit"]:hover { + background: transparent; +} + +.login2 .login-wrapper .social-login { + margin-bottom: 20px; + padding-bottom: 25px; + border-bottom: 1px solid #cccccc; +} + +.login2 .login-wrapper .social-login > .btn { + width: 49%; + margin: 0; +} + +.login2 .login-wrapper .social-login .facebook { + background-color: #335397; + border-color: #335397; +} + +.login2 .login-wrapper .social-login .facebook:hover { + background-color: transparent; + color: #335397; +} + +.login2 .login-wrapper .social-login .twitter { + background-color: #00c7f7; + border-color: #00c7f7; +} + +.login2 .login-wrapper .social-login .twitter:hover { + background-color: transparent; + color: #00c7f7; +} diff --git a/res/auth-ldap/views/index.jade b/res/auth-ldap/views/index.jade new file mode 100644 index 00000000..587c23e5 --- /dev/null +++ b/res/auth-ldap/views/index.jade @@ -0,0 +1,9 @@ +doctype html +html + head + title STF + meta(charset='utf-8') + include partials/styles + body(ng-cloak) + div(ng-view) + script(src='/static/lib/requirejs/require.js', data-main='static/scripts/main.js') diff --git a/res/auth-ldap/views/partials/signin.jade b/res/auth-ldap/views/partials/signin.jade new file mode 100644 index 00000000..d3fda513 --- /dev/null +++ b/res/auth-ldap/views/partials/signin.jade @@ -0,0 +1,28 @@ +.login2 + .login-wrapper + a(href='./') + img(width='128', height='128', src='/static/images/logo-128.png', title='STF') + + form(name='signin', novalidate, ng-submit='submit()') + .alert.alert-danger(ng-show='error') + span(ng-show='error.$invalid') Check errors below + span(ng-show='error.$incorrect') Incorrect login details + span(ng-show='error.$system') System error + + .form-group + .input-group + span.input-group-addon + i.fa.fa-envelope + input.form-control(ng-model='username', name='username', required, type='text', placeholder='LDAP Username') + .alert.alert-warning(ng-show='signin.username.$dirty && signin.username.$invalid') + span(ng-show='signin.username.$error.required') Please enter your LDAP username + + .form-group + .input-group + span.input-group-addon + i.fa.fa-lock + input.form-control(ng-model='password', name='password', required, type='password', placeholder='Password') + .alert.alert-warning(ng-show='signin.password.$dirty && signin.password.$invalid') + span Please enter your password + + input.btn.btn-lg.btn-primary.btn-block(type='submit', value='Log in') diff --git a/res/auth-ldap/views/partials/styles.jade b/res/auth-ldap/views/partials/styles.jade new file mode 100644 index 00000000..9b52edd2 --- /dev/null +++ b/res/auth-ldap/views/partials/styles.jade @@ -0,0 +1,6 @@ +link(href='http://fonts.googleapis.com/css?family=Lato:100,300,400,700', media='all', rel='stylesheet', type='text/css') +link(rel='stylesheet', href='/static/lib/se7en-bootstrap-3/build/stylesheets/bootstrap.min.css') +link(rel='stylesheet', href='/static/lib/se7en-bootstrap-3/build/stylesheets/se7en-font.css') +link(rel='stylesheet', href='/static/lib/se7en-bootstrap-3/build/stylesheets/style.css') +link(rel='stylesheet', href='/static/lib/se7en-bootstrap-3/build/stylesheets/font-awesome.min.css') +link(rel='stylesheet', href='/static/styles/login.css')