Skip to content

Commit

Permalink
Mostly working auth server!
Browse files Browse the repository at this point in the history
  • Loading branch information
BytewaveMLP committed May 25, 2020
1 parent d17a4ac commit 08fec14
Show file tree
Hide file tree
Showing 5 changed files with 89 additions and 39 deletions.
3 changes: 3 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ dist/
config.example.toml
config.toml
docker-compose.yml
docker-compose.dev.yml
LICENSE
README.md
yarn-error.log
drawpile-config.ini
testkey.pem
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@ node_modules/

yarn-error.log
/config.toml
testkey.pem
drawpile-config.ini
16 changes: 11 additions & 5 deletions config.example.toml
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
# Base64-encoded Ed25519 private key
signingKey = ""

[ldap]
url = "ldap://localhost:389"
bindDN = "cn=admin,dc=example,dc=com"
bindPW = "admin"
userDN = "ou=users,dc=example,dc=com"
userSearchFilter = "(uid=%u)"

# Uncomment if you only want members of a specific group to be able to
# authenticate
#groupDN = "ou=groups,dc=example,dc=com"
#groupName = "drawpile"
# DN which contains your LDAP groups
groupDN = "ou=drawpile,ou=groups,dc=example,dc=com"
# The name of the group the user must belong to in order to join sessions at all
#requiredGroup = "drawpile"
groupObjectClass = "groupOfNames"
memberAttribute = "member"
# LDAP group representing your moderators; will set mod flag if the user is in this group
# modGroup = "moderator"
14 changes: 14 additions & 0 deletions docker-compose.dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,20 @@ services:
networks:
- default
- ciderandsaddle
drawpile:
image: callaa/drawpile-srv:2.1
volumes:
- drawpile:/home/drawpile
- ./drawpile-config.ini:/home/drawpile/drawpile-config.ini:ro
command: --sessions /home/drawpile/sessions --config /home/drawpile/drawpile-config.ini --extauth http://127.0.0.1:8081
ports:
- 27750:27750
networks:
- default
- ciderandsaddle

volumes:
drawpile:

networks:
ciderandsaddle:
Expand Down
93 changes: 59 additions & 34 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,11 @@
import crypto from 'crypto';
import crypto, { sign } from 'crypto';
import express from 'express';
import * as ldapts from 'ldapts';
import winston from 'winston';

require('source-map-support').install();
require('toml-require').install();

const config = require('../config.toml') as ServerConfig;

const log = winston.createLogger({
level: process.env.NODE_ENV === 'production' ? 'info' : 'debug',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.printf(info => `[${info.timestamp}] ${info.level}: ${info.message}`),
),
transports: [
new winston.transports.Console(),
],
});

const DEFUALT_PORT = 8081;

interface ServerConfig {
port?: number;
signingKey: string;
Expand All @@ -30,9 +15,10 @@ interface ServerConfig {
bindPW: string;
userDN: string;
userSearchFilter: string;
groupDN?: string;
groupName?: string;
memberOfAttribute: string;
groupDN: string;
groupObjectClass: string;
memberAttribute: string;
modGroup?: string;
};
}

Expand All @@ -58,25 +44,47 @@ interface LDAPUser {
[key: string]: string | string[] | Buffer | Buffer[];
}

const config = require('../config.toml') as ServerConfig;
const DEFUALT_PORT = 8081;
const signingKey = crypto.createPrivateKey({
key: Buffer.from(config.signingKey, 'base64'),
format: 'der',
type: 'pkcs8',
});

const log = winston.createLogger({
level: process.env.NODE_ENV === 'production' ? 'info' : 'debug',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.printf(info => `[${info.timestamp}] ${info.level}: ${info.message}`),
),
transports: [
new winston.transports.Console(),
],
});


const ldapClient = new ldapts.Client(config.ldap);

const app = express();
app.use(express.json());

const btoa = (data: string) => Buffer.from(data, 'utf8').toString('base64');

function createDrawpileAuthToken(payload: DrawpileAuthPayload, avatar?: string | Buffer) {
const payloadJSON = JSON.stringify(payload);
const signingKey = `-----BEGIN PRIVATE KEY-----
${config.signingKey}
-----END PRIVATE KEY-----`;
const signature = crypto.sign(null, Buffer.from(payloadJSON, 'utf8'), signingKey).toString('base64');

// version.payload.avatar?.signature
const components = [
avatar ? '2': '1',
Buffer.from(payloadJSON).toString('base64'),
signature
btoa(payloadJSON),
];
if (avatar) components.splice(2, 0, typeof avatar === 'string' ? avatar : avatar.toString('base64'));

const signature = crypto.sign(null, Buffer.from(components.join('.'), 'utf8'), signingKey).toString('base64');

if (avatar) components.push(typeof avatar === 'string' ? avatar : avatar.toString('base64'));
components.push(signature);

return components.join('.');
}

Expand Down Expand Up @@ -114,32 +122,49 @@ async function loginUser(username: string, password: string): Promise<LDAPUser |
return user;
}

app.get('/', async (req, res) => {
app.use((req, res, next) => {
log.info('Request received from ' + req.ip);
log.debug('Request method: ' + req.method);
log.debug('Request URI: ' + req.url);
log.debug('Request body: ' + JSON.stringify(req.body));

next();
});

app.post('/', async (req, res) => {
if (!req.body.username) return res.status(400).send('Bad request');

const authResponse: AuthResponse = {
status: 'auth',
ingroup: config.ldap.groupName,
ingroup: req.body.group,
};

if (!req.body.password) { // auth server request
if (!req.body.password) { // server request
log.info(`Checking if username ${req.body.username} is available for guest access`);

if (!config.allowGuests) {
log.info(`Guest login disabled`);
return res.json(authResponse);
}

const user = await findUser(req.body.username);
if (user) {
log.info(`Username ${req.body.username} is taken by a registered user`);
return res.json(authResponse); // auth required, username taken
}

log.info(`Username ${req.body.username} is available`);
authResponse.status = 'guest';
return res.json(authResponse);
}

// since password is set, this is a login request

// missing nonce, invalid request
if (!req.body.nonce) return res.status(400).send('Bad request');

log.info(`Attempting to authenticate user ${req.body.username}`);

let user: LDAPUser | null = null;

try {
Expand All @@ -163,14 +188,14 @@ app.get('/', async (req, res) => {

log.info(`Username ${req.body.username} successfully authenticated`);

const authPayload: DrawpileAuthPayload = {
authResponse.token = createDrawpileAuthToken({
username: req.body.username,
iat: Date.now(),
uid: user.entryUUID,
nonce: crypto.randomBytes(8).toString('hex'),
};

authResponse.token = createDrawpileAuthToken(authPayload);
group: req.body.group,
nonce: req.body.nonce,
});
log.debug('Auth response: ' + JSON.stringify(authResponse));
return res.json(authResponse);
});

Expand Down

0 comments on commit 08fec14

Please sign in to comment.