Compare commits

..

5 Commits

15 changed files with 4691 additions and 8 deletions

12
.gitignore vendored
View File

@ -1,8 +1,6 @@
# ---> Composer
composer.phar
/vendor/
# Commit your application's lock file https://getcomposer.org/doc/01-basic-usage.md#commit-your-composer-lock-file-to-version-control
# You may choose to ignore a library lock file http://getcomposer.org/doc/02-libraries.md#lock-file
# composer.lock
# Modules installed via NPM
node_modules/
# Stylesheets and Javascript files generated through Grunt.js
public/styles/
public/js/

65
Gruntfile.js Normal file
View File

@ -0,0 +1,65 @@
module.exports = function(grunt) {
var pkg = grunt.file.readJSON('package.json')
// Project configuration.
grunt.initConfig({
'dart-sass': {
dist: {
options: {
style: 'compressed'
},
files: [{
expand: true,
cwd: 'assets/sass',
src: ['*.sass'],
dest: 'public/styles',
ext: '.css'
}]
}
},
coffee: {
options: {
sourceMap: true,
style: 'compressed'
},
files: {
expand: true,
flatten: true,
cwd: 'assets/coffee',
src: ['*.coffee'],
dest: 'public/js',
ext: '.js'
}
},
watch: {
css: {
files: ['assets/sass/*.sass'],
tasks: ['dart-sass'],
options: {
atBegin: true,
spawn: false
}
},
js: {
files: ['assets/coffee/*.coffee'],
tasks: ['coffee'],
options: {
atBegin: true,
spawn: false
}
}
}
});
// Load task plugins
grunt.loadNpmTasks('grunt-dart-sass');
grunt.loadNpmTasks('grunt-contrib-coffee');
grunt.loadNpmTasks('grunt-contrib-watch');
// Default task(s).
grunt.registerTask('default', ['dart-sass', 'coffee']);
};

View File

@ -1,4 +1,4 @@
Copyright (c) <year> <owner>
Copyright (c) 2022 Metaunix
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

44
archon.js Normal file
View File

@ -0,0 +1,44 @@
const express = require('express');
const session = require('express-session');
const app = express();
const port = 3000;
// Enable POST data handling
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
// Start creating our session config
var sess = {
secret: 'dark archon', // will need to change this later
cookie: {}
}
if (app.get('env') === 'production') {
app.set('trust proxy', 1) // trust first proxy
sess.cookie.secure = true // serve secure cookies
}
// Enable the Express.js session handling
app.use(session(sess))
// Initialize the Twig template engine - this might get swapped for Twing later.
app.set('view engine', 'twig');
// Service static files from public/
app.use(express.static('public'));
// Load middleware
authMiddleware = require('./src/middleware/authMiddleware');
app.use('/', authMiddleware.authProtected);
// Load in route handlers
indexRoutes = require('./routes/index');
authRoutes = require('./routes/auth');
// Assign routes to handlers
app.get('/', indexRoutes.home);
app.get('/auth/login', authRoutes.getLogin);
app.post('/auth/login', authRoutes.postLogin);
app.listen(port, () => {
console.log(`Example app listening on port ${port}`)
});

View File

@ -0,0 +1,2 @@
window.onload = () ->
console.log('Test.')

53
assets/sass/archon.sass Normal file
View File

@ -0,0 +1,53 @@
body
padding-bottom: 115px
background: white
a
color: cornflowerblue
transition: all 200ms ease-in-out
&:hover
color: darken(cornflowerblue, 10%)
input
transition: all 200ms ease-in-out
input[type=submit],
button
background-color: cornflowerblue
color: #f0f0f0
transition: all 200ms ease-in-out
&:hover
background-color: darken(cornflowerblue, 10%)
color: white
.u-text-center
text-align: center
.container
max-width: 1024px
.container.fluid
max-width: 100%
#header h1
text-align: center
#footer
position: fixed
left: 0
right: 0
bottom: 0
padding-top: 25px
padding-bottom: 25px
border-top: 1px solid #999
.row
position: relative
p.no-margin
margin-bottom: 0
#ldapObjectTable table
width: 100%

4302
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

29
package.json Normal file
View File

@ -0,0 +1,29 @@
{
"name": "archon",
"version": "0.1.0",
"description": "A web-based LDAP user and group manager",
"main": "archon.js",
"scripts": {
"start": "node archon.js",
"test": "echo \"Error: no test specified\" && exit 1",
"grunt": "grunt"
},
"repository": {
"type": "git",
"url": "gitea@git.metaunix.net:metaunix/archon.git"
},
"author": "Gregory Ballantine <gballantine@metaunix.net>",
"license": "BSD-2-Clause",
"dependencies": {
"express": "^4.18.1",
"express-session": "^1.17.3",
"grunt-dart-sass": "^2.0.1",
"ldapjs": "^2.3.2",
"twig": "^1.15.4"
},
"devDependencies": {
"grunt": "^1.5.3",
"grunt-contrib-coffee": "^2.1.0",
"grunt-contrib-watch": "^1.1.0"
}
}

0
public/.gitkeep Normal file
View File

32
routes/auth.js Normal file
View File

@ -0,0 +1,32 @@
const ldap = require('ldapjs');
exports.getLogin = (req, res, next) => {
res.render('auth/login');
};
exports.postLogin = (req, res, next) => {
bindHost = req.body.ldap_bind_host;
bindDn = req.body.ldap_bind_dn;
bindPw = req.body.ldap_bind_pw;
client = ldap.createClient({url: 'ldap://' + bindHost + '/'});
client.bind(bindDn, bindPw, (err) => {
if (err) {
console.log('There was an error while logging in. Please try again.');
res.redirect('/auth/login');
return next(err);
} else {
console.log('Success!');
req.session.ldap_bind_host = bindHost;
req.session.ldap_bind_dn = bindDn;
req.session.ldap_bind_pw = bindPw;
baseDnBits = bindDn.split(',');
baseDnBits.shift();
baseDn = baseDnBits.join(',');
req.session.ldap_base_dn = baseDn;
return res.redirect('/');
}
});
};

31
routes/index.js Normal file
View File

@ -0,0 +1,31 @@
const ldap = require('ldapjs');
searchOpts = {
filter: '(objectClass=posixAccount)',
scope: 'sub',
attributes: ['uid', 'displayName', 'mail'],
};
exports.home = function(req, res, next) {
client = ldap.createClient({url: 'ldap://' + req.session.ldap_bind_host + '/'});
client.bind(req.session.ldap_bind_dn, req.session.ldap_bind_pw, (err) => {
if (err) {
console.log('There was an error while logging in. Please try again.');
res.redirect('/auth/login');
return next(err);
} else {
client.search('ou=People,' + req.session.ldap_base_dn, searchOpts, (err, result) => {
users = [];
result.on('searchEntry', (entry) => {
users.push(entry.object);
});
return res.render('index', {
users: users
});
});
}
});
};

View File

@ -0,0 +1,15 @@
exports.authProtected = (req, res, next) => {
// Extra check to make sure this isn't performed on the login page
doNotProtect = ['/auth/login'];
if (doNotProtect.includes(req.path)) {
return next();
}
if (!('ldap_bind_dn' in req.session)) {
return res.redirect('/auth/login');
} else {
return next();
}
};

38
views/auth/login.twig Normal file
View File

@ -0,0 +1,38 @@
{% extends 'layout.twig' %}
{% block content %}
<header id="header" class="row">
<div class="columns twelve">
<h1>Login</h1>
</div>
</header>
<div class="row">
<form id="loginForm" class="columns twelve" action="/auth/login" method="POST">
<div class="row">
<label class="columns twelve">
LDAP Host:
<input class="u-full-width" type="text" name="ldap_bind_host" placeholder="Enter LDAP host...">
</label>
</div>
<div class="row">
<label class="columns six">
Bind DN:
<input class="u-full-width" type="text" name="ldap_bind_dn" placeholder="Enter bind DN...">
</label>
<label class="columns six">
Bind Password:
<input class="u-full-width" type="password" name="ldap_bind_pw" placeholder="Enter bind password...">
</label>
</div>
<div class="row">
<div class="columns twelve u-text-center">
<input type="submit" name="bind_submit" value="Login">
</div>
</div>
</form>
</div>
{% endblock %}

42
views/index.twig Normal file
View File

@ -0,0 +1,42 @@
{% extends 'layout.twig' %}
{% block content %}
<header class="row">
<div class="columns twelve u-text-center">
<h1>Archon LDAP Manager</h1>
</div>
</header>
<section id="connectionInfo" class="row">
<div class="columns twelve u-text-center">
<p>Connected to: <span id="ldapHost"></span></p>
</div>
</section>
<section id="userActions" class="row">
<div class="columns twelve">
<p><a href="user/create.html">Create new user</a></p>
</div>
</section>
<section id="ldapObjectTable" class="row">
<table>
<thead>
<tr>
<th>User ID</th>
<th>Display Name</th>
<th>Email Address</th>
</tr>
</thead>
<tbody id="ldapUserList">
{% for user in users %}
<tr>
<td>{{ user.uid }}</td>
<td>{{ user.displayName }}</td>
<td>{{ user.mail }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</section>
{% endblock %}

32
views/layout.twig Normal file
View File

@ -0,0 +1,32 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/skeleton/2.0.4/skeleton.min.css">
<link rel="stylesheet" href="/styles/archon.css">
<title>Archon LDAP Manager</title>
<script src="/js/archon.js" charset="utf-8"></script>
{% block scripts %}{% endblock %}
</head>
<body>
<div class="container">
{% block content %}{% endblock %}
</div>
<footer id="footer">
<div class="container fluid">
<div class="row">
<div class="columns three"><p></p></div>
<div class="columns six">
<p class="no-margin">This app was built using:</p>
<p class="no-margin">
Node.js <span id="node-version"></span>,
Chromium <span id="chrome-version"></span>,
and Electron <span id="electron-version"></span>.
</p>
</div>
</div>
</div>
</footer>
</body>
</html>