draco-auth
v1.0.11
Published
A middleware package to handle authentication and security via a Draco server
Downloads
194
Readme
draco-auth
draco-auth is an Express middleware that seamlessly integrates your application with the Draco authentication and security system. It handles session management, user information injection, permission checks, and provides utility functions for authentication flows.
Features
- Draco Server Integration: Connects to a Draco server via REST API using an API key.
- Session Management: Manages user sessions and cookie handling (
req.draco). - User Injection: Injects user information into
req.draco.userandres.locals.user. - Authentication Utilities: Provides
loginURLandlogoutURLfunctions onreqandres.locals. - Middleware for Access Control:
NeedsAuthentication: Ensures routes are accessed by authenticated users.NeedsPermission: Ensures routes are accessed by users with specific permissions.NeedsOnePermissionOf: Ensures routes are accessed by users with one of the provided permissions.
- Event Logging: Allows logging of custom events to the Draco server via
req.logEvent. - Permission Checking: Facilitates checking user permissions with
req.hasPermissionorreq.hasOnePermissionOf.
Installation
Install the package using NPM:
> npm install --save draco-auth
Usage
Prerequisites
Before using draco-auth, ensure the following are set up on the main Draco server:
- Users: Create users on the Draco server.
- Applets: Create applets on the Draco server and generate an API key for each applet.
- Permissions: Assign permissions for the applet to the necessary users on the Draco server.
Configuration
draco-auth requires the Draco server's host and API key to function. These must be provided via environment variables.
The simplest way to set this up is by using a .env file.
Set Up Environment Variables
Create a
.envfile in your project root (if you haven't already) and add your Draco host and API key:DRACO_HOST=https://DRACO_URL_HERE DRACO_API_KEY=your_draco_api_key_hereEnsure you load environment variables in your application (commonly done using the
dotenvpackage):require('dotenv').config();
Integrating draco-auth with Express
Using TypeScript (Recommended)
Define Permissions
Create a
permissions.tsfile (or any suitable file) and define your permissions using an enum:export enum Permissions { PERMISSION_1 = 'permission_1', PERMISSION_2 = 'permission_2', }Set Up Express with
draco-authIn your main Express application file (e.g.,
index.ts):import express from 'express'; import dracoAuth, { NeedsAuthentication, NeedsPermission, NeedsOnePermissionOf } > from 'draco-auth'; import { Permissions } from './permissions'; // Adjust the import path as necessary import dotenv from 'dotenv'; dotenv.config(); // Load environment variables const app = express(); // Express setup app.set('view engine', 'pug'); // Example setup app.use(express.static('public')); // Integrate draco-auth middleware app.use(dracoAuth({ permissions: Object.values(Permissions) })); // Define routes app.get('/', (req, res) => { res.send('This is an unprotected route'); }) app.get('/protected', NeedsAuthentication, (req, res) => { res.send('This is a protected route.'); }); app.get('/privileged', NeedsOnePermissionOf([Permissions.Manager, Permissions.> Admin]), (req, res) => { res.send('Privileged page.'); }); app.get('/admin', NeedsPermission(Permissions.Admin), (req, res) => { res.send('Admin dashboard.'); }); app.listen(3000, () => { console.log('⚡ Server is running on port 3000'); });
Using JavaScript
Set Up Express with
draco-authIn your main Express application file (e.g.,
index.js):const express = require('express'); const dracoAuth = require('draco-auth'); const dotenv = require('dotenv'); dotenv.config(); // Load environment variables const app = express(); // Express setup app.set('view engine', 'pug'); // Example setup app.use(express.static('public')); // Integrate draco-auth middleware app.use(dracoAuth.default({ permissions: [ 'admin', 'manager', 'permission_1', 'permission_2', // Add more permissions as needed ] })); // Define routes app.get('/', (req, res) => { res.send('This is an unprotected route.'); }); app.get('/protected', dracoAuth.NeedsAuthentication, (req, res) => { res.send('This is a protected route.'); }); app.get('/privileged', dracoAuth.NeedsOnePermissionOf(['manager', 'admin']), (req, > res) => { res.send('Privileged page.'); }); app.get('/admin', dracoAuth.NeedsPermission('admin'), (req, res) => { res.send('Admin dashboard.'); }); app.listen(3000, () => { console.log('Server is running on port 3000'); });
API Reference
Middleware Functions
dracoAuth(options)
Initializes the
draco-authmiddleware.Options:
permissions: string[]
An array of permission strings that can be assigned to users.
NeedsAuthentication
Middleware to ensure that a user is authenticated. If not, redirects them to the Draco login screen.Usage:
app.get('/secure', NeedsAuthentication, (req, res) => { res.send('Secure Content'); });NeedsPermission(permission: string, redirect?: string)
Middleware to ensure that a user is authenticated and possesses a specific permission. If the user lacks the required permission, they are redirected to the specified URL (defaults to/).Parameters:
permission: string
The permission required to access the route.redirect?: string
Optional. The URL to redirect unauthorized users to.
Usage:
app.get('/admin', NeedsPermission('admin'), (req, res) => { res.send('Admin Content'); });NeedsOnePermissionOf(permissions: string[], redirect?: string)
Middleware to ensure that a user is authenticated and possesses at least one permission from the provided array. If the user lacks all the required permission, they are redirected to the specified URL (defaults to/).Parameters:
permissions: string[]The permissions which are allowed to access the route.redirect?: string
Optional. The URL to redirect unauthorized users to.
Usage:
app.get('/admin', NeedsOnePermissionOf(['admin', 'manager']), (req, res) => { res.send('Privileged Content'); });
Request Object Extensions
The middleware augments the Express req and res objects with additional properties and functions:
req.draco
An object representing the user's session.req.draco.user
The authenticated user's information, adhering to the following interface:interface User { name: string; surname: string; email: string; image_path?: string; permissions: string[]; }res.locals.user
Same asreq.draco.user, available for use in front-end templates.req.loginURL(redirect_url: string): string
Generates a URL that redirects the user to the Draco login screen. After successful login, the user is redirected toredirect_url.Usage:
const loginLink = req.loginURL('/dashboard');Available in
res.locals.loginURLfor use in templates.req.logoutURL(redirect_url: string): string
Generates a URL that logs the user out by destroying their session and clears local permissions. After logout, the user is redirected toredirect_url.Usage:
const logoutLink = req.logoutURL('/goodbye');Available in
res.locals.logoutURLfor use in templates.req.logEvent(event: string, additional_information?: string): void
Sends an event to the Draco server for logging. Requires the user to be authenticated.Parameters:
event: string
The name of the event (e.g.,'item_deleted').additional_information?: string
Optional. Additional details about the event.
Usage:
req.logEvent('item_deleted', 'Deleted item with ID 123');req.hasPermission(permission: string): boolean
Checks if the authenticated user has a specific permission.Parameters:
permission: string
The permission to check.
Usage:
if (req.hasPermission('delete_entries')) { // Perform delete operation }Available in
res.locals.canfor use in templates. Usage:- if (can('delete_entries')) // Show delete buttonreq.hasOnePermissionOf(permissions: ...string[]): boolean
Checks whether the authenticated user has one of the specified permission.Parameters:
permissions: ...string[]
The permissions to check.
Usage:
if (req.hasOnePermissionOf('delete_entries', 'admin')) { // Perform delete operation }Available in
res.locals.canfor use in templates. Usage:- if (can('delete_entries')) // Show delete buttonres.locals.loginURL
Same asreq.loginURL, available for use in front-end templates.res.locals.logoutURL
Same asreq.logoutURL, available for use in front-end templates.res.locals.user
The authenticated user's information, available for use in front-end templates.
Additional Information
- Public Hosting: While
draco-authis intended for private use, it is hosted publicly on NPM for ease of deployment. Ensure that sensitive information, such as API keys, is securely managed. - Caching: User information and permissions are cached for 1 minute on the applet. Any changes made on the Draco server may take up to a minute to reflect in the application.
Example
Here's a concise example combining the above concepts in a TypeScript Express application:
import express from 'express'; import dracoAuth, { NeedsAuthentication, NeedsPermission } from 'draco-auth'; import { Permissions } from './permissions'; import dotenv from 'dotenv'; dotenv.config(); const app = express(); app.set('view engine', 'pug'); app.use(express.static('public')); app.use(dracoAuth({ permissions: Object.values(Permissions) })); app.get('/', NeedsAuthentication, (req, res) => { res.render('home'); }); app.get('/dashboard', NeedsAuthentication, (req, res) => { res.render('dashboard'); }); app.get('/admin', NeedsPermission('admin'), (req, res) => { res.render('admin'); }); app.listen(3000, () => { console.log('Server running on port 3000'); });
In your Pug templates, you can access the user, loginURL, and logoutURL via res.locals:
//- home.pug - if(user) h1 Welcome, #{ user.name }! a(href=logoutURL('/goodbye')) Logout - else a(href=loginURL('/dashboard')) Login
Development
Building
Before making changes to draco-auth, ensure the following steps have been done:
- Clone: Clone this repository.
- Build: Run
npm run buildto generate thedistfolder in the repo directory. - Test app: Set up a test application (or clone
draco-testif you have access to the company Bitbucket).
Troubleshooting
Problem. Strange Redirects after Login
After being redirected to Draco to sign in to an applet, if you are sent to an unfamiliar URL instead of back to the applet, it is likely an issue with the request headers.
If you are using nginx or another reverse-proxy, ensure you are setting the following header after the proxy pass:
proxy_set_header X-Forwarded-For $remote_addr;
Made with ❤️ using TypeScript and coffee.
