Skip to content

Commit b04ef0d

Browse files
committed
feat(active-directory): create @verdaccio/active-directory plugin
1 parent 5dd82d1 commit b04ef0d

File tree

14 files changed

+581
-17
lines changed

14 files changed

+581
-17
lines changed

plugins/active-directory/.babelrc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"presets": [
3+
["@verdaccio", { "typescript": true }]
4+
]
5+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
lib/
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
lib/

plugins/active-directory/LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2019 Verdaccio
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

plugins/active-directory/README.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# @verdaccio/active-directory
2+
3+
Active Directory authentication plugin for Verdaccio
4+
5+
## Installation
6+
7+
```bash
8+
npm install -g @verdaccio/active-directory
9+
```
10+
11+
## Config
12+
13+
This settings can be set in `config.yaml`. All fields are mandatory except `groupName`, which is optional, to add security group(s). Also, this optional field can be a single string or a list of strings. Take care that, when defining `groupName` key, the user that will be authenticating must be in, at least, one of the groups defined, to authenticate successfully.
14+
15+
```yaml
16+
auth:
17+
activedirectory:
18+
url: "ldap://localhost"
19+
baseDN: 'dc=local,dc=host'
20+
domainSuffix: 'local.host'
21+
# groupName: 'singleGroup' # optional, single group syntax
22+
# groupName: # optional, multiple groups syntax
23+
# - 'group1'
24+
# - 'group2'
25+
```
26+
27+
## License
28+
29+
@verdaccio/active-directory is an open source project with [MIT license](LICENSE)
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
module.exports = {
2+
name: 'memory-storage-jest',
3+
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
4+
transform: {
5+
'^.+\\.(js|jsx|ts|tsx)$': 'babel-jest',
6+
},
7+
testPathIgnorePatterns: ['lib'],
8+
verbose: true,
9+
collectCoverage: true,
10+
coveragePathIgnorePatterns: ['node_modules', 'lib'],
11+
};
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
require.requireActual('babel/polyfill');
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
{
2+
"name": "@verdaccio/active-directory",
3+
"version": "8.0.0-next.3",
4+
"description": "Active Directory authentication plugin for Verdaccio",
5+
"keywords": [
6+
"verdaccio",
7+
"active-directory",
8+
"auth",
9+
"plugin"
10+
],
11+
"author": "Sergio Herrera <sergio@sergiohgz.eu>",
12+
"license": "MIT",
13+
"publishConfig": {
14+
"access": "public"
15+
},
16+
"main": "lib/index.js",
17+
"types": "lib/index.d.ts",
18+
"files": [
19+
"lib"
20+
],
21+
"engines": {
22+
"node": ">=8"
23+
},
24+
"dependencies": {
25+
"activedirectory2": "1.2.7"
26+
},
27+
"devDependencies": {
28+
"@types/activedirectory2": "^1.2.0",
29+
"@verdaccio/babel-preset": "^8.0.0-next.2",
30+
"@verdaccio/eslint-config": "^8.0.0-next.2",
31+
"@verdaccio/types": "^8.0.0-next.2"
32+
},
33+
"scripts": {
34+
"build": "npm run build:types && npm run build:js",
35+
"build:js": "babel src --out-dir lib --extensions \".ts,.tsx\" --source-maps inline",
36+
"build:types": "tsc --emitDeclarationOnly",
37+
"lint": "eslint \"**/*.{js,ts}\"",
38+
"lint:stage": "lint-staged",
39+
"test": "jest",
40+
"type-check": "tsc --noEmit",
41+
"type-check:watch": "npm run type-check -- --watch"
42+
},
43+
"lint-staged": {
44+
"**/*.{js,ts}": [
45+
"eslint --fix",
46+
"git add"
47+
]
48+
}
49+
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import { Callback, IPluginAuth, Logger } from '@verdaccio/types';
2+
import ActiveDirectory from 'activedirectory2';
3+
4+
export const NotAuthMessage = 'AD - Active Directory authentication failed';
5+
6+
export interface ActiveDirectoryConfig {
7+
url: string;
8+
baseDN: string;
9+
domainSuffix: string;
10+
groupName?: string | string[];
11+
}
12+
13+
class ActiveDirectoryPlugin implements IPluginAuth<ActiveDirectoryConfig> {
14+
private config: ActiveDirectoryConfig;
15+
private logger: Logger;
16+
17+
public constructor(config: ActiveDirectoryConfig, opts: { logger: Logger }) {
18+
this.config = config;
19+
this.logger = opts.logger;
20+
}
21+
22+
public authenticate(user: string, password: string, cb: Callback): void {
23+
const username = `${user}@${this.config.domainSuffix}`;
24+
25+
const connectionConfig = {
26+
...this.config,
27+
domainSuffix: undefined,
28+
username,
29+
password,
30+
};
31+
32+
const connection = new ActiveDirectory(connectionConfig);
33+
34+
connection.authenticate(username, password, (err, isAuthenticated): void => {
35+
if (err) {
36+
this.logger.warn(`AD - Active Directory authentication failed with error: ${err}`);
37+
return cb(err);
38+
}
39+
40+
if (!isAuthenticated) {
41+
this.logger.warn(NotAuthMessage);
42+
return cb(new Error(NotAuthMessage));
43+
}
44+
45+
const { groupName } = this.config;
46+
if (!groupName) {
47+
this.logger.info('AD - Active Directory authentication succeeded');
48+
cb(null, [user]);
49+
} else {
50+
// TODO check for updates on @types/activedirectory2 or add types for this fn
51+
// @ts-ignore
52+
connection.getGroupMembershipForUser(username, (err, groups: object[]): void => {
53+
if (err) {
54+
this.logger.warn(`AD - Active Directory group check failed with error: ${err}`);
55+
return cb(err);
56+
}
57+
58+
const requestedGroups = Array.isArray(groupName) ? groupName : [groupName];
59+
const matchingGroups = requestedGroups.filter((requestedGroup): boolean =>
60+
groups.some((group: any): boolean => requestedGroup === group.cn || requestedGroup === group.dn)
61+
);
62+
63+
if (matchingGroups.length <= 0) {
64+
const notMemberMessage = `AD - User ${user} is not member of group(s): ${requestedGroups.join(', ')}`;
65+
66+
this.logger.warn(notMemberMessage);
67+
cb(new Error(notMemberMessage));
68+
} else {
69+
this.logger.info(
70+
`AD - Active Directory authentication succeeded in group(s): ${matchingGroups.join(', ')}`
71+
);
72+
cb(null, [...matchingGroups, user]);
73+
}
74+
});
75+
}
76+
});
77+
}
78+
}
79+
80+
export default ActiveDirectoryPlugin;
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import ActiveDirectory from './active-directory';
2+
3+
export { ActiveDirectory };
4+
5+
export default ActiveDirectory;

0 commit comments

Comments
 (0)