Workflow funktioniert nun wieder. Es gab Probleme nach Aenderungen.
Build and Publish Site / docker (push) Successful in 23s
Build and Publish Site / docker (push) Successful in 23s
ABER: Die Applikation funktioniert nur lokal. Die deployte Version geht noch nicht.
This commit is contained in:
+12
@@ -0,0 +1,12 @@
|
||||
If you know how to fix the issue, make a pull request instead.
|
||||
|
||||
- [ ] I have a question that is inappropriate for [StackOverflow](https://stackoverflow.com/). (Please ask any appropriate questions, such as how to use the library, there).
|
||||
- [ ] I believe this is an issue in this library and not in the underlying libraries [ldapjs](https://github.com/joyent/node-ldapjs/) or [ldapauth-fork](https://github.com/vesse/node-ldapauth-fork). (This library is a passport strategy and does not implement the LDAP communication)
|
||||
|
||||
**Note:** if the issue template is not used, the issue will be closed.
|
||||
|
||||
## Problem Description
|
||||
|
||||
|
||||
## Steps to Reproduce
|
||||
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
## Changes
|
||||
|
||||
* v3.0.0
|
||||
* Update ldapauth-fork to v5 which upgrade ldapjs to v2
|
||||
* v2.1.4
|
||||
* Allow any version of @types/node
|
||||
* v2.1.3
|
||||
* [#86](https://github.com/vesse/passport-ldapauth/pull/86) Allow configuring missing credentials response status.
|
||||
* v2.1.2
|
||||
* [#80](https://github.com/vesse/passport-ldapauth/pull/80) Run error handler only once since the a new LdapAuth instance is created for every authenticate request.
|
||||
* v2.1.1
|
||||
* Bump deps
|
||||
* v2.1.0
|
||||
* [#77](https://github.com/vesse/passport-ldapauth/pull/77) Add `noSuchObject` error message
|
||||
* v2.0.0
|
||||
* `ldapauth-fork` major version update now uses Bunyan logger
|
||||
* Added TypeScript type definitions
|
||||
* v1.0.0
|
||||
* `ldapauth-fork` is now an event emitter. Emitted errors will cause authentication error.
|
||||
* [#38](https://github.com/vesse/passport-ldapauth/pull/38) Added option to handle erros as failures with `handleErrorsAsFailures`. Additionally a *synchronous* `failureErrorCallback` function that receives the error as argument can be provided.
|
||||
* v0.6.0
|
||||
* Added option `credentialsLookup` that can be used eg. to add Basic Auth header parsing support.
|
||||
* v0.5.0
|
||||
* Updated deps. ldapauth-fork update changes bind credentials handling to work better with falsy values needed in anonymous bind.
|
||||
* v0.4.0
|
||||
* Updated ldapauth-fork which updates ldapjs to 1.0.0
|
||||
* v0.3.1
|
||||
* [#35](https://github.com/vesse/passport-ldapauth/issues/35) - Show more specific error messages from Microsoft AD login errors if identified.
|
||||
* v0.3.0
|
||||
* [#10](https://github.com/vesse/passport-ldapauth/issues/10) - Add support for fetching groups. While this is really coming from [ldapauth-fork](https://github.com/vesse/node-ldapauth-fork), updated the minor version of this library as well to draw attention to new features.
|
||||
* v0.2.6
|
||||
* [#24](https://github.com/vesse/passport-ldapauth/pull/24) - Pass `req` to options function, enables request specific LDAP configuration.
|
||||
* v0.2.5
|
||||
* [#21](https://github.com/vesse/passport-ldapauth/issues/21) - Handle `constraintViolationError` as a login failure instead of an error.
|
||||
* v0.2.4
|
||||
* Inherit from [passport-strategy](https://github.com/jaredhanson/passport-strategy) like `passport-local` and others do.
|
||||
* v0.2.3
|
||||
* Documentation using the same keys as ldapjs (bindDn and bindCredentials)
|
||||
* v0.2.2
|
||||
* Allow configuring flash messages when calling `passport.authenticate()`
|
||||
* Return HTTP 400 when username or password is missing
|
||||
* v0.2.1
|
||||
* Passport as peerDependency, prevents version incompatibility
|
||||
* v0.2.0
|
||||
* [#8](https://github.com/vesse/passport-ldapauth/issues/8) - Possibility to provide a callback function instead of options object to constructor (contributed by Linagora)
|
||||
* Update Passport dependency to 0.2.0
|
||||
* Get rid of `var self = this;`
|
||||
* v0.1.2
|
||||
* [#6](https://github.com/vesse/passport-ldapauth/issues/6) - Handle NoSuchObjectError as login failure.
|
||||
* v0.1.1
|
||||
* Documentation changes due to renaming git repository of `ldapauth-fork`
|
||||
* v0.1.0
|
||||
* Use [ldapauth-fork](https://github.com/vesse/node-ldapauth-fork) instead of
|
||||
[ldapauth](https://github.com/trentm/node-ldapauth)
|
||||
* ldapjs upgraded to 0.6.3
|
||||
* New options including `tlsOptions`
|
||||
* Refactored tests
|
||||
* v0.0.6 (14 July 2013)
|
||||
* Fixes [#1](https://github.com/vesse/passport-ldapauth/issues/1)
|
||||
* Updated devDependencies
|
||||
* v0.0.5 (16 April 2013)
|
||||
* Create LDAP client on every request to prevent socket being closed due
|
||||
to inactivity.
|
||||
* v0.0.4 (14 April 2013)
|
||||
* Fixed passport-ldapauth version range.
|
||||
* v0.0.3 (14 April 2013)
|
||||
* Initial release.
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2013 Vesa Poikajärvi
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
+201
@@ -0,0 +1,201 @@
|
||||
# passport-ldapauth
|
||||
|
||||
[](https://travis-ci.org/vesse/passport-ldapauth)
|
||||
[](http://npmjs.com/package/passport-ldapauth)
|
||||
[](https://wakeone.co)
|
||||
|
||||
[Passport](http://passportjs.org/) authentication strategy against LDAP / AD server. This module is a Passport strategy wrapper for [ldapauth-fork](https://github.com/vesse/node-ldapauth-fork).
|
||||
|
||||
This module lets you authenticate using LDAP or AD in your Node.js applications. By plugging into Passport, LDAP authentication can be integrated into any framework that supports Connect-style middleware.
|
||||
|
||||
## Install
|
||||
|
||||
```
|
||||
npm install passport-ldapauth
|
||||
```
|
||||
|
||||
## Usage
|
||||
### Configure strategy
|
||||
|
||||
```javascript
|
||||
var LdapStrategy = require('passport-ldapauth');
|
||||
|
||||
passport.use(new LdapStrategy({
|
||||
server: {
|
||||
url: 'ldap://localhost:389',
|
||||
...
|
||||
}
|
||||
}));
|
||||
```
|
||||
|
||||
* `server`: LDAP settings. These are passed directly to [ldapauth-fork](https://github.com/vesse/node-ldapauth-fork). See its documentation for all available options.
|
||||
* `url`: e.g. `ldap://localhost:389`
|
||||
* `bindDN`: e.g. `cn='root'`
|
||||
* `bindCredentials`: Password for bindDN
|
||||
* `searchBase`: e.g. `o=users,o=example.com`
|
||||
* `searchFilter`: LDAP search filter, e.g. `(uid={{username}})`. Use literal `{{username}}` to have the given username used in the search.
|
||||
* `searchAttributes`: Optional array of attributes to fetch from LDAP server, e.g. `['displayName', 'mail']`. Defaults to `undefined`, i.e. fetch all attributes
|
||||
* `tlsOptions`: Optional object with options accepted by Node.js [tls](http://nodejs.org/api/tls.html#tls_tls_connect_options_callback) module.
|
||||
* `usernameField`: Field name where the username is found, defaults to _username_
|
||||
* `passwordField`: Field name where the password is found, defaults to _password_
|
||||
* `credentialsLookup`: Optional, synchronous function that provides the login credentials from `req`. See [below](#credentialslookup) for more.
|
||||
* `missingCredentialsStatus`: Returned HTTP status code when credentials could not be found in the request. Defaults to _400_
|
||||
* `handleErrorsAsFailures`: When `true`, unknown errors and ldapjs emitted errors are handled as authentication failures instead of errors (default: `false`).
|
||||
* `failureErrorCallback`: Optional, synchronous function that is called with the received error when `handleErrorsAsFailures` is enabled.
|
||||
* `passReqToCallback`: When `true`, `req` is the first argument to the verify callback (default: `false`):
|
||||
|
||||
passport.use(new LdapStrategy(..., function(req, user, done) {
|
||||
...
|
||||
done(null, user);
|
||||
}
|
||||
));
|
||||
|
||||
Note: you can pass a function instead of an object as `options`, see the [example below](#asynchronous-configuration-retrieval)
|
||||
|
||||
### Authenticate requests
|
||||
|
||||
Use `passport.authenticate()`, specifying the `'ldapauth'` strategy, to authenticate requests.
|
||||
|
||||
#### `authenticate()` options
|
||||
|
||||
In addition to [default authentication options](http://passportjs.org/guide/authenticate/) the following flash message options are available for `passport.authenticate()`:
|
||||
|
||||
* `badRequestMessage`: missing username/password (default: 'Missing credentials')
|
||||
* `invalidCredentials`: `InvalidCredentialsError` and `/no such user/i` LDAP errors (default: 'Invalid username/password')
|
||||
* `noSuchObject`: `NoSuchObjectError` LDAP errors (default: 'Bad search base')
|
||||
* `userNotFound`: LDAP returns no error but also no user (default: 'Invalid username/password')
|
||||
* `constraintViolation`: user account is locked (default: 'Exceeded password retry limit, account locked')
|
||||
|
||||
And for [Microsoft AD messages](http://www-01.ibm.com/support/docview.wss?uid=swg21290631), these flash message options can also be used (used instead of `invalidCredentials` if matching error code is found):
|
||||
|
||||
* `invalidLogonHours`: not being allowed to login at this current time (default: 'Not Permitted to login at this time')
|
||||
* `invalidWorkstation`: not being allowed to login from this current location (default: 'Not permited to logon at this workstation')
|
||||
* `passwordExpired`: expired password (default: 'Password expired')
|
||||
* `accountDisabled`: disabled account (default: 'Account disabled')
|
||||
* `accountExpired`: expired account (default: 'Account expired')
|
||||
* `passwordMustChange`: password change (default: 'User must reset password')
|
||||
* `accountLockedOut`: locked out account (default: 'User account locked')
|
||||
|
||||
## Express example
|
||||
|
||||
```javascript
|
||||
var express = require('express'),
|
||||
passport = require('passport'),
|
||||
bodyParser = require('body-parser'),
|
||||
LdapStrategy = require('passport-ldapauth');
|
||||
|
||||
var OPTS = {
|
||||
server: {
|
||||
url: 'ldap://localhost:389',
|
||||
bindDN: 'cn=root',
|
||||
bindCredentials: 'secret',
|
||||
searchBase: 'ou=passport-ldapauth',
|
||||
searchFilter: '(uid={{username}})'
|
||||
}
|
||||
};
|
||||
|
||||
var app = express();
|
||||
|
||||
passport.use(new LdapStrategy(OPTS));
|
||||
|
||||
app.use(bodyParser.json());
|
||||
app.use(bodyParser.urlencoded({extended: false}));
|
||||
app.use(passport.initialize());
|
||||
|
||||
app.post('/login', passport.authenticate('ldapauth', {session: false}), function(req, res) {
|
||||
res.send({status: 'ok'});
|
||||
});
|
||||
|
||||
app.listen(8080);
|
||||
```
|
||||
|
||||
### Active Directory over SSL example
|
||||
|
||||
Simple example config for connecting over `ldaps://` to a server requiring some internal CA certificate (often the case in corporations using Windows AD).
|
||||
|
||||
```javascript
|
||||
var fs = require('fs');
|
||||
|
||||
var opts = {
|
||||
server: {
|
||||
url: 'ldaps://ad.corporate.com:636',
|
||||
bindDN: 'cn=non-person,ou=system,dc=corp,dc=corporate,dc=com',
|
||||
bindCredentials: 'secret',
|
||||
searchBase: 'dc=corp,dc=corporate,dc=com',
|
||||
searchFilter: '(&(objectcategory=person)(objectclass=user)(|(samaccountname={{username}})(mail={{username}})))',
|
||||
searchAttributes: ['displayName', 'mail'],
|
||||
tlsOptions: {
|
||||
ca: [
|
||||
fs.readFileSync('/path/to/root_ca_cert.crt')
|
||||
]
|
||||
}
|
||||
}
|
||||
};
|
||||
...
|
||||
```
|
||||
|
||||
## `credentialsLookup`
|
||||
|
||||
A synchronous function that receives the `req` object and returns an objec with keys `username` and `password` (or `name` and `pass`) can be provided. Note, that when this is provided the default lookup is not performed. This can be used to eg. enable basic auth header support:
|
||||
|
||||
```javascript
|
||||
var basicAuth = require('basic-auth');
|
||||
var ldapOpts = {
|
||||
server: { ... },
|
||||
credentialsLookup: basicAuth
|
||||
}
|
||||
```
|
||||
|
||||
## Asynchronous configuration retrieval
|
||||
|
||||
Instead of providing a static configuration object, you can pass a function as `options` that will take care of fetching the configuration. It will be called with the `req` object and a callback function having the standard `(err, result)` signature. Notice that the provided function will be called on every authenticate request.
|
||||
|
||||
```javascript
|
||||
var getLDAPConfiguration = function(req, callback) {
|
||||
// Fetching things from database or whatever
|
||||
process.nextTick(function() {
|
||||
var opts = {
|
||||
server: {
|
||||
url: 'ldap://localhost:389',
|
||||
bindDN: 'cn=root',
|
||||
bindCredentials: 'secret',
|
||||
searchBase: 'ou=passport-ldapauth',
|
||||
searchFilter: '(uid={{username}})'
|
||||
}
|
||||
};
|
||||
|
||||
callback(null, opts);
|
||||
});
|
||||
};
|
||||
|
||||
var LdapStrategy = require('passport-ldapauth');
|
||||
|
||||
passport.use(new LdapStrategy(getLDAPConfiguration,
|
||||
function(user, done) {
|
||||
...
|
||||
return done(null, user);
|
||||
}
|
||||
));
|
||||
```
|
||||
|
||||
## `ldapsearch`
|
||||
|
||||
[ldapsearch](http://linux.die.net/man/1/ldapsearch) is a great command line tool for testing your config. The user search query performed in the Express example above when user logging in has uid `john` is the same as the following `ldapsearch` call:
|
||||
|
||||
```bash
|
||||
ldapsearch \
|
||||
-H ldap://localhost:389 \
|
||||
-x \
|
||||
-D cn=root \
|
||||
-w secret \
|
||||
-b ou=passport-ldapauth \
|
||||
"(uid=john)"
|
||||
```
|
||||
|
||||
If the query does not return expected user the configuration is likely incorrect.
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
|
||||
`passport-ldapauth` has been partially sponsored by [Wakeone Ltd](https://wakeone.co/).
|
||||
Generated
Vendored
+7
@@ -0,0 +1,7 @@
|
||||
var Strategy = require('./strategy');
|
||||
|
||||
// Export directly from package since there is nothing else to export
|
||||
exports = module.exports = Strategy;
|
||||
|
||||
// Export constructor like before for compatibility
|
||||
exports.Strategy = Strategy;
|
||||
Generated
Vendored
+144
@@ -0,0 +1,144 @@
|
||||
// Type definitions for passport-ldapauth 2.0
|
||||
// Project: https://github.com/vesse/passport-ldapauth
|
||||
// Definitions by: Vesa Poikajärvi <https://github.com/vesse>
|
||||
// TypeScript Version: 2.1
|
||||
|
||||
/// <reference types="node"/>
|
||||
|
||||
import { IncomingMessage } from 'http';
|
||||
import { Options as LdapAuthOptions } from 'ldapauth-fork';
|
||||
import {
|
||||
Strategy as PassportStrategy,
|
||||
AuthenticateOptions as PassportAuthenticateOptions
|
||||
} from 'passport';
|
||||
|
||||
declare namespace Strategy {
|
||||
/**
|
||||
* Return value type for credentialsLookup
|
||||
*/
|
||||
interface CredentialsLookupResult {
|
||||
username?: string;
|
||||
password?: string;
|
||||
user?: string;
|
||||
pass?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Credentials lookup function (eg. basic-auth)
|
||||
*/
|
||||
type CredentialsLookup = (req: IncomingMessage) => CredentialsLookupResult;
|
||||
|
||||
/**
|
||||
* Callback notified of an error when errors are handled as failures
|
||||
*/
|
||||
type FailureErrorCallback = (err: any) => void;
|
||||
|
||||
/**
|
||||
* passport-ldapauth options
|
||||
*/
|
||||
interface Options {
|
||||
/**
|
||||
* ldapauth-fork connection options
|
||||
*/
|
||||
server: LdapAuthOptions;
|
||||
/**
|
||||
* Form field name for username (default: username)
|
||||
*/
|
||||
usernameField?: string;
|
||||
/**
|
||||
* Form field name for password (default: password)
|
||||
*/
|
||||
passwordField?: string;
|
||||
/**
|
||||
* If set to true, request is passed to verify callback
|
||||
*/
|
||||
passReqToCallback?: boolean;
|
||||
/**
|
||||
* Credentials lookup function to be used instead of default search from request
|
||||
*/
|
||||
credentialsLookup?: CredentialsLookup;
|
||||
/**
|
||||
* Set to true to handle errors as login failures
|
||||
*/
|
||||
handleErrorsAsFailures?: boolean;
|
||||
/**
|
||||
* Synchronous failure error callback for handling the failure if using handleErrorsAsFailures
|
||||
*/
|
||||
failureErrorCallback?: FailureErrorCallback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback function returning the options if using OptionsFunction
|
||||
*/
|
||||
type OptionsFunctionCallback = (error: any, options: Options) => void;
|
||||
|
||||
/**
|
||||
* Callback for getting options dunamically for every authenticate call
|
||||
*/
|
||||
type OptionsFunction = (req: IncomingMessage, callback: OptionsFunctionCallback) => void;
|
||||
|
||||
/**
|
||||
* Flash message localizations for authenticate
|
||||
*/
|
||||
interface AuthenticateOptions extends PassportAuthenticateOptions {
|
||||
badRequestMessage?: string;
|
||||
invalidCredentials?: string;
|
||||
userNotFound?: string;
|
||||
constraintViolation?: string;
|
||||
invalidLogonHours?: string;
|
||||
invalidWorkstation?: string;
|
||||
passwordExpired?: string;
|
||||
accountDisabled?: string;
|
||||
accountExpired?: string;
|
||||
passwordMustChange?: string;
|
||||
accountLockedOut?: string;
|
||||
noSuchObject?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Options that verify done callback can pass
|
||||
*/
|
||||
interface VerifyOptions {
|
||||
message: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback executed in the verify function once done
|
||||
*
|
||||
* @param error Possible error, setting this will result in error from Passport
|
||||
* @param user The user object, or false if there was no error but the authentication should be failed
|
||||
* @param options
|
||||
*/
|
||||
type VerifyDoneCallback = (error: any, user?: any, options?: VerifyOptions) => void;
|
||||
|
||||
/**
|
||||
* Verify callback when passReqToCallback = false
|
||||
*/
|
||||
type VerifyCallback = (user: any, callback: VerifyDoneCallback) => void;
|
||||
|
||||
/**
|
||||
* Verify callback when passReqToCallback = true
|
||||
*/
|
||||
type VerifyCallbackWithRequest = (req: IncomingMessage, user: any, callback: VerifyDoneCallback) => void;
|
||||
}
|
||||
|
||||
declare class Strategy implements PassportStrategy {
|
||||
/**
|
||||
* @param options Strategy options or function returning the options
|
||||
* @param verify User provided verify callback for checking the LDAP result
|
||||
*/
|
||||
constructor(options: Strategy.Options | Strategy.OptionsFunction, verify?: Strategy.VerifyCallback | Strategy.VerifyCallbackWithRequest);
|
||||
|
||||
/**
|
||||
* Name of the strategy
|
||||
*/
|
||||
name: string;
|
||||
|
||||
/**
|
||||
* @param req
|
||||
* @param options
|
||||
*/
|
||||
authenticate(req: IncomingMessage, options?: Strategy.AuthenticateOptions): void;
|
||||
}
|
||||
|
||||
export = Strategy;
|
||||
Generated
Vendored
+364
@@ -0,0 +1,364 @@
|
||||
'use strict';
|
||||
var passport = require('passport-strategy');
|
||||
var LdapAuth = require('ldapauth-fork');
|
||||
var util = require('util');
|
||||
|
||||
/**
|
||||
* Passport wrapper for ldapauth
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Options callback callback (ie. the callback given if giving a callback
|
||||
* for options instead of an object)
|
||||
*
|
||||
* @callback optionsCallbackCallback
|
||||
* @param {(Error|undefined)} err - Possible error
|
||||
* @param {Object} options - Options object
|
||||
*/
|
||||
/**
|
||||
* Options callback
|
||||
*
|
||||
* @callback optionsCallback
|
||||
* @param {Object} req - HTTP request
|
||||
* @param {optionsCallbackCallback} callback - The callback returning the options
|
||||
*/
|
||||
/**
|
||||
* Verify done callback
|
||||
*
|
||||
* @callback verifyDoneCallback
|
||||
* @param {(Error|undefined)} err - Possible error
|
||||
* @param {(Object|boolean)} user - The verified user or false if not allowed
|
||||
* @param {Object} [info] info - Additional info message
|
||||
*/
|
||||
/**
|
||||
* Found LDAP user verify callback
|
||||
*
|
||||
* @callback verifyCallback
|
||||
* @param {Object} user - The user object from LDAP
|
||||
* @param {verifyDoneCallback} callback - The verify callback
|
||||
*/
|
||||
/**
|
||||
* Found LDAP user verify callback with request
|
||||
*
|
||||
* @callback verifyReqCallback
|
||||
* @param {Object} req - The HTTP request
|
||||
* @param {Object} user - The user object from LDAP
|
||||
* @param {verifyDoneCallback} callback - The verify callback
|
||||
*/
|
||||
/**
|
||||
* @typedef credentialsLookupResult
|
||||
* @type {object}
|
||||
* @property {string} username - Username to use
|
||||
* @property {string} password - Password to use
|
||||
*/
|
||||
/**
|
||||
* @typedef credentialsLookupResultAlt
|
||||
* @type {object}
|
||||
* @property {string} user - Username to use
|
||||
* @property {string} pass - Password to use
|
||||
*/
|
||||
/**
|
||||
* Credentials lookup function
|
||||
*
|
||||
* @callback credentialsLookup
|
||||
* @param {Object} req - The HTTP request
|
||||
* @return {(credentialsLookupResult|credentialsLookupResultAlt)} - Found credentials
|
||||
*/
|
||||
/**
|
||||
* Synchronous function for doing something with an error if handling
|
||||
* errors as failures
|
||||
*
|
||||
* @callback failureErrorCallback
|
||||
* @param {Error} err - The error occurred
|
||||
*/
|
||||
|
||||
/**
|
||||
* Add default values to options
|
||||
*
|
||||
* @private
|
||||
* @param {Object} options - Options object
|
||||
* @returns {Object} The given options with defaults filled
|
||||
*/
|
||||
var setDefaults = function(options) {
|
||||
options.usernameField || (options.usernameField = 'username');
|
||||
options.passwordField || (options.passwordField = 'password');
|
||||
return options;
|
||||
};
|
||||
|
||||
/**
|
||||
* Strategy constructor
|
||||
* <br>
|
||||
*
|
||||
* The LDAP authentication strategy authenticates requests based on the
|
||||
* credentials submitted through an HTML-based login form.
|
||||
* <br>
|
||||
*
|
||||
* Applications may supply a `verify` callback which accepts `user` object
|
||||
* and then calls the `done` callback supplying a `user`, which should be set
|
||||
* to `false` if user is not allowed to authenticate. If an exception occured,
|
||||
* `err` should be set.
|
||||
* <br>
|
||||
*
|
||||
* Options can be also given as function that accepts a callback end calls it
|
||||
* with error and options arguments. Notice that the callback is executed on
|
||||
* every authenticate call.
|
||||
* <br>
|
||||
*
|
||||
* Example:
|
||||
* <pre>
|
||||
* var LdapStrategy = require('passport-ldapauth').Strategy;
|
||||
* passport.use(new LdapStrategy({
|
||||
* server: {
|
||||
* url: 'ldap://localhost:389',
|
||||
* bindDN: 'cn=root',
|
||||
* bindCredentials: 'secret',
|
||||
* searchBase: 'ou=passport-ldapauth',
|
||||
* searchFilter: '(uid={{username}})',
|
||||
* reconnect: true
|
||||
* }
|
||||
* },
|
||||
* function(user, done) {
|
||||
* return cb(null, user);
|
||||
* }
|
||||
* ));
|
||||
* </pre>
|
||||
* @constructor
|
||||
* @param {(Object|optionsCallback)} options - Configuration options or options returning function
|
||||
* @param {Object} options.server - [ldapauth-fork options]{@link https://github.com/vesse/node-ldapauth-fork}
|
||||
* @param {string} [options.usernameField=username] - Form field name for username
|
||||
* @param {string} [options.passwordField=password] - Form field name for password
|
||||
* @param {Number} [options.missingCredentialsStatus=400] - HTTP status code returned when credentials are missing from the request
|
||||
* @param {boolean} [options.passReqToCallback=false] - If true, request is passed to verify callback
|
||||
* @param {credentialsLookup} [options.credentialsLookup] - Credentials lookup function to use instead of default
|
||||
* @param {boolean} [options.handleErrorAsFailures=false] - Set to true to handle errors as login failures
|
||||
* @param {failureErrorCallback} [options.failureErrorCallback] - Function receives the occurred error when errors handled as failures
|
||||
* @param {(verifyCallback|verifyReqCallback|undefined)} [verify] - User verify callback
|
||||
*/
|
||||
var Strategy = function(options, verify) {
|
||||
// We now accept function as options as well so we cannot assume anymore
|
||||
// that a call with a function parameter only would have skipped options
|
||||
// and just provided a verify callback
|
||||
if (!options) {
|
||||
throw new Error('LDAP authentication strategy requires options');
|
||||
}
|
||||
|
||||
this.options = null;
|
||||
this.getOptions = null;
|
||||
|
||||
if (typeof options === 'object') {
|
||||
this.options = setDefaults(options);
|
||||
} else if (typeof options === 'function') {
|
||||
this.getOptions = options;
|
||||
}
|
||||
|
||||
passport.Strategy.call(this);
|
||||
|
||||
this.name = 'ldapauth';
|
||||
this.verify = verify;
|
||||
};
|
||||
|
||||
util.inherits(Strategy, passport.Strategy);
|
||||
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* Get value for given field from given object. Taken from passport-local,
|
||||
* copyright 2011-2013 Jared Hanson
|
||||
*
|
||||
* @private
|
||||
* @param {Object} obj - The HTTP request object
|
||||
* @param {string} field - The field name to look for
|
||||
* @returns {string|null} - Found value for the field or null
|
||||
*/
|
||||
var lookup = function(obj, field) {
|
||||
var i, len, chain, prop;
|
||||
if (!obj) { return null; }
|
||||
chain = field.split(']').join('').split('[');
|
||||
for (i = 0, len = chain.length; i < len; i++) {
|
||||
prop = obj[chain[i]];
|
||||
if (typeof(prop) === 'undefined') { return null; }
|
||||
if (typeof(prop) !== 'object') { return prop; }
|
||||
obj = prop;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
/* eslint-enable */
|
||||
|
||||
/**
|
||||
* Verify the outcome of caller verify function - even if authentication (and
|
||||
* usually authorization) is taken care by LDAP there may be reasons why
|
||||
* a verify callback is provided, and again reasons why it may reject login
|
||||
* for a valid user.
|
||||
*
|
||||
* @private
|
||||
* @returns {undefined}
|
||||
*/
|
||||
var verify = function() {
|
||||
// Callback given to user given verify function.
|
||||
return function(err, user, info) {
|
||||
if (err) {
|
||||
return this.error(err);
|
||||
}
|
||||
if (!user) {
|
||||
return this.fail(info);
|
||||
}
|
||||
return this.success(user, info);
|
||||
}.bind(this);
|
||||
};
|
||||
|
||||
/**
|
||||
* Execute failureErrorCallback if provided
|
||||
*
|
||||
* @private
|
||||
* @param {Error} err - The error occurred
|
||||
* @returns {undefined}
|
||||
*/
|
||||
var handleErrorAsFailure = function(err) {
|
||||
if (typeof this.options.failureErrorCallback === 'function') {
|
||||
this.options.failureErrorCallback(err);
|
||||
}
|
||||
return this.fail(err, 500);
|
||||
};
|
||||
|
||||
/**
|
||||
* The actual authenticate implementation
|
||||
*
|
||||
* @private
|
||||
* @param {Object} req - The HTTP request
|
||||
* @param {Object} [options] - Flash messages
|
||||
* @returns {undefined}
|
||||
*/
|
||||
var handleAuthentication = function(req, options) {
|
||||
var username;
|
||||
var password;
|
||||
var ldap;
|
||||
var errorHandler;
|
||||
options || (options = {}); // eslint-disable-line no-param-reassign
|
||||
|
||||
if (typeof this.options.credentialsLookup === 'function') {
|
||||
var credentials = this.options.credentialsLookup(req);
|
||||
if (credentials != null) {
|
||||
// name and pass as a courtesy for those who use basic-auth directly as
|
||||
// they're likely the main user group.
|
||||
username = credentials.username || credentials.name;
|
||||
password = credentials.password || credentials.pass;
|
||||
}
|
||||
} else {
|
||||
username = lookup(req.body, this.options.usernameField) || lookup(req.query, this.options.usernameField);
|
||||
password = lookup(req.body, this.options.passwordField) || lookup(req.query, this.options.passwordField);
|
||||
}
|
||||
|
||||
if (!username || !password) {
|
||||
return this.fail({ message: options.badRequestMessage || 'Missing credentials' }, this.options.missingCredentialsStatus || 400);
|
||||
}
|
||||
|
||||
errorHandler = this.options.handleErrorsAsFailures === true ? handleErrorAsFailure.bind(this) : this.error.bind(this);
|
||||
|
||||
/**
|
||||
* AD possible messages
|
||||
* http://www-01.ibm.com/support/docview.wss?uid=swg21290631
|
||||
*/
|
||||
var messages = {
|
||||
'530': options.invalidLogonHours || 'Not Permitted to login at this time',
|
||||
'531': options.invalidWorkstation || 'Not permited to logon at this workstation',
|
||||
'532': options.passwordExpired || 'Password expired',
|
||||
'533': options.accountDisabled || 'Account disabled',
|
||||
'534': options.accountDisabled || 'Account disabled',
|
||||
'701': options.accountExpired || 'Account expired',
|
||||
'773': options.passwordMustChange || 'User must reset password',
|
||||
'775': options.accountLockedOut || 'User account locked',
|
||||
default: options.invalidCredentials || 'Invalid username/password'
|
||||
};
|
||||
|
||||
ldap = new LdapAuth(this.options.server);
|
||||
ldap.once('error', errorHandler);
|
||||
ldap.on('error', function() { /* Ignored */ });
|
||||
ldap.authenticate(username, password, function(err, user) {
|
||||
ldap.close(function(){
|
||||
// We don't care about the closing
|
||||
});
|
||||
|
||||
if (err) {
|
||||
// Invalid credentials / user not found are not errors but login failures
|
||||
if (err.name === 'InvalidCredentialsError' || err.name === 'NoSuchObjectError' || (typeof err === 'string' && err.match(/no such user/i))) {
|
||||
var message;
|
||||
if (err.name === 'NoSuchObjectError') {
|
||||
message = options.noSuchObject || 'Bad search base';
|
||||
} else {
|
||||
message = options.invalidCredentials || 'Invalid username/password';
|
||||
}
|
||||
|
||||
if (err.message) {
|
||||
var ldapComment = err.message.match(/data ([0-9a-fA-F]*), v[0-9a-fA-F]*/);
|
||||
if (ldapComment && ldapComment[1]){
|
||||
message = messages[ldapComment[1]] || messages['default'];
|
||||
}
|
||||
}
|
||||
return this.fail({ message: message }, 401);
|
||||
}
|
||||
if (err.name === 'ConstraintViolationError'){
|
||||
return this.fail({ message: options.constraintViolation || 'Exceeded password retry limit, account locked' }, 401);
|
||||
}
|
||||
|
||||
// Other errors are (most likely) real errors
|
||||
return errorHandler(err);
|
||||
}
|
||||
|
||||
if (!user) {
|
||||
return this.fail({ message: options.userNotFound || 'Invalid username/password' }, 401);
|
||||
}
|
||||
|
||||
// Execute given verify function
|
||||
if (this.verify) {
|
||||
if (this.options.passReqToCallback) {
|
||||
return this.verify(req, user, verify.call(this));
|
||||
} else {
|
||||
return this.verify(user, verify.call(this));
|
||||
}
|
||||
} else {
|
||||
return this.success(user);
|
||||
}
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
/**
|
||||
* Authenticate the request coming from a form or such.
|
||||
*
|
||||
* @param {Object} req - The HTTP request
|
||||
* @param {Object} [options] - Authentication options (flash messages). All messages have default values.
|
||||
* @param {string} [options.badRequestMessage] - Message for missing username/password
|
||||
* @param {string} [options.invalidCredentials] - Message for InvalidCredentialsError, NoSuchObjectError, and /no such user/ LDAP errors
|
||||
* @param {string} [options.userNotFound] - Message for user not found
|
||||
* @param {string} [options.constraintViolation] - Message when account is locked (or other constraint violation)
|
||||
* @param {string} [options.invalidLogonHours] - Message for Windows AD invalidLogonHours error
|
||||
* @param {string} [options.invalidWorkstation] - Message for Windows AD invalidWorkstation error
|
||||
* @param {string} [options.passwordExpired] - Message for Windows AD passwordExpired error
|
||||
* @param {string} [options.accountDisabled] - Message for Windows AD accountDisabled error
|
||||
* @param {string} [options.accountExpired] - Message for Windows AD accountExpired error
|
||||
* @param {string} [options.passwordMustChange] - Message for Windows AD passwordMustChange error
|
||||
* @param {string} [options.accountLockedOut] - Message for Windows AD accountLockedOut error
|
||||
* @returns {undefined}
|
||||
*/
|
||||
Strategy.prototype.authenticate = function(req, options) {
|
||||
if ((typeof this.options === 'object') && (!this.getOptions)) {
|
||||
return handleAuthentication.call(this, req, options);
|
||||
}
|
||||
|
||||
var callback = function(err, configuration) {
|
||||
if (err) {
|
||||
return this.fail(err);
|
||||
}
|
||||
|
||||
this.options = setDefaults(configuration);
|
||||
handleAuthentication.call(this, req, options);
|
||||
};
|
||||
|
||||
// Added functionality: getOptions can accept now up to 2 parameters
|
||||
if (this.getOptions.length === 1) { // Accepts 1 parameter, backwards compatibility
|
||||
this.getOptions(callback.bind(this));
|
||||
} else { // Accepts 2 parameters, pass request as well
|
||||
this.getOptions(req, callback.bind(this));
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = Strategy;
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
{
|
||||
"name": "passport-ldapauth",
|
||||
"description": "LDAP authentication strategy for Passport",
|
||||
"author": "Vesa Poikajärvi <vesa.poikajarvi@iki.fi>",
|
||||
"contributors": [
|
||||
"Simon Gaeremynck <gaeremyncks@gmail.com>",
|
||||
"Michael Bailly <mbailly@linagora.com>",
|
||||
"Jason Gelinas <jason.gelinas@citi.com>",
|
||||
"arumi <arumi@wge7033.secheron.net>",
|
||||
"Anthony Hernandez <anthony.hernandez@clownphobia.com>",
|
||||
"Chris Harwood <harwood@teralogics.com>",
|
||||
"David Dolcimascolo <david.dolcimascolo@stordata.fr>"
|
||||
],
|
||||
"keywords": [
|
||||
"ldap",
|
||||
"passport",
|
||||
"authentication",
|
||||
"ldapauth"
|
||||
],
|
||||
"version": "3.0.1",
|
||||
"license": "MIT",
|
||||
"main": "./lib/passport-ldapauth",
|
||||
"types": "./lib/passport-ldapauth/strategy.d.ts",
|
||||
"engines": {
|
||||
"node": ">=0.8.0"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/vesse/passport-ldapauth.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/vesse/passport-ldapauth/issues"
|
||||
},
|
||||
"dependencies": {
|
||||
"ldapauth-fork": "^5.0.1",
|
||||
"passport-strategy": "^1.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/basic-auth": "^1.1.3",
|
||||
"@types/bunyan": "^1.8.6",
|
||||
"@types/express": "^4.17.9",
|
||||
"@types/node": "^14.14.7",
|
||||
"@types/passport": "^1.0.4",
|
||||
"basic-auth": "^2.0.1",
|
||||
"body-parser": "^1.19.0",
|
||||
"bunyan": "^1.8.14",
|
||||
"chai": "^4.2.0",
|
||||
"eslint": "^7.13.0",
|
||||
"express": "^4.17.1",
|
||||
"ldapjs": "^2.2.1",
|
||||
"merge": "^2.1.0",
|
||||
"mocha": "^8.2.1",
|
||||
"passport": "^0.4.1",
|
||||
"supertest": "^6.0.1",
|
||||
"typescript": "^4.0.5",
|
||||
"watch": "^1.0.2"
|
||||
},
|
||||
"scripts": {
|
||||
"prepare": "npm run lint",
|
||||
"lint": "eslint ./lib",
|
||||
"lint:watch": "watch 'npm run lint' ./lib --wait 0.5",
|
||||
"test": "NODE_PATH=lib mocha --reporter spec test/*-test.js",
|
||||
"test:watch": "watch 'npm test' ./lib ./test --wait 0.5",
|
||||
"test:typescript": "cd test/typescript && tsc"
|
||||
}
|
||||
}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
var express = require('express'),
|
||||
passport = require('passport'),
|
||||
LdapStrategy = require('passport-ldapauth').Strategy,
|
||||
bodyParser = require('body-parser');
|
||||
|
||||
var server = null;
|
||||
|
||||
var init_passport = function(opts, testopts) {
|
||||
if (testopts.no_callback === true) {
|
||||
passport.use(new LdapStrategy(opts));
|
||||
} else {
|
||||
passport.use(new LdapStrategy(opts, function(user, cb) {
|
||||
return cb(null, user);
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
passport.serializeUser(function(user, cb) {
|
||||
cb(null, user.dn);
|
||||
});
|
||||
|
||||
passport.deserializeUser(function(dn, cb) {
|
||||
cb(null, {dn: dn});
|
||||
});
|
||||
|
||||
exports.start = function(opts, testopts, cb) {
|
||||
|
||||
var app = express();
|
||||
|
||||
init_passport(opts, testopts);
|
||||
|
||||
app.use(bodyParser.json());
|
||||
app.use(passport.initialize());
|
||||
|
||||
app.post('/login', passport.authenticate('ldapauth', {session: false}), function(req, res) {
|
||||
res.send({status: 'ok'});
|
||||
});
|
||||
|
||||
app.post('/custom-cb-login', function(req, res, next) {
|
||||
passport.authenticate('ldapauth', function(err, user, info) {
|
||||
if (err) return next(err);
|
||||
if (!user) return res.status(401).send(info);
|
||||
req.logIn(user, function(err) {
|
||||
if (err) return next(err);
|
||||
return res.json(user);
|
||||
})
|
||||
})(req, res, next);
|
||||
});
|
||||
|
||||
if (typeof cb === 'function') return cb(app);
|
||||
return;
|
||||
};
|
||||
|
||||
exports.close = function(cb) {
|
||||
if (server) server.close();
|
||||
server = null;
|
||||
if (typeof cb === 'function') return cb();
|
||||
return;
|
||||
};
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
var ldap = require('ldapjs');
|
||||
|
||||
authorize = function(req, res, next) {
|
||||
return next();
|
||||
};
|
||||
|
||||
var SUFFIX = 'ou=passport-ldapauth';
|
||||
var server = null;
|
||||
|
||||
db = {
|
||||
'valid': {
|
||||
dn: 'cn=valid,ou=passport-ldapauth',
|
||||
attributes: {
|
||||
uid: 'valid',
|
||||
name: 'Valid User'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
exports.start = function(port, cb) {
|
||||
if (server) {
|
||||
if (typeof cb === 'function') return cb();
|
||||
return;
|
||||
}
|
||||
|
||||
server = ldap.createServer();
|
||||
|
||||
server.bind('cn=root', function(req, res, next) {
|
||||
if (req.dn.toString() !== 'cn=root' || req.credentials !== 'secret') {
|
||||
return next(new ldap.InvalidCredentialsError());
|
||||
}
|
||||
res.end();
|
||||
return next();
|
||||
});
|
||||
|
||||
server.bind(SUFFIX, authorize, function(req, res, next) {
|
||||
var dn = req.dn.toString();
|
||||
if (dn !== 'cn=valid, ou=passport-ldapauth' || req.credentials !== 'valid') {
|
||||
return next(new ldap.InvalidCredentialsError());
|
||||
}
|
||||
res.end();
|
||||
return next();
|
||||
});
|
||||
|
||||
server.search(SUFFIX, authorize, function(req, res, next) {
|
||||
if (req.filter.attribute === 'uid' && req.filter.value === 'valid') {
|
||||
res.send(db['valid']);
|
||||
} else if (req.filter.attribute === 'uid' && req.filter.value === 'ms-ad') {
|
||||
return next(new ldap.InvalidCredentialsError("0090308: LdapErr: DSID-0C09030B, comment: AcceptSecurityContext error, data 533, v893 HEX: 0x533 - account disabled"));
|
||||
} else if (req.filter.attribute === 'member' && req.filter.value === db.valid.dn) {
|
||||
res.send({
|
||||
dn: 'cn=Group 1, ou=passport-ldapauth',
|
||||
attributes: {
|
||||
name: 'Group 1'
|
||||
}
|
||||
});
|
||||
res.send({
|
||||
dn: 'cn=Group 2, ou=passport-ldapauth',
|
||||
attributes: {
|
||||
name: 'Group 2'
|
||||
}
|
||||
});
|
||||
}
|
||||
res.end();
|
||||
return next();
|
||||
});
|
||||
|
||||
server.listen(port, function() {
|
||||
if (typeof cb === 'function') return cb();
|
||||
});
|
||||
};
|
||||
|
||||
exports.close = function(cb) {
|
||||
if (server) server.close();
|
||||
server = null;
|
||||
if (typeof cb === 'function') return cb();
|
||||
return;
|
||||
};
|
||||
|
||||
if (!module.parent) {
|
||||
exports.start(1389);
|
||||
}
|
||||
+428
@@ -0,0 +1,428 @@
|
||||
var should = require('chai').Should(),
|
||||
LdapStrategy = require('passport-ldapauth'),
|
||||
request = require('supertest'),
|
||||
basicAuth = require('basic-auth'),
|
||||
ldapserver = require('./ldapserver'),
|
||||
appserver = require('./appserver');
|
||||
|
||||
var LDAP_PORT = 1399;
|
||||
|
||||
var expressapp = null;
|
||||
|
||||
// Base options that are cloned where needed to edit
|
||||
var BASE_OPTS = {
|
||||
server: {
|
||||
url: 'ldap://localhost:' + LDAP_PORT.toString(),
|
||||
bindDn: 'cn=root',
|
||||
bindCredentials: 'secret',
|
||||
searchBase: 'ou=passport-ldapauth',
|
||||
searchFilter: '(uid={{username}})'
|
||||
}
|
||||
},
|
||||
BASE_TEST_OPTS = {
|
||||
no_callback: false
|
||||
};
|
||||
|
||||
var start_servers = function(opts, test_opts) {
|
||||
return function(cb) {
|
||||
ldapserver.start(LDAP_PORT, function() {
|
||||
appserver.start(opts, test_opts, function(app) {
|
||||
expressapp = app;
|
||||
cb();
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
var stop_servers = function(cb) {
|
||||
appserver.close(function() {
|
||||
ldapserver.close(function() {
|
||||
cb();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
describe('LDAP authentication strategy', function() {
|
||||
|
||||
describe('by itself', function() {
|
||||
|
||||
it('should export Strategy constructor directly', function(cb) {
|
||||
require('passport-ldapauth').should.be.a('function');
|
||||
cb();
|
||||
});
|
||||
|
||||
it('should export Strategy constructor separately as well', function(cb) {
|
||||
var strategy = require('passport-ldapauth').Strategy;
|
||||
strategy.should.be.a('function');
|
||||
(function() {
|
||||
new strategy(BASE_OPTS);
|
||||
}).should.not.throw(Error);
|
||||
cb();
|
||||
});
|
||||
|
||||
it('should be named ldapauth', function(cb) {
|
||||
var s = new LdapStrategy(BASE_OPTS);
|
||||
s.name.should.equal('ldapauth');
|
||||
cb();
|
||||
});
|
||||
|
||||
it('should throw an error if no arguments are provided', function(cb) {
|
||||
(function() {
|
||||
new LdapStrategy();
|
||||
}).should.throw(Error);
|
||||
cb();
|
||||
});
|
||||
|
||||
it('should throw an error if options are not accepted by ldapauth', function(cb) {
|
||||
var s = new LdapStrategy({}, function() {});
|
||||
(function() {
|
||||
s.authenticate({body: {username: 'valid', password: 'valid'}});
|
||||
}).should.throw(Error);
|
||||
cb();
|
||||
});
|
||||
|
||||
it('should initialize without a verify callback', function(cb) {
|
||||
(function() {
|
||||
new LdapStrategy({server: {}})
|
||||
}).should.not.throw(Error);
|
||||
cb();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('with basic settings', function() {
|
||||
|
||||
before(start_servers(BASE_OPTS, BASE_TEST_OPTS));
|
||||
|
||||
after(stop_servers);
|
||||
|
||||
it('should return unauthorized if credentials are not given', function(cb) {
|
||||
request(expressapp)
|
||||
.post('/login')
|
||||
.send({})
|
||||
.expect(400)
|
||||
.end(cb);
|
||||
});
|
||||
|
||||
it('should allow access with valid credentials', function(cb) {
|
||||
request(expressapp)
|
||||
.post('/login')
|
||||
.send({username: 'valid', password: 'valid'})
|
||||
.expect(200)
|
||||
.end(cb);
|
||||
});
|
||||
|
||||
it('should allow access with valid credentials in query string', function(cb) {
|
||||
request(expressapp)
|
||||
.post('/login?username=valid&password=valid')
|
||||
.expect(200)
|
||||
.end(cb);
|
||||
});
|
||||
|
||||
it('should return unauthorized with invalid credentials', function(cb) {
|
||||
request(expressapp)
|
||||
.post('/login')
|
||||
.send({username: 'valid', password: 'invalid'})
|
||||
.expect(401)
|
||||
.end(cb);
|
||||
});
|
||||
|
||||
it('should return unauthorized with non-existing user', function(cb) {
|
||||
request(expressapp)
|
||||
.post('/login')
|
||||
.send({username: 'nonexisting', password: 'invalid'})
|
||||
.expect(401)
|
||||
.end(cb);
|
||||
});
|
||||
|
||||
it('should return more specific flash message for AD reply', function(cb) {
|
||||
request(expressapp)
|
||||
.post('/custom-cb-login')
|
||||
.send({username: 'ms-ad', password: 'invalid'})
|
||||
.expect(401)
|
||||
.end(function(err, res) {
|
||||
should.not.exist(err);
|
||||
res.body.message.should.equal('Account disabled')
|
||||
cb(err, res);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('without a verify callback', function() {
|
||||
before(start_servers(BASE_OPTS, {no_callback: true}));
|
||||
|
||||
after(stop_servers);
|
||||
|
||||
it('should still authenticate', function(cb) {
|
||||
request(expressapp)
|
||||
.post('/login')
|
||||
.send({username: 'valid', password: 'valid'})
|
||||
.expect(200)
|
||||
.end(cb);
|
||||
});
|
||||
|
||||
it('should reject invalid event', function(cb) {
|
||||
request(expressapp)
|
||||
.post('/login')
|
||||
.send({username: 'valid', password: 'invalid'})
|
||||
.expect(401)
|
||||
.end(cb);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with optional options', function() {
|
||||
|
||||
afterEach(stop_servers);
|
||||
|
||||
it('should read given fields instead of defaults', function(cb) {
|
||||
var OPTS = JSON.parse(JSON.stringify(BASE_OPTS));
|
||||
OPTS.usernameField = 'ldapuname';
|
||||
OPTS.passwordField = 'ldappwd';
|
||||
|
||||
start_servers(OPTS, BASE_TEST_OPTS)(function() {
|
||||
request(expressapp)
|
||||
.post('/login')
|
||||
.send({ldapuname: 'valid', ldappwd: 'valid'})
|
||||
.expect(200)
|
||||
.end(cb);
|
||||
});
|
||||
});
|
||||
|
||||
it('should pass request to verify callback if defined so', function(cb) {
|
||||
var OPTS = JSON.parse(JSON.stringify(BASE_OPTS));
|
||||
OPTS.passReqToCallback = true;
|
||||
|
||||
start_servers(OPTS, BASE_TEST_OPTS)(function() {
|
||||
var req = {body: {username: 'valid', password: 'valid', testkey: 1}},
|
||||
s = new LdapStrategy(OPTS, function(req, user, done) {
|
||||
req.should.have.keys('body');
|
||||
req.body.should.have.keys(['username', 'password', 'testkey']);
|
||||
done(null, user);
|
||||
});
|
||||
|
||||
s.success = function(user) {
|
||||
should.exist(user);
|
||||
user.uid.should.equal('valid');
|
||||
cb();
|
||||
};
|
||||
|
||||
s.error = function() {}; // Just to have this when not run via passport
|
||||
|
||||
s.authenticate(req);
|
||||
});
|
||||
});
|
||||
|
||||
it('should allow access with valid credentials in the header', function(cb) {
|
||||
var OPTS = JSON.parse(JSON.stringify(BASE_OPTS));
|
||||
OPTS.credentialsLookup = basicAuth;
|
||||
|
||||
start_servers(OPTS, BASE_TEST_OPTS)(function() {
|
||||
request(expressapp)
|
||||
.post('/login')
|
||||
.set('Authorization', 'Basic dmFsaWQ6dmFsaWQ=')
|
||||
.expect(200)
|
||||
.end(cb);
|
||||
});
|
||||
});
|
||||
|
||||
it('should support returning a custom status code when credentials are missing', function(cb) {
|
||||
var OPTS = JSON.parse(JSON.stringify(BASE_OPTS));
|
||||
OPTS.missingCredentialsStatus = 401;
|
||||
|
||||
start_servers(OPTS, BASE_TEST_OPTS)(function() {
|
||||
request(expressapp)
|
||||
.post('/login')
|
||||
.expect(401)
|
||||
.end(cb);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('with options as function', function() {
|
||||
var OPTS = JSON.parse(JSON.stringify(BASE_OPTS));
|
||||
OPTS.usernameField = 'cb_uname';
|
||||
OPTS.passwordField = 'cb_pwd';
|
||||
|
||||
var opts = function(cb) {
|
||||
process.nextTick(function() {
|
||||
cb(null, OPTS);
|
||||
});
|
||||
};
|
||||
|
||||
before(start_servers(opts, BASE_TEST_OPTS));
|
||||
after(stop_servers);
|
||||
|
||||
it('should use the options returned from the function', function(cb) {
|
||||
request(expressapp)
|
||||
.post('/login')
|
||||
.send({cb_uname: 'valid', cb_pwd: 'valid'})
|
||||
.expect(200)
|
||||
.end(cb);
|
||||
});
|
||||
|
||||
it('should not allow login if using wrong fields', function(cb) {
|
||||
request(expressapp)
|
||||
.post('/login')
|
||||
.send({username: 'valid', password: 'valid'})
|
||||
.expect(400)
|
||||
.end(cb);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with options as function returning dynamic sets', function() {
|
||||
var OPTS = JSON.parse(JSON.stringify(BASE_OPTS));
|
||||
OPTS.usernameField = 'first_uname';
|
||||
OPTS.passwordField = 'first_pwd';
|
||||
|
||||
var OPTS2 = JSON.parse(JSON.stringify(BASE_OPTS));
|
||||
OPTS2.usernameField = 'second_uname';
|
||||
OPTS2.passwordField = 'second_pwd';
|
||||
|
||||
var opts = function(req, cb) {
|
||||
process.nextTick(function() {
|
||||
if (req.body.set == 'first') {
|
||||
cb(null, OPTS);
|
||||
} else {
|
||||
cb(null, OPTS2);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
before(start_servers(opts, BASE_TEST_OPTS));
|
||||
after(stop_servers);
|
||||
|
||||
it('should use the first set options returned from the function', function(cb) {
|
||||
request(expressapp)
|
||||
.post('/login')
|
||||
.send({first_uname: 'valid', first_pwd: 'valid', set: 'first'})
|
||||
.expect(200)
|
||||
.end(cb);
|
||||
});
|
||||
|
||||
it('should not allow first set login if using wrong fields', function(cb) {
|
||||
request(expressapp)
|
||||
.post('/login')
|
||||
.send({second_uname: 'valid', second_pwd: 'valid', set: 'first'})
|
||||
.expect(400)
|
||||
.end(cb);
|
||||
});
|
||||
|
||||
it('should use the second set options returned from the function', function(cb) {
|
||||
request(expressapp)
|
||||
.post('/login')
|
||||
.send({second_uname: 'valid', second_pwd: 'valid', set: 'second'})
|
||||
.expect(200)
|
||||
.end(cb);
|
||||
});
|
||||
|
||||
it('should not allow second set login if using wrong fields', function(cb) {
|
||||
request(expressapp)
|
||||
.post('/login')
|
||||
.send({first_uname: 'valid', first_pwd: 'valid', set: 'second'})
|
||||
.expect(400)
|
||||
.end(cb);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with group fetch settings defined', function() {
|
||||
var OPTS;
|
||||
|
||||
var groupTest = function(opts, cb) {
|
||||
start_servers(opts, BASE_TEST_OPTS)(function() {
|
||||
var req = {body: {username: 'valid', password: 'valid'}},
|
||||
s = new LdapStrategy(opts, function(user, done) {
|
||||
req.should.have.keys('body');
|
||||
req.body.should.have.keys(['username', 'password']);
|
||||
done(null, user);
|
||||
});
|
||||
|
||||
s.success = function(user) {
|
||||
should.exist(user);
|
||||
user.uid.should.equal('valid');
|
||||
user._groups.length.should.equal(2);
|
||||
user._groups[0].name.should.equal('Group 1');
|
||||
user._groups[1].name.should.equal('Group 2');
|
||||
cb();
|
||||
};
|
||||
|
||||
s.error = function() {}; // Just to have this when not run via passport
|
||||
|
||||
s.authenticate(req);
|
||||
});
|
||||
}
|
||||
|
||||
beforeEach(function(cb) {
|
||||
OPTS = JSON.parse(JSON.stringify(BASE_OPTS));
|
||||
OPTS.server.groupSearchBase = 'ou=passport-ldapauth';
|
||||
OPTS.server.groupSearchScope = 'sub';
|
||||
cb();
|
||||
});
|
||||
|
||||
afterEach(stop_servers);
|
||||
|
||||
it('should return groups for user with string filter', function(cb) {
|
||||
OPTS.server.groupSearchFilter = '(member={{dn}})';
|
||||
groupTest(OPTS, cb);
|
||||
});
|
||||
|
||||
it('should return groups for user with function filter', function(cb) {
|
||||
OPTS.server.groupSearchFilter = function(user) {
|
||||
return '(member={{dn}})'.replace(/{{dn}}/, user.dn)
|
||||
};
|
||||
groupTest(OPTS, cb);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with invalid LDAP url', function() {
|
||||
var OPTS = JSON.parse(JSON.stringify(BASE_OPTS));
|
||||
OPTS.server.url = 'ldap://nonexistingdomain.fi:389';
|
||||
|
||||
before(start_servers(OPTS, BASE_TEST_OPTS));
|
||||
|
||||
after(stop_servers);
|
||||
|
||||
it('should return with an error', function(cb) {
|
||||
request(expressapp)
|
||||
.post('/login')
|
||||
.send({username: 'valid', password: 'valid'})
|
||||
.expect(500)
|
||||
.end(cb);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with handleErrorsAsFailures', function() {
|
||||
after(stop_servers);
|
||||
|
||||
it('should return with a failure', function(cb) {
|
||||
var callbackCalled = false;
|
||||
var testCompleted = false;
|
||||
var OPTS = JSON.parse(JSON.stringify(BASE_OPTS));
|
||||
OPTS.server.url = 'ldap://nonexistingdomain.fi:389';
|
||||
OPTS.handleErrorsAsFailures = true;
|
||||
OPTS.failureErrorCallback = function(err) {
|
||||
should.exist(err);
|
||||
callbackCalled = true;
|
||||
}
|
||||
|
||||
start_servers(OPTS, BASE_TEST_OPTS)(function() {
|
||||
var req = {body: {username: 'valid', password: 'valid'}},
|
||||
s = new LdapStrategy(OPTS);
|
||||
|
||||
s.fail = function(msg, code) {
|
||||
code.should.equal(500);
|
||||
callbackCalled.should.be.true;
|
||||
// There can be more than one event emitted and mocha fails
|
||||
// if callback is called more than once
|
||||
if (testCompleted === false) {
|
||||
testCompleted = true;
|
||||
cb();
|
||||
}
|
||||
}
|
||||
|
||||
s.error = function() {}; // Just to have this when not run via passport
|
||||
|
||||
s.authenticate(req);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
"use strict";
|
||||
exports.__esModule = true;
|
||||
var Passport = require("passport");
|
||||
var LdapStrategy = require("../../");
|
||||
var Express = require("express");
|
||||
var Logger = require("bunyan");
|
||||
var BasicAuth = require("basic-auth");
|
||||
var app = Express();
|
||||
var user = { dn: 'test' };
|
||||
var log = new Logger({
|
||||
name: 'ldap',
|
||||
component: 'client',
|
||||
stream: process.stderr,
|
||||
level: 'trace'
|
||||
});
|
||||
var options = {
|
||||
server: {
|
||||
url: 'ldap://ldap.forumsys.com:389',
|
||||
bindDN: 'cn=read-only-admin,dc=example,dc=com',
|
||||
bindCredentials: 'password',
|
||||
searchBase: 'dc=example,dc=com',
|
||||
searchFilter: '(uid={{username}})',
|
||||
log: log,
|
||||
cache: true,
|
||||
includeRaw: true,
|
||||
groupSearchFilter: '(member={{dn}})',
|
||||
groupSearchBase: 'dc=example,dc=com'
|
||||
},
|
||||
credentialsLookup: BasicAuth
|
||||
};
|
||||
var optionsAsFunction = function (req, callback) {
|
||||
callback(null, options);
|
||||
};
|
||||
var regularCallback = function (user, callback) {
|
||||
if (user.foo) {
|
||||
callback(new Error('Foo user is an error'), null, { message: 'Foo user' });
|
||||
}
|
||||
else if (!user) {
|
||||
callback(null, false, { message: 'No user' });
|
||||
}
|
||||
else {
|
||||
callback(null, user);
|
||||
}
|
||||
};
|
||||
var reqCallback = function (req, user, callback) {
|
||||
if (user.foo) {
|
||||
callback(new Error('Foo user is an error'), null, { message: 'Foo user' });
|
||||
}
|
||||
else if (!user) {
|
||||
callback(null, false, { message: 'No user' });
|
||||
}
|
||||
else {
|
||||
callback(null, user);
|
||||
}
|
||||
};
|
||||
var credentialsLookup = function (req) { return ({
|
||||
user: 'username',
|
||||
pass: 'password'
|
||||
}); };
|
||||
Passport.serializeUser(function (user, done) { return done(null, user.dn); });
|
||||
Passport.deserializeUser(function (dn, done) { return done(null, user); });
|
||||
Passport.use(new LdapStrategy(options, regularCallback));
|
||||
Passport.use('withreq', new LdapStrategy(options, reqCallback));
|
||||
Passport.use('dynopts', new LdapStrategy(optionsAsFunction));
|
||||
var authOpts = {
|
||||
badRequestMessage: 'Bad request you did there'
|
||||
};
|
||||
app.post('/login', Passport.authenticate('ldapauth', authOpts));
|
||||
app.post('/login', function (req, res, next) {
|
||||
Passport.authenticate('ldapauth', function (err, user) {
|
||||
req.logIn(user, function (err) {
|
||||
res.send({ ok: 1 });
|
||||
});
|
||||
})(req, res, next);
|
||||
});
|
||||
//# sourceMappingURL=test.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"test.js","sourceRoot":"","sources":["test.ts"],"names":[],"mappings":";;AAGA,mCAAqC;AACrC,qCAAuC;AACvC,iCAAmC;AAEnC,+BAAiC;AACjC,sCAAwC;AAExC,IAAM,GAAG,GAAG,OAAO,EAAE,CAAC;AAOtB,IAAM,IAAI,GAAS,EAAE,EAAE,EAAE,MAAM,EAAE,CAAC;AAElC,IAAM,GAAG,GAAG,IAAI,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM;IACZ,SAAS,EAAE,QAAQ;IACnB,MAAM,EAAE,OAAO,CAAC,MAAM;IACtB,KAAK,EAAE,OAAO;CACjB,CAAC,CAAC;AAEH,IAAM,OAAO,GAAyB;IAClC,MAAM,EAAE;QACJ,GAAG,EAAE,8BAA8B;QACnC,MAAM,EAAE,sCAAsC;QAC9C,eAAe,EAAE,UAAU;QAC3B,UAAU,EAAE,mBAAmB;QAC/B,YAAY,EAAE,oBAAoB;QAClC,GAAG,EAAE,GAAG;QACR,KAAK,EAAE,IAAI;QACX,UAAU,EAAE,IAAI;QAChB,iBAAiB,EAAE,iBAAiB;QACpC,eAAe,EAAE,mBAAmB;KACvC;IACD,iBAAiB,EAAE,SAAS;CAC/B,CAAA;AAED,IAAM,iBAAiB,GAAiC,UAAC,GAAY,EAAE,QAA8C;IACjH,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;AAC5B,CAAC,CAAA;AAED,IAAM,eAAe,GAAgC,UAAC,IAAU,EAAE,QAAyC;IACvG,IAAI,IAAI,CAAC,GAAG,EAAE;QACV,QAAQ,CAAC,IAAI,KAAK,CAAC,sBAAsB,CAAC,EAAE,IAAI,EAAE,EAAE,OAAO,EAAE,UAAU,EAAE,CAAC,CAAC;KAC9E;SAAM,IAAI,CAAC,IAAI,EAAE;QACd,QAAQ,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC,CAAC;KACjD;SAAM;QACH,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;KACxB;AACL,CAAC,CAAA;AAED,IAAM,WAAW,GAA2C,UAAC,GAAY,EAAE,IAAU,EAAE,QAAyC;IAC5H,IAAI,IAAI,CAAC,GAAG,EAAE;QACV,QAAQ,CAAC,IAAI,KAAK,CAAC,sBAAsB,CAAC,EAAE,IAAI,EAAE,EAAE,OAAO,EAAE,UAAU,EAAE,CAAC,CAAC;KAC9E;SAAM,IAAI,CAAC,IAAI,EAAE;QACd,QAAQ,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC,CAAC;KACjD;SAAM;QACH,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;KACxB;AACL,CAAC,CAAA;AAED,IAAM,iBAAiB,GAAmC,UAAC,GAAY,IAA2C,OAAA,CAAC;IAC/G,IAAI,EAAE,UAAU;IAChB,IAAI,EAAE,UAAU;CACnB,CAAC,EAHgH,CAGhH,CAAC;AAEH,QAAQ,CAAC,aAAa,CAAC,UAAC,IAAU,EAAE,IAAI,IAAK,OAAA,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE,CAAC,EAAnB,CAAmB,CAAC,CAAC;AAClE,QAAQ,CAAC,eAAe,CAAC,UAAC,EAAE,EAAE,IAAI,IAAK,OAAA,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,EAAhB,CAAgB,CAAC,CAAC;AAEzD,QAAQ,CAAC,GAAG,CAAC,IAAI,YAAY,CAAC,OAAO,EAAE,eAAe,CAAC,CAAC,CAAC;AACzD,QAAQ,CAAC,GAAG,CAAC,SAAS,EAAE,IAAI,YAAY,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC,CAAC;AAChE,QAAQ,CAAC,GAAG,CAAC,SAAS,EAAE,IAAI,YAAY,CAAC,iBAAiB,CAAC,CAAC,CAAC;AAE7D,IAAM,QAAQ,GAAqC;IAC/C,iBAAiB,EAAE,2BAA2B;CACjD,CAAA;AAED,GAAG,CAAC,IAAI,CAAC,QAAQ,EAAE,QAAQ,CAAC,YAAY,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC,CAAC;AAEhE,GAAG,CAAC,IAAI,CAAC,QAAQ,EAAE,UAAC,GAAG,EAAE,GAAG,EAAE,IAAI;IAC9B,QAAQ,CAAC,YAAY,CAAC,UAAU,EAAE,UAAC,GAAU,EAAE,IAAU;QACrD,GAAG,CAAC,KAAK,CAAC,IAAI,EAAE,UAAC,GAAU;YACvB,GAAG,CAAC,IAAI,CAAC,EAAC,EAAE,EAAE,CAAC,EAAC,CAAC,CAAC;QACtB,CAAC,CAAC,CAAA;IACN,CAAC,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC;AACvB,CAAC,CAAC,CAAC"}
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
/**
|
||||
* Just a test file that should compile properly with tsc
|
||||
*/
|
||||
import * as Passport from 'passport';
|
||||
import * as LdapStrategy from '../../';
|
||||
import * as Express from 'express';
|
||||
import { Request } from 'express';
|
||||
import * as Logger from 'bunyan';
|
||||
import * as BasicAuth from 'basic-auth';
|
||||
|
||||
const app = Express();
|
||||
|
||||
interface User {
|
||||
dn: string;
|
||||
foo?: boolean;
|
||||
}
|
||||
|
||||
const user: User = { dn: 'test' };
|
||||
|
||||
const log = new Logger({
|
||||
name: 'ldap',
|
||||
component: 'client',
|
||||
stream: process.stderr,
|
||||
level: 'trace'
|
||||
});
|
||||
|
||||
const options: LdapStrategy.Options = {
|
||||
server: {
|
||||
url: 'ldap://ldap.forumsys.com:389',
|
||||
bindDN: 'cn=read-only-admin,dc=example,dc=com',
|
||||
bindCredentials: 'password',
|
||||
searchBase: 'dc=example,dc=com',
|
||||
searchFilter: '(uid={{username}})',
|
||||
log: log,
|
||||
cache: true,
|
||||
includeRaw: true,
|
||||
groupSearchFilter: '(member={{dn}})',
|
||||
groupSearchBase: 'dc=example,dc=com'
|
||||
},
|
||||
credentialsLookup: BasicAuth
|
||||
}
|
||||
|
||||
const optionsAsFunction: LdapStrategy.OptionsFunction = (req: Request, callback: LdapStrategy.OptionsFunctionCallback) => {
|
||||
callback(null, options);
|
||||
}
|
||||
|
||||
const regularCallback: LdapStrategy.VerifyCallback = (user: User, callback: LdapStrategy.VerifyDoneCallback) => {
|
||||
if (user.foo) {
|
||||
callback(new Error('Foo user is an error'), null, { message: 'Foo user' });
|
||||
} else if (!user) {
|
||||
callback(null, false, { message: 'No user' });
|
||||
} else {
|
||||
callback(null, user);
|
||||
}
|
||||
}
|
||||
|
||||
const reqCallback: LdapStrategy.VerifyCallbackWithRequest = (req: Request, user: User, callback: LdapStrategy.VerifyDoneCallback) => {
|
||||
if (user.foo) {
|
||||
callback(new Error('Foo user is an error'), null, { message: 'Foo user' });
|
||||
} else if (!user) {
|
||||
callback(null, false, { message: 'No user' });
|
||||
} else {
|
||||
callback(null, user);
|
||||
}
|
||||
}
|
||||
|
||||
const credentialsLookup: LdapStrategy.CredentialsLookup = (req: Request): LdapStrategy.CredentialsLookupResult => ({
|
||||
user: 'username',
|
||||
pass: 'password'
|
||||
});
|
||||
|
||||
Passport.serializeUser((user: User, done) => done(null, user.dn));
|
||||
Passport.deserializeUser((dn, done) => done(null, user));
|
||||
|
||||
Passport.use(new LdapStrategy(options, regularCallback));
|
||||
Passport.use('withreq', new LdapStrategy(options, reqCallback));
|
||||
Passport.use('dynopts', new LdapStrategy(optionsAsFunction));
|
||||
|
||||
const authOpts: LdapStrategy.AuthenticateOptions = {
|
||||
badRequestMessage: 'Bad request you did there'
|
||||
}
|
||||
|
||||
app.post('/login', Passport.authenticate('ldapauth', authOpts));
|
||||
|
||||
app.post('/login', (req, res, next) => {
|
||||
Passport.authenticate('ldapauth', (err: Error, user: User) => {
|
||||
req.logIn(user, (err: Error) => {
|
||||
res.send({ok: 1});
|
||||
})
|
||||
})(req, res, next);
|
||||
});
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"noImplicitAny": true,
|
||||
"removeComments": true,
|
||||
"preserveConstEnums": true,
|
||||
"sourceMap": true
|
||||
},
|
||||
"files": [
|
||||
"test.ts"
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user