mirror of
https://github.com/openstf/stf
synced 2025-10-06 03:50:04 +02:00
Add UI to LDAP login.
This commit is contained in:
parent
01339b089e
commit
9119dcca63
15 changed files with 291 additions and 14 deletions
23
lib/cli.js
23
lib/cli.js
|
@ -194,6 +194,9 @@ program
|
||||||
, 'session SSID (or $SSID)'
|
, 'session SSID (or $SSID)'
|
||||||
, String
|
, String
|
||||||
, process.env.SSID || 'ssid')
|
, process.env.SSID || 'ssid')
|
||||||
|
.option('-a, --app-url <url>'
|
||||||
|
, 'URL to app'
|
||||||
|
, String)
|
||||||
.option('-u, --ldap-url <url>'
|
.option('-u, --ldap-url <url>'
|
||||||
, 'LDAP server URL (or $LDAP_URL)'
|
, 'LDAP server URL (or $LDAP_URL)'
|
||||||
, String
|
, String
|
||||||
|
@ -202,6 +205,10 @@ program
|
||||||
, 'LDAP timeout (or $LDAP_TIMEOUT)'
|
, 'LDAP timeout (or $LDAP_TIMEOUT)'
|
||||||
, Number
|
, Number
|
||||||
, process.env.LDAP_TIMEOUT || 1000)
|
, process.env.LDAP_TIMEOUT || 1000)
|
||||||
|
.option('--ldap-bind-enable <dn>'
|
||||||
|
, 'LDAP bind DN (or $LDAP_BIND_DN)'
|
||||||
|
, String
|
||||||
|
, process.env.LDAP_BIND_DN)
|
||||||
.option('--ldap-bind-dn <dn>'
|
.option('--ldap-bind-dn <dn>'
|
||||||
, 'LDAP bind DN (or $LDAP_BIND_DN)'
|
, 'LDAP bind DN (or $LDAP_BIND_DN)'
|
||||||
, String
|
, String
|
||||||
|
@ -221,12 +228,24 @@ program
|
||||||
.option('--ldap-search-class <class>'
|
.option('--ldap-search-class <class>'
|
||||||
, 'LDAP search objectClass (or $LDAP_SEARCH_CLASS)'
|
, 'LDAP search objectClass (or $LDAP_SEARCH_CLASS)'
|
||||||
, String
|
, String
|
||||||
, process.env.LDAP_SEARCH_CLASS || 'user')
|
, process.env.LDAP_SEARCH_CLASS || 'top')
|
||||||
|
.option('--ldap-search-field <name>'
|
||||||
|
, 'LDAP search field (or $LDAP_SEARCH_FIELD)'
|
||||||
|
, String
|
||||||
|
, process.env.LDAP_SEARCH_FIELD)
|
||||||
.action(function(options) {
|
.action(function(options) {
|
||||||
|
if (!options.secret) {
|
||||||
|
this.missingArgument('--secret')
|
||||||
|
}
|
||||||
|
if (!options.appUrl) {
|
||||||
|
this.missingArgument('--app-url')
|
||||||
|
}
|
||||||
|
|
||||||
require('./roles/auth/ldap')({
|
require('./roles/auth/ldap')({
|
||||||
port: options.port
|
port: options.port
|
||||||
, secret: options.secret
|
, secret: options.secret
|
||||||
, ssid: options.ssid
|
, ssid: options.ssid
|
||||||
|
, appUrl: options.appUrl
|
||||||
, ldap: {
|
, ldap: {
|
||||||
url: options.ldapUrl
|
url: options.ldapUrl
|
||||||
, timeout: options.ldapTimeout
|
, timeout: options.ldapTimeout
|
||||||
|
@ -238,7 +257,7 @@ program
|
||||||
dn: options.ldapSearchDn
|
dn: options.ldapSearchDn
|
||||||
, scope: options.ldapSearchScope
|
, scope: options.ldapSearchScope
|
||||||
, objectClass: options.ldapSearchClass
|
, objectClass: options.ldapSearchClass
|
||||||
, loginField: options.ldapSearchLoginField
|
, field: options.ldapSearchField
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -7,12 +7,18 @@ var logger = require('../../util/logger')
|
||||||
var requtil = require('../../util/requtil')
|
var requtil = require('../../util/requtil')
|
||||||
var ldaputil = require('../../util/ldaputil')
|
var ldaputil = require('../../util/ldaputil')
|
||||||
var jwtutil = require('../../util/jwtutil')
|
var jwtutil = require('../../util/jwtutil')
|
||||||
|
var pathutil = require('../../util/pathutil')
|
||||||
var urlutil = require('../../util/urlutil')
|
var urlutil = require('../../util/urlutil')
|
||||||
|
|
||||||
module.exports = function(options) {
|
module.exports = function(options) {
|
||||||
var log = logger.createLogger('auth-ldap')
|
var log = logger.createLogger('auth-ldap')
|
||||||
, app = express()
|
, 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.cookieParser())
|
||||||
app.use(express.cookieSession({
|
app.use(express.cookieSession({
|
||||||
secret: options.secret
|
secret: options.secret
|
||||||
|
@ -20,13 +26,34 @@ module.exports = function(options) {
|
||||||
}))
|
}))
|
||||||
app.use(express.json())
|
app.use(express.json())
|
||||||
app.use(express.urlencoded())
|
app.use(express.urlencoded())
|
||||||
|
app.use(express.csrf())
|
||||||
app.use(validator())
|
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) {
|
app.use(function(req, res, next) {
|
||||||
res.locals.csrf = req.csrfToken()
|
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')
|
var log = logger.createLogger('auth-ldap')
|
||||||
log.setLocalIdentifier(req.ip)
|
log.setLocalIdentifier(req.ip)
|
||||||
switch (req.accepts(['json'])) {
|
switch (req.accepts(['json'])) {
|
||||||
|
|
|
@ -25,14 +25,19 @@ module.exports.login = function(options, username, password) {
|
||||||
, maxConnections: 1
|
, maxConnections: 1
|
||||||
})
|
})
|
||||||
|
|
||||||
client.bind(options.bind.dn, options.bind.credentials, function(err) {
|
if (options.bind.dn) {
|
||||||
if (err) {
|
client.bind(options.bind.dn, options.bind.credentials, function(err) {
|
||||||
resolver.reject(err)
|
if (err) {
|
||||||
}
|
resolver.reject(err)
|
||||||
else {
|
}
|
||||||
resolver.resolve(client)
|
else {
|
||||||
}
|
resolver.resolve(client)
|
||||||
})
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
resolver.resolve(client)
|
||||||
|
}
|
||||||
|
|
||||||
return resolver.promise
|
return resolver.promise
|
||||||
}
|
}
|
||||||
|
@ -48,7 +53,7 @@ module.exports.login = function(options, username, password) {
|
||||||
, value: options.search.objectClass
|
, value: options.search.objectClass
|
||||||
})
|
})
|
||||||
, new ldap.EqualityFilter({
|
, new ldap.EqualityFilter({
|
||||||
attribute: options.search.loginField
|
attribute: options.search.field
|
||||||
, value: username
|
, value: username
|
||||||
})
|
})
|
||||||
]
|
]
|
||||||
|
|
BIN
res/auth-ldap/images/logo-128.png
Normal file
BIN
res/auth-ldap/images/logo-128.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 8.3 KiB |
11
res/auth-ldap/scripts/app.js
Normal file
11
res/auth-ldap/scripts/app.js
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
define([
|
||||||
|
'angular'
|
||||||
|
, './controllers/index'
|
||||||
|
]
|
||||||
|
, function(ng) {
|
||||||
|
return ng.module('app', [
|
||||||
|
'ngRoute'
|
||||||
|
, 'app.controllers'
|
||||||
|
])
|
||||||
|
}
|
||||||
|
)
|
11
res/auth-ldap/scripts/bootstrap.js
vendored
Normal file
11
res/auth-ldap/scripts/bootstrap.js
vendored
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
define([
|
||||||
|
'require'
|
||||||
|
, 'angular'
|
||||||
|
, 'angular-route'
|
||||||
|
, 'app'
|
||||||
|
, 'routes'
|
||||||
|
]
|
||||||
|
, function(require, ng) {
|
||||||
|
ng.bootstrap(document, ['app'])
|
||||||
|
}
|
||||||
|
)
|
37
res/auth-ldap/scripts/controllers/SignInCtrl.js
Normal file
37
res/auth-ldap/scripts/controllers/SignInCtrl.js
Normal file
|
@ -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
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}])
|
||||||
|
})
|
6
res/auth-ldap/scripts/controllers/index.js
Normal file
6
res/auth-ldap/scripts/controllers/index.js
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
define([
|
||||||
|
'./SignInCtrl'
|
||||||
|
]
|
||||||
|
, function() {
|
||||||
|
}
|
||||||
|
)
|
3
res/auth-ldap/scripts/controllers/module.js
Normal file
3
res/auth-ldap/scripts/controllers/module.js
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
define(['angular'], function(ng) {
|
||||||
|
return ng.module('app.controllers', [])
|
||||||
|
})
|
19
res/auth-ldap/scripts/main.js
Normal file
19
res/auth-ldap/scripts/main.js
Normal file
|
@ -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'
|
||||||
|
]
|
||||||
|
})
|
17
res/auth-ldap/scripts/routes.js
Normal file
17
res/auth-ldap/scripts/routes.js
Normal file
|
@ -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: '/'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
])
|
||||||
|
})
|
79
res/auth-ldap/styles/login.css
Normal file
79
res/auth-ldap/styles/login.css
Normal file
|
@ -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;
|
||||||
|
}
|
9
res/auth-ldap/views/index.jade
Normal file
9
res/auth-ldap/views/index.jade
Normal file
|
@ -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')
|
28
res/auth-ldap/views/partials/signin.jade
Normal file
28
res/auth-ldap/views/partials/signin.jade
Normal file
|
@ -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')
|
6
res/auth-ldap/views/partials/styles.jade
Normal file
6
res/auth-ldap/views/partials/styles.jade
Normal file
|
@ -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')
|
Loading…
Add table
Add a link
Reference in a new issue