Skip to content
This repository has been archived by the owner on May 14, 2024. It is now read-only.

Support sasl authentication #826

Closed
wants to merge 4 commits into from
Closed

Support sasl authentication #826

wants to merge 4 commits into from

Conversation

FROGGS
Copy link

@FROGGS FROGGS commented Dec 26, 2022

I've added a working sasl (NTLM/GSSAPI) authentication. I've tested this successfully against ActiveDirectory. Also Wireshark shows that the produced messages are fine.

image

The client got a new method called saslBind, which takes one parameter which is the token that the browser submits in the Authorization: NTLM <token> header.

Important to note is, that one needs keepAlive, since we have to subsequent requests to the LDAP server, which need to be on the same connection. Otherwise the second request (type3) wont be recognized by the LDAP server.

I've also prepared a patch for typescripts type definititions to include the new saslBind-Method. I'll push that as soon this PR gets approved.

An authorization middleware for Express might look like this, sorry this is typescript. One would most likely mix this with code that hands out a JWT upon successfull authentication. Further requests by the client should then be authnticated using that JWT, als long as it remains valid.

What I do not yet know if one needs to have one ldapjs-Client per user connection. I imagine that there could be problems when two users try to authenticate at the same time. So, worst case scenario is that one needs to keep unique instances of ldapjs per authenticating user at hand. After authencation is finished these instances can be recycled. Though, I have a hard time simulating this.

import { NextFunction, Request, Response } from 'express';
import * as ldapjs from 'ldapjs';

const client = ldapjs.createClient({ url: 'ldap://192.168.0.1' });
client.on('connect', (result) => (
  console.debug('NTLM: connected, setting keepAlive to true'),
  result.setKeepAlive(true)
));

export async function ntlmAuth(req: Request, res: Response, next: NextFunction) {
  const match        = req.headers.authorization?.match(/^NTLM\s(.+)$/);
  const authBase64   = match ? match[1] : '';

  if (authBase64) {
    const token = Buffer.from(authBase64, 'base64');
    console.debug('NTLM: sending type%d bind', token.length === 40 ? '1' : '3');
    client.saslBind(token, (err, result) => {
      if (result?.saslChallange) {
        console.debug('NTLM: got type2 challange');
        res.status(401).setHeader('WWW-Authenticate', 'NTLM ' + (result.saslChallange as Buffer).toString('base64')).send('');
      }
      else if (result) {
        console.debug('NTLM: got type4 success');
        next();
      }
      else {
        console.error('NTLM: got type2 error', err);
      }
    });
  }
  else {
    console.debug('NTLM: Sending initial 401 to browser');
    res.status(401).setHeader('WWW-Authenticate', 'NTLM').send('');
  }
}

Additionally here is the most basic typescript Express app where it could be used:

import express from 'express';

const app: express.Application = express();
const cookieParser = require('cookie-parser');
app.use(cookieParser());

app.get('/', ntlmAuth, (req, res) => res.send("We are authenticated!"));
 
app.listen(3000, () => console.log("server started"));

@FROGGS FROGGS mentioned this pull request Dec 26, 2022
Copy link
Member

@jsumners jsumners left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wow. Thank you for taking the time to work on this old issue. However, can I convince you to wait until v3 is released? Most of this work is happening in lib/messages/ and I am currently quite deep into splitting that out to https://github.com/ldapjs/messages.

Comment on lines +309 to +321
Client.prototype.saslBind = function saslBind (token,
controls,
callback,
_bypass) {
if (typeof (token) !== 'string' && !Buffer.isBuffer(token)) {
throw new TypeError('token (string or Buffer) required')
}
if (typeof (controls) === 'function') {
callback = controls
controls = []
} else {
controls = validateControls(controls)
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd rather the function accept an input object so that positional parameter inspection is unnecessary.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you talking about an incopatible change also for client.bind()?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No. This is a completely new method. There is no reason to carry forward difficult to maintain patterns.

* @param {Function} callback of the form f(err, res).
* @throws {TypeError} on invalid input.
*/
Client.prototype.saslBind = function saslBind (token,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we able to add a corresponding test for this in `client.test.js?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure

@FROGGS
Copy link
Author

FROGGS commented Dec 26, 2022

When will v3 happen?

@jsumners
Copy link
Member

When will v3 happen?

Dates are not promised. I will say that I had hoped to have it out before the new year, but there's always something else popping up in this codebase that makes estimates like that difficult. As an example, it turns out that the message objects are tightly coupled to the actual connection. So once I have @ldapjs/messages complete, I have to go through the client and server code and make adjustments to remove that tight coupling.

@FROGGS
Copy link
Author

FROGGS commented Dec 26, 2022

When will v3 happen?

Dates are not promised. I will say that I had hoped to have it out before the new year, but there's always something else popping up in this codebase that makes estimates like that difficult. As an example, it turns out that the message objects are tightly coupled to the actual connection. So once I have @ldapjs/messages complete, I have to go through the client and server code and make adjustments to remove that tight coupling.

So I'd say I leave my PR as is and wait for the v3. Then I'll create new PRs to this and the messages repo.

Feel free to tag this PR accordingly.

@FROGGS
Copy link
Author

FROGGS commented Dec 30, 2022

Note: There are two issues I've noticed when bringing this to "production":

  1. It is slightly inconvenient to get the authenticated username, so this needs to be improved for v3.
  2. The code is actually a bit to restrictive about token lengths. I'll need to revise that part of the code.

@ethfeat
Copy link

ethfeat commented Jan 4, 2023

I am trying to setup a mock service for my work and I would also need the server side SASL support. Any chance to have it anytime soon?

@jsumners
Copy link
Member

jsumners commented Jan 4, 2023

I am trying to setup a mock service for my work and I would also need the server side SASL support. Any chance to have it anytime soon?

#826 (comment)

@FROGGS
Copy link
Author

FROGGS commented Jan 5, 2023

I am trying to setup a mock service for my work and I would also need the server side SASL support. Any chance to have it anytime soon?

#826 (comment)

Since I have currently no idea how the formula for this challenge-response is, I'm unable to implement it.

@jsumners
Copy link
Member

jsumners commented Jan 5, 2023

I'm hesitant to suggest this, because it technically means the org should be supporting it going forward, but I'm willing to compromise and merge it to the v2.x branch with the understanding that it will not be ported to v3 by myself (or @UziTech).

@jsumners
Copy link
Member

For what it's worth, #834 was the biggest blocker for v3. Now that it is out there, progress should be quick.

@jsumners
Copy link
Member

👋

On February 22, 2023, we released version 3 of this library. As a result, we are closing this issue/pull request.

Please see issue #839 for more information, including how to proceed if you feel this closure is in error.

@ldapjs ldapjs locked as resolved and limited conversation to collaborators Feb 22, 2023
@jsumners jsumners closed this Feb 22, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants