Start basic LDAP server groundwork

This commit is contained in:
garrettmills
2020-04-16 19:59:48 -05:00
parent dbdaf775df
commit 226b90b7bf
36 changed files with 971 additions and 11 deletions

122
app/assets/auth/forms.css Normal file
View File

@@ -0,0 +1,122 @@
:root {
--input-padding-x: 1.5rem;
--input-padding-y: 0.75rem;
}
.login,
.image {
min-height: 100vh;
}
.bg-image {
background-image: url('https://source.unsplash.com/4812YdII6W0/2592x1728');
background-size: cover;
background-position: center;
}
.login-heading {
font-weight: 300;
}
.btn-login {
font-size: 0.9rem;
letter-spacing: 0.05rem;
padding: 0.75rem 1rem;
border-radius: 2rem;
}
.form-label-group {
position: relative;
margin-bottom: 1rem;
}
.form-label-group>input,
.form-label-group>label {
padding: var(--input-padding-y) var(--input-padding-x);
height: auto;
border-radius: 2rem;
}
.form-label-group>label {
position: absolute;
top: 0;
left: 0;
display: block;
width: 100%;
margin-bottom: 0;
/* Override default `<label>` margin */
line-height: 1.5;
color: #495057;
cursor: text;
/* Match the input under the label */
border: 1px solid transparent;
border-radius: .25rem;
transition: all .1s ease-in-out;
}
.form-label-group input::-webkit-input-placeholder {
color: transparent;
}
.form-label-group input:-ms-input-placeholder {
color: transparent;
}
.form-label-group input::-ms-input-placeholder {
color: transparent;
}
.form-label-group input::-moz-placeholder {
color: transparent;
}
.form-label-group input::placeholder {
color: transparent;
}
.form-label-group input:not(:placeholder-shown) {
padding-top: calc(var(--input-padding-y) + var(--input-padding-y) * (2 / 3));
padding-bottom: calc(var(--input-padding-y) / 3);
}
.form-label-group input:not(:placeholder-shown)~label {
padding-top: calc(var(--input-padding-y) / 3);
padding-bottom: calc(var(--input-padding-y) / 3);
font-size: 12px;
color: #777;
}
.form-error-message {
color: red;
font-weight: bold;
}
.form-submit-button {
margin-top: 50px;
margin-bottom: 50px;
}
/* Fallback for Edge
-------------------------------------------------- */
@supports (-ms-ime-align: auto) {
.form-label-group>label {
display: none;
}
.form-label-group input::-ms-input-placeholder {
color: #777;
}
}
/* Fallback for IE
-------------------------------------------------- */
@media all and (-ms-high-contrast: none),
(-ms-high-contrast: active) {
.form-label-group>label {
display: none;
}
.form-label-group input:-ms-input-placeholder {
color: #777;
}
}

View File

@@ -0,0 +1,12 @@
const FormController = require('flitter-auth/controllers/Forms')
/*
* Handles views and processing for auth registration/login/logout/etc.
* Most handlers are inherited from the default flitter-auth/controllers/Forms
* controller, however you can override them here as you need.
*/
class Forms extends FormController {
}
module.exports = exports = Forms

View File

@@ -0,0 +1,16 @@
const Controller = require('flitter-auth/controllers/KeyAction')
/*
* KeyAction Controller
* -------------------------------------------------------------
* Provides handler methods for flitter-auth's key actions.
* Key actions allow your application to dynamically generate
* one-time links that call methods on controllers and (optionally)
* can even automatically sign in a user for the request, then log
* them out. e.g. a password reset link could use a key action.
*/
class KeyAction extends Controller {
}
module.exports = exports = KeyAction

View File

@@ -0,0 +1,13 @@
const Oauth2Controller = require('flitter-auth/controllers/Oauth2')
/*
* Handles views, processing, and data retrieval for flitter-auth's
* built-in OAuth2 server, if it is enabled. Most handlers are inherited
* from flitter-auth/controllers/Oauth2, but you can override them here
* as you need.
*/
class Oauth2 extends Oauth2Controller {
}
module.exports = exports = Oauth2

View File

@@ -0,0 +1,7 @@
const { Injectable } = require('flitter-di')
class LDAPController extends Injectable {
}
module.exports = exports = LDAPController

View File

@@ -0,0 +1,10 @@
const { Injectable } = require('flitter-di')
const ImplementationError = require('libflitter/errors/ImplementationError')
class LDAPMiddleware extends Injectable {
async test(req, res, next) {
throw new ImplementationError()
}
}
module.exports = exports = LDAPMiddleware

View File

@@ -0,0 +1,27 @@
const example_routes = {
prefix: 'dc=base',
middleware: [
],
search: {
},
bind: {
},
add: {
},
del: {
},
}
module.exports = exports = example_routes

10
app/models/LDAPBase.js Normal file
View File

@@ -0,0 +1,10 @@
const { Model } = require('flitter-orm')
const ImplementationError = require('libflitter/errors/ImplementationError')
class LDAPBase extends Model {
toLDAP() {
throw new ImplementationError()
}
}
module.exports = exports = LDAPBase

View File

@@ -0,0 +1,25 @@
const Model = require('flitter-auth/model/KeyAction')
/*
* KeyAction Model
* -------------------------------------------------------------
* Represents a single available key action. Key actions
* are one-time use links that directly call a method on
* a controller. These actions:
*
* - Can pass along context
* - Have expiration dates
* - Are single-use only
* - Can automatically log in a user during the request lifecycle
*
* You can generate these actions using the request.security.keyaction()
* method.
*
* See: module:flitter-auth/SecurityContext~SecurityContext#keyaction
* See: module:flitter-auth/model/KeyAction~KeyAction
*/
class KeyAction extends Model {
}
module.exports = exports = KeyAction

View File

@@ -0,0 +1,18 @@
const AuthUser = require('flitter-auth/model/User')
/*
* Auth user model. This inherits fields and methods from the default
* flitter-auth/model/User model, however you can override methods and
* properties here as you need.
*/
class User extends AuthUser {
static get schema() {
return {...super.schema, ...{
// other schema fields here
}}
}
// Other members and methods here
}
module.exports = exports = User

View File

@@ -9,6 +9,7 @@
* routes file.
*/
const Middleware = [
"auth:Utility",
// 'MiddlewareName',

View File

@@ -0,0 +1,15 @@
/*
* GuestOnly Middleware
* -------------------------------------------------------------
* Allows the request to proceed unless there's an authenticated user
* in the session. If so, redirect to the auth flow destination if one
* exists. If not, redirect to the default login route.
*/
const Middleware = require('flitter-auth/middleware/GuestOnly')
class GuestOnly extends Middleware {
}
module.exports = GuestOnly

View File

@@ -0,0 +1,12 @@
const Middleware = require('flitter-auth/middleware/KeyAction')
/*
* KeyAction Middleware
* -------------------------------------------------------------
* Middleware for processing key actions.
*/
class KeyAction extends Middleware {
}
module.exports = exports = KeyAction

View File

@@ -0,0 +1,14 @@
/*
* Oauth2TokenOnly Middleware
* -------------------------------------------------------------
* Allows the request to proceed if a valid OAuth2 bearer token was
* provided. If not, return a JSON-encoded error message.
*/
const Middleware = require('flitter-auth/middleware/Oauth2TokenOnly')
class Oauth2TokenOnly extends Middleware {
}
module.exports = Oauth2TokenOnly

View File

@@ -0,0 +1,14 @@
/*
* ProviderRegistrationEnabled Middleware
* -------------------------------------------------------------
* Redirects the user to the login page if the registration page for
* a particular auth provider is not enabled.
*/
const Middleware = require('flitter-auth/middleware/ProviderRegistrationEnabled')
class ProviderRegistrationEnabled extends Middleware {
}
module.exports = ProviderRegistrationEnabled

View File

@@ -0,0 +1,15 @@
/*
* Auth ProviderRoute Middleware
* -------------------------------------------------------------
* Many auth routes specify the name of a particular auth provider to
* use. This middleware looks up the provider by that name and injects
* it into the request.
*/
const Middleware = require('flitter-auth/middleware/ProviderRoute')
class ProviderRoute extends Middleware {
}
module.exports = ProviderRoute

View File

@@ -0,0 +1,15 @@
/*
* UserOnly Middleware
* -------------------------------------------------------------
* Allows the request to proceed if there's an authenticated user
* in the session. Otherwise, redirects the user to the login page
* of the default provider.
*/
const Middleware = require('flitter-auth/middleware/UserOnly')
class UserOnly extends Middleware {
}
module.exports = UserOnly

View File

@@ -0,0 +1,15 @@
/*
* Auth Utility Middleware
* -------------------------------------------------------------
* This should be applied globally. Ensures basic things about the
* request are true. For example, it provides the auth session data
* and handles auth flow.
*/
const Middleware = require('flitter-auth/middleware/Utility')
class Utility extends Middleware {
}
module.exports = Utility

View File

@@ -0,0 +1,113 @@
/*
* Auth Form Routes
* -------------------------------------------------------------
* The routes here pertain to auth forms like register/login etc.
* The general structure is as follows:
*
* /auth/{provider name}/{action}
* Individual providers may be interacted with individually, therefore:
*
* /auth/flitter/register
*
* You can omit the provider name to use the default provider:
*
* /auth/register
*/
const index = {
prefix: '/auth',
middleware: [
],
get: {
'/:provider/register': [
'middleware::auth:ProviderRoute',
'middleware::auth:GuestOnly',
'middleware::auth:ProviderRegistrationEnabled',
'controller::auth:Forms.registration_provider_get',
],
'/register': [
'middleware::auth:ProviderRoute',
'middleware::auth:GuestOnly',
'middleware::auth:ProviderRegistrationEnabled',
'controller::auth:Forms.registration_provider_get',
],
'/:provider/login': [
'middleware::auth:ProviderRoute',
'middleware::auth:GuestOnly',
'controller::auth:Forms.login_provider_get',
],
'/login': [
'middleware::auth:ProviderRoute',
'middleware::auth:GuestOnly',
'controller::auth:Forms.login_provider_get',
],
'/:provider/logout': [
'middleware::auth:ProviderRoute',
'middleware::auth:UserOnly',
'controller::auth:Forms.logout_provider_clean_session',
// Note, this separation is between when the auth action has happened properly
// and before the user is allowed to continue. You can use it to add your own
// custom middleware for auth flow handling.
'controller::auth:Forms.logout_provider_present_success',
],
'/logout': [
'middleware::auth:ProviderRoute',
'middleware::auth:UserOnly',
'controller::auth:Forms.logout_provider_clean_session',
'controller::auth:Forms.logout_provider_present_success',
],
},
post: {
'/:provider/register': [
'middleware::auth:ProviderRoute',
'middleware::auth:GuestOnly',
'middleware::auth:ProviderRegistrationEnabled',
'controller::auth:Forms.registration_provider_create_user',
'controller::auth:Forms.registration_provider_present_user_created',
],
'/register': [
'middleware::auth:ProviderRoute',
'middleware::auth:GuestOnly',
'middleware::auth:ProviderRegistrationEnabled',
'controller::auth:Forms.registration_provider_create_user',
'controller::auth:Forms.registration_provider_present_user_created',
],
'/:provider/login': [
'middleware::auth:ProviderRoute',
'middleware::auth:GuestOnly',
'controller::auth:Forms.login_provider_authenticate_user',
'controller::auth:Forms.login_provider_present_success',
],
'/login': [
'middleware::auth:ProviderRoute',
'middleware::auth:GuestOnly',
'controller::auth:Forms.login_provider_authenticate_user',
'controller::auth:Forms.login_provider_present_success',
],
'/:provider/logout': [
'middleware::auth:ProviderRoute',
'middleware::auth:UserOnly',
'controller::auth:Forms.logout_provider_clean_session',
'controller::auth:Forms.logout_provider_present_success',
],
'/logout': [
'middleware::auth:ProviderRoute',
'middleware::auth:UserOnly',
'controller::auth:Forms.logout_provider_clean_session',
'controller::auth:Forms.logout_provider_present_success',
],
},
}
module.exports = exports = index

View File

@@ -0,0 +1,16 @@
module.exports = exports = {
prefix: '/auth/action', // This is assumed by flitter-auth. Don't change it.
middleware: [],
get: {
'/:key': [
'middleware::auth:KeyAction',
'controller::auth:KeyAction.handle',
],
},
post: {
'/:key': [
'middleware::auth:KeyAction',
'controller::auth:KeyAction.handle',
],
},
}

View File

@@ -0,0 +1,46 @@
/*
* oauth2 Routes
* -------------------------------------------------------------
* Routes pertaining to the flitter-auth OAuth2 server implementation.
*/
const oauth2 = {
// Route prefix for all below routes
prefix: '/auth/service/oauth2/',
middleware: [
// Return 404 errors for these routes if the oauth2 server isn't enabled
['util:Config', {key: 'auth.servers.oauth2.enable'}],
],
get: {
// Show the authorization page
'/authorize': [
'middleware::auth:UserOnly',
'controller::auth:Oauth2.authorize_get',
],
// Built-in data endpoints
// Get the user info using a bearer token
'/data/user': [
['util:Config', {key: 'auth.servers.oauth2.build_in_endpoints.user.enable'}],
'middleware::auth:Oauth2TokenOnly',
'controller::auth:Oauth2.data_user_get',
],
},
post: {
// Handle a successful authorization
'/authorize': [
'middleware::auth:UserOnly',
'controller::auth:Oauth2.authorize_post',
],
// Redeem an authorization code for an OAuth2 bearer token
'/redeem': [
'controller::auth:Oauth2.redeem_token',
],
},
}
module.exports = oauth2

21
app/unit/LDAPRegistry.js Normal file
View File

@@ -0,0 +1,21 @@
const Unit = require('libflitter/Unit')
class LDAPRegistry extends Unit {
static get name() {
return 'ldap_registry'
}
static get services() {
return [...super.services, 'output']
}
async go(app) {
}
async cleanup(app) {
}
}
module.exports = exports = LDAPRegistry

View File

@@ -0,0 +1,44 @@
const Unit = require('libflitter/Unit')
const LDAP = require('ldapjs')
class LDAPServerUnit extends Unit {
static get name() {
return 'ldap_server'
}
static get services() {
return [...super.services, 'configs', 'express', 'output']
}
async go(app) {
this.config = this.configs.get('ldap:server')
const server_config = {}
// If Flitter is configured to use an SSL certificate,
// use it to enable LDAPS in the server.
if ( this.express.use_ssl() ) {
this.output.info('[LDAP Server] Using configured SSL certificate to enable LDAPS.')
server_config.certificate = await this.express.ssl_certificate()
server_config.key = await this.express.ssl_key()
}
this.server = LDAP.createServer(server_config)
if ( this.config.max_connections ) {
this.server.maxConnections = this.config.max_connections
}
this.output.info(`[LDAP Server] Will listen on ${this.config.interface}:${this.config.port}`)
await new Promise((res, rej) => {
this.server.listen(this.config.port, this.config.interface, () => {
res()
})
})
}
async cleanup(app) {
this.server.close()
}
}
module.exports = exports = LDAPServerUnit

View File

@@ -0,0 +1,17 @@
html
head
title #{title} | #{_app.name}
meta(name='viewport' content='width=device-width initial-scale=1')
link(rel='stylesheet' href='https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css')
link(rel='stylesheet' href='/assets/auth/forms.css')
body
.container-fluid
.row.no-gutter
.d-none.d-md-flex.col-md-6.col-lg-8.bg-image
.col-md-6.col-lg-4
.login.d-flex.align-items-center.py-5
.container
.row
.col-md-9.col-lg-8.mx-auto
block content
script(src='https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css')

9
app/views/auth/form.pug Normal file
View File

@@ -0,0 +1,9 @@
extends ./auth_page
block content
h3.login-heading.mb-4 #{heading_text}
if errors
each error in errors
p.form-error-message #{error}
form(method='post' enctype='multipart/form-data')
block form

View File

@@ -0,0 +1,5 @@
extends ./auth_page
block content
h3.login-heading.mb-4 #{message}
a.btn.btn-lg.btn-primary.btn-block.btn-login.text-uppercase.font-weight-bold.mb-2.form-submit-button(href=button_link) #{button_text}

17
app/views/auth/login.pug Normal file
View File

@@ -0,0 +1,17 @@
extends ./form
block form
.form-label-group
input#inputUsername.form-control(type='text' name='username' value=(form_data ? form_data.username : '') required placeholder='Username' autofocus)
label(for='inputUsername') Username
.form-label-group
input#inputPassword.form-control(type='password' name='password' required placeholder='Password')
label(for='inputPassword') Password
button.btn.btn-lg.btn-primary.btn-block.btn-login.text-uppercase.font-weight-bold.mb-2.form-submit-button(type='submit') Login
if registration_enabled
.text-center
span.small Need an account?&nbsp;
a(href='./register') Register here.
.text-center
span.small(style="color: #999999;") Provider: #{provider_name}

View File

@@ -0,0 +1,12 @@
extends ./auth_page
block content
h3.login-heading.mb-4 Authorize #{client.name}?
h5.login-heading.mb-4 #{client.name} wants to access basic user information about your #{_app.name} account.
h5.login-heading.mb-4 After authorization, you may not be prompted again.
form(method='post' enctype='multipart/form-data')
input(type='hidden' name='redirect_uri' value=uri.toString())
input(type='hidden' name='client_id' value=client.clientID)
button.btn.btn-lg.btn-primary.btn-block.btn-login.text-uppercase.font-weight-bold.mb-2.form-submit-button(type='submit') Authorize #{client.name}
.text-center
span.small(style="color: #999999;") Will redirect to: #{uri.host}

View File

@@ -0,0 +1,16 @@
extends ./form
block form
.form-label-group
input#inputUsername.form-control(type='text' name='username' value=(form_data ? form_data.username : '') required placeholder='Username' autofocus)
label(for='inputUsername') Username
.form-label-group
input#inputPassword.form-control(type='password' name='password' required placeholder='Password')
label(for='inputPassword') Password
button.btn.btn-lg.btn-primary.btn-block.btn-login.text-uppercase.font-weight-bold.mb-2.form-submit-button(type='submit') Register
.text-center
span.small Already registered?&nbsp;
a(href='./login') Log-in here.
.text-center
span.small(style="color: #999999;") Provider: #{provider_name}