feat: add more auth providers and cleanup google auth
it's no longer needed to use stunnel ;-)
This commit is contained in:
parent
5e5005cf6b
commit
3f600c664f
112
README.md
112
README.md
@ -14,6 +14,33 @@ This is a first implementation draft, which is currently only working with a nod
|
|||||||
|
|
||||||
CONTRIBUTIONS WELCOME! If you are willing to help, just open a PR or contact me via bug system or simon.tretter@hokify.com.
|
CONTRIBUTIONS WELCOME! If you are willing to help, just open a PR or contact me via bug system or simon.tretter@hokify.com.
|
||||||
|
|
||||||
|
## Motivation
|
||||||
|
|
||||||
|
### Why not Freeradius?
|
||||||
|
|
||||||
|
There are several reasons why I started implementing this radius server in node js. We are using
|
||||||
|
freeradius right now, but have several issues which are hard to tackle due to the reason that freeradius
|
||||||
|
is a complex software and supports many uses cases. It is also written in C++ and uses threads behind the scene.
|
||||||
|
Therefore it's not easy to extend or modify it, or even bring new feature in.
|
||||||
|
The idea of this project is to make a super simple node radius server, which is async by default. No complex
|
||||||
|
thread handling, no other fancy thing. The basic goal is to make WPA2 authenticiation easy again.
|
||||||
|
|
||||||
|
### 802.11x protocol in node
|
||||||
|
|
||||||
|
Another motivation is that it is very exciting to see how wireless protocols have evolved, and see
|
||||||
|
how a implementation like TTLS works.
|
||||||
|
|
||||||
|
### Few alternatives (only non-free ones like Jumpcloud...)
|
||||||
|
|
||||||
|
Furthermore there are few alternatives out there, e.g. jumpcloud is non-free and I couldn't find many others.
|
||||||
|
|
||||||
|
### Vision
|
||||||
|
|
||||||
|
As soon as I understood the TTLS PAP Tunnel approach, I had this vision of making Wlan Authentification easy
|
||||||
|
for everyone. Why limit it to something "complex" like LDAP and co. This library aims to make it easy for everyone
|
||||||
|
to implement either their own authentication mechanismus (e.g. against a database), or provides some mechansimns
|
||||||
|
out of the box (e.g. imap, static, ldap,..).
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
npm install
|
npm install
|
||||||
@ -28,7 +55,7 @@ you need:
|
|||||||
2. Optional: Create your own SSL certificate (e.g. self signed via npm run create-certificate)
|
2. Optional: Create your own SSL certificate (e.g. self signed via npm run create-certificate)
|
||||||
3. Check config.js and adapt to your needs
|
3. Check config.js and adapt to your needs
|
||||||
|
|
||||||
- configure authentication (passport config), e.g. for LDAP
|
- configure authentication e.g. for LDAP
|
||||||
|
|
||||||
```js
|
```js
|
||||||
var config = {
|
var config = {
|
||||||
@ -46,15 +73,86 @@ var config = {
|
|||||||
4. Install und build server: npm install && npm run build
|
4. Install und build server: npm install && npm run build
|
||||||
5. Start server "npm run start"
|
5. Start server "npm run start"
|
||||||
|
|
||||||
## Authentications
|
## Configuration
|
||||||
|
|
||||||
right now only one simple ldap implementation is done,
|
see config.js in root
|
||||||
the idea is though to use [passport.js](http://www.passportjs.org/) as authentication provider,
|
|
||||||
therefore it would be possible to use the radius server with your email provider authentication or any
|
|
||||||
other auth mechanismus you use (well everything with no 2factor or anything else that requries an extra step).
|
|
||||||
|
### Authentications
|
||||||
|
|
||||||
|
#### Google LDAP
|
||||||
|
|
||||||
|
google ldap optimized authenticiation implementaiton
|
||||||
|
|
||||||
|
#### LDAP
|
||||||
|
|
||||||
|
ldap authentication
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface ILDAPAuthOptions {
|
||||||
|
/** ldap url
|
||||||
|
* e.g. ldaps://ldap.google.com
|
||||||
|
*/
|
||||||
|
url: string;
|
||||||
|
/** base DN
|
||||||
|
* e.g. 'dc=hokify,dc=com', */
|
||||||
|
base: string;
|
||||||
|
/** tls options
|
||||||
|
* e.g. {
|
||||||
|
key: fs.readFileSync('ldap.gsuite.hokify.com.40567.key'),
|
||||||
|
cert: fs.readFileSync('ldap.gsuite.hokify.com.40567.crt'),
|
||||||
|
servername: 'ldap.google.com'
|
||||||
|
} */
|
||||||
|
tlsOptions?: any;
|
||||||
|
/**
|
||||||
|
* searchFilter
|
||||||
|
*/
|
||||||
|
searchFilter?: string;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### IMAP
|
||||||
|
|
||||||
|
imap authenticiation
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface IIMAPAuthOptions {
|
||||||
|
host: string;
|
||||||
|
port?: number;
|
||||||
|
useSecureTransport?: boolean;
|
||||||
|
validHosts?: string[];
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### SMTP
|
||||||
|
|
||||||
|
smtp authenticiation
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface ISMTPAuthOptions {
|
||||||
|
host: string;
|
||||||
|
port?: number;
|
||||||
|
useSecureTransport?: boolean;
|
||||||
|
validHosts?: string[];
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Static Auth
|
||||||
|
|
||||||
|
static authenticiation
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface IStaticAuthOtions {
|
||||||
|
validCrentials: {
|
||||||
|
username: string;
|
||||||
|
password: string;
|
||||||
|
}[];
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
You need to specify at least a radius password and the base DN for LDAP:
|
Ensure you have installed latest node version and run:
|
||||||
|
|
||||||
npm run start
|
npm run start
|
||||||
|
0
tests/.gitignore → __tests__/.gitignore
vendored
0
tests/.gitignore → __tests__/.gitignore
vendored
21
__tests__/auth/google-ldap.test.ts
Normal file
21
__tests__/auth/google-ldap.test.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import 'mocha';
|
||||||
|
import { expect } from 'chai';
|
||||||
|
import * as fs from 'fs';
|
||||||
|
import { GoogleLDAPAuth } from '../../src/auth/GoogleLDAPAuth';
|
||||||
|
|
||||||
|
describe('test google ldap auth', function() {
|
||||||
|
this.timeout(10000);
|
||||||
|
it('authenticate against ldap server', async () => {
|
||||||
|
const auth = new GoogleLDAPAuth({
|
||||||
|
base: 'dc=hokify,dc=com',
|
||||||
|
tlsOptions: {
|
||||||
|
key: fs.readFileSync('./ldap.gsuite.hokify.com.40567.key'),
|
||||||
|
cert: fs.readFileSync('./ldap.gsuite.hokify.com.40567.crt')
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await auth.authenticate('username', 'password');
|
||||||
|
|
||||||
|
expect(result).to.equal(true);
|
||||||
|
});
|
||||||
|
});
|
18
__tests__/auth/imap.test.ts
Normal file
18
__tests__/auth/imap.test.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import 'mocha';
|
||||||
|
import { expect } from 'chai';
|
||||||
|
import { IMAPAuth } from '../../src/auth/IMAPAuth';
|
||||||
|
|
||||||
|
describe('test imap auth', () => {
|
||||||
|
it('authenticate against imap server', async () => {
|
||||||
|
const auth = new IMAPAuth({
|
||||||
|
host: 'imap.gmail.com',
|
||||||
|
port: 993,
|
||||||
|
useSecureTransport: true,
|
||||||
|
validHosts: ['gmail.com']
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await auth.authenticate('username', 'password');
|
||||||
|
|
||||||
|
expect(result).to.equal(true);
|
||||||
|
});
|
||||||
|
});
|
23
__tests__/auth/ldap.test.ts
Normal file
23
__tests__/auth/ldap.test.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import 'mocha';
|
||||||
|
import { expect } from 'chai';
|
||||||
|
import * as fs from 'fs';
|
||||||
|
import { LDAPAuth } from '../../src/auth/LDAPAuth';
|
||||||
|
|
||||||
|
describe('test ldap auth', function() {
|
||||||
|
this.timeout(10000);
|
||||||
|
it('authenticate against ldap server', async () => {
|
||||||
|
const auth = new LDAPAuth({
|
||||||
|
url: 'ldaps://ldap.google.com:636',
|
||||||
|
base: 'dc=hokify,dc=com',
|
||||||
|
tlsOptions: {
|
||||||
|
servername: 'ldap.google.com',
|
||||||
|
key: fs.readFileSync('./ldap.gsuite.hokify.com.40567.key'),
|
||||||
|
cert: fs.readFileSync('./ldap.gsuite.hokify.com.40567.crt'),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await auth.authenticate('username', 'password');
|
||||||
|
|
||||||
|
expect(result).to.equal(true);
|
||||||
|
});
|
||||||
|
});
|
18
__tests__/auth/smtp.test.ts
Normal file
18
__tests__/auth/smtp.test.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import 'mocha';
|
||||||
|
import { expect } from 'chai';
|
||||||
|
import { SMTPAuth } from '../../src/auth/SMTPAuth';
|
||||||
|
|
||||||
|
describe('test smtp auth', () => {
|
||||||
|
it('authenticate against smtp server', async () => {
|
||||||
|
const auth = new SMTPAuth({
|
||||||
|
host: 'smtp.gmail.com',
|
||||||
|
port: 465,
|
||||||
|
useSecureTransport: true,
|
||||||
|
validHosts: ['gmail.com']
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await auth.authenticate('username', 'password');
|
||||||
|
|
||||||
|
expect(result).to.equal(true);
|
||||||
|
});
|
||||||
|
});
|
7
__tests__/tsconfig.json
Normal file
7
__tests__/tsconfig.json
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"extends": "../tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"include": ["__tests__/*.ts", "*.ts", "../src/**/*.ts"]
|
||||||
|
}
|
46
config.js
46
config.js
@ -19,20 +19,46 @@ module.exports = {
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
||||||
// authentication
|
// GoogleLDAPAuth (optimized for google auth)
|
||||||
authentication: 'ldap',
|
authentication: 'GoogleLDAPAuth',
|
||||||
authenticationOptions: {
|
authenticationOptions: {
|
||||||
url: 'ldap://127.0.0.1:1636',
|
|
||||||
base: 'dc=hokify,dc=com',
|
base: 'dc=hokify,dc=com',
|
||||||
tlsOptions2: {
|
tlsOptions: {
|
||||||
|
key: fs.readFileSync('ldap.gsuite.hokify.com.40567.key'),
|
||||||
|
cert: fs.readFileSync('ldap.gsuite.hokify.com.40567.crt')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** LDAP AUTH
|
||||||
|
authentication: 'LDAPAuth',
|
||||||
|
authenticationOptions: {
|
||||||
|
url: 'ldaps://ldap.google.com',
|
||||||
|
base: 'dc=hokify,dc=com',
|
||||||
|
tlsOptions: {
|
||||||
key: fs.readFileSync('ldap.gsuite.hokify.com.40567.key'),
|
key: fs.readFileSync('ldap.gsuite.hokify.com.40567.key'),
|
||||||
cert: fs.readFileSync('ldap.gsuite.hokify.com.40567.crt'),
|
cert: fs.readFileSync('ldap.gsuite.hokify.com.40567.crt'),
|
||||||
|
servername: 'ldap.google.com'
|
||||||
// This is necessary only if using the client certificate authentication.
|
|
||||||
requestCert: true,
|
|
||||||
|
|
||||||
// This is necessary only if the client uses the self-signed certificate.
|
|
||||||
ca: [fs.readFileSync('ldap.gsuite.hokify.com.40567.key')]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** IMAP AUTH
|
||||||
|
authentication: 'IMAPAuth',
|
||||||
|
authenticationOptions: {
|
||||||
|
host: 'imap.gmail.com',
|
||||||
|
port: 993,
|
||||||
|
useSecureTransport: true,
|
||||||
|
validHosts: ['hokify.com']
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** SMTP AUTH
|
||||||
|
authentication: 'IMAPAuth',
|
||||||
|
authenticationOptions: {
|
||||||
|
host: 'smtp.gmail.com',
|
||||||
|
port: 465,
|
||||||
|
useSecureTransport: true,
|
||||||
|
validHosts: ['gmail.com']
|
||||||
|
}
|
||||||
|
*/
|
||||||
};
|
};
|
||||||
|
899
package-lock.json
generated
899
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
19
package.json
19
package.json
@ -1,34 +1,41 @@
|
|||||||
{
|
{
|
||||||
"name": "radius-server",
|
"name": "radius-server",
|
||||||
"description": "radius server for google LDAP and TTLT",
|
"description": "radius server for google LDAP and TTLS",
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"debug": "DEBUG=radius:* ../node/node dist/app.js",
|
"debug": "DEBUG=radius:* ../node/node dist/app.js",
|
||||||
"start": "../node/node dist/app.js",
|
"start": "../node/node dist/app.js",
|
||||||
"build": "tsc",
|
"build": "tsc",
|
||||||
"dev": "ts-node src/app.ts",
|
"dev": "ts-node src/app.ts",
|
||||||
"test-ttls-pap": "tests/eapol_test -c tests/ttls-pap.conf -s testing123",
|
"test": "mocha -r ts-node/register __tests__/**/*.test.ts",
|
||||||
|
"test-ttls-pap": "__tests__/eapol_test -c __tests__/ttls-pap.conf -s testing123",
|
||||||
"test-radtest": "radtest -x user pwd localhost 1812 testing123",
|
"test-radtest": "radtest -x user pwd localhost 1812 testing123",
|
||||||
"create-certificate": "sh ./ssl/create.sh && sh ./ssl/sign.sh"
|
"create-certificate": "sh ./ssl/create.sh && sh ./ssl/sign.sh"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"debug": "^4.1.1",
|
"debug": "^4.1.1",
|
||||||
|
"imap-simple": "^4.3.0",
|
||||||
|
"ldapauth-fork": "^4.3.1",
|
||||||
"ldapjs": "^1.0.2",
|
"ldapjs": "^1.0.2",
|
||||||
"md5": "^2.2.1",
|
"md5": "^2.2.1",
|
||||||
"native-duplexpair": "^1.0.0",
|
"native-duplexpair": "^1.0.0",
|
||||||
"node-cache": "^5.1.0",
|
"node-cache": "^5.1.0",
|
||||||
"passport-ldapauth": "^2.1.3",
|
|
||||||
"radius": "~1.1.4",
|
"radius": "~1.1.4",
|
||||||
"ts-node": "^8.6.2",
|
"ts-node": "^8.6.2",
|
||||||
"type-cacheable": "^4.0.0",
|
"type-cacheable": "^4.0.0",
|
||||||
"yargs": "~15.1.0"
|
"yargs": "~15.1.0",
|
||||||
|
"smtp-client": "^0.3.1"
|
||||||
},
|
},
|
||||||
"license": "GPLv3",
|
"license": "GPLv3",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/ldapjs": "^1.0.5",
|
|
||||||
"@types/radius": "0.0.28",
|
|
||||||
"@hokify/eslint-config": "^0.2.4",
|
"@hokify/eslint-config": "^0.2.4",
|
||||||
|
"@types/chai": "^4.2.9",
|
||||||
|
"@types/ldapjs": "^1.0.5",
|
||||||
|
"@types/mocha": "^7.0.1",
|
||||||
|
"@types/radius": "0.0.28",
|
||||||
|
"chai": "^4.2.0",
|
||||||
"eslint": "^6.8.0",
|
"eslint": "^6.8.0",
|
||||||
|
"mocha": "^7.0.1",
|
||||||
"prettier": "^1.19.1",
|
"prettier": "^1.19.1",
|
||||||
"typescript": "^3.8.2"
|
"typescript": "^3.8.2"
|
||||||
}
|
}
|
||||||
|
26
src/app.ts
26
src/app.ts
@ -1,24 +1,32 @@
|
|||||||
import { GoogleLDAPAuth } from './auth/google-ldap';
|
|
||||||
import { UDPServer } from './server/UDPServer';
|
import { UDPServer } from './server/UDPServer';
|
||||||
import { RadiusService } from './radius/RadiusService';
|
import { RadiusService } from './radius/RadiusService';
|
||||||
|
|
||||||
import * as config from '../config';
|
import * as config from '../config';
|
||||||
|
import { Authentication } from './auth';
|
||||||
|
import { IAuthentication } from './types/Authentication';
|
||||||
|
|
||||||
console.log(`Listener Port: ${config.port || 1812}`);
|
console.log(`Listener Port: ${config.port || 1812}`);
|
||||||
console.log(`RADIUS Secret: ${config.secret}`);
|
console.log(`RADIUS Secret: ${config.secret}`);
|
||||||
console.log(`Auth Mode: ${config.authentication}`);
|
console.log(`Auth Mode: ${config.authentication}`);
|
||||||
|
|
||||||
// const ldap = new LDAPAuth({url: 'ldap://ldap.google.com', base: 'dc=hokify,dc=com', uid: 'uid', tlsOptions});
|
(async () => {
|
||||||
|
/* configure auth mechansim */
|
||||||
const ldap = new GoogleLDAPAuth(
|
let auth: IAuthentication;
|
||||||
config.authenticationOptions.url,
|
try {
|
||||||
config.authenticationOptions.base
|
const AuthMechanismus = (await import(`./auth/${config.authentication}`))[
|
||||||
);
|
config.authentication
|
||||||
|
];
|
||||||
|
auth = new AuthMechanismus(config.authenticationOptions);
|
||||||
|
} catch (err) {
|
||||||
|
console.error('cannot load auth mechanismus', config.authentication);
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
// start radius server
|
||||||
|
const authentication = new Authentication(auth);
|
||||||
|
|
||||||
const server = new UDPServer(config.port);
|
const server = new UDPServer(config.port);
|
||||||
const radiusService = new RadiusService(config.secret, ldap);
|
const radiusService = new RadiusService(config.secret, authentication);
|
||||||
|
|
||||||
(async () => {
|
|
||||||
server.on('message', async (msg, rinfo) => {
|
server.on('message', async (msg, rinfo) => {
|
||||||
const response = await radiusService.handleMessage(msg);
|
const response = await radiusService.handleMessage(msg);
|
||||||
|
|
||||||
|
25
src/auth.ts
Normal file
25
src/auth.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import * as NodeCache from 'node-cache';
|
||||||
|
import { IAuthentication } from './types/Authentication';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* this is just a simple abstraction to provide
|
||||||
|
* an application layer for caching credentials
|
||||||
|
*/
|
||||||
|
export class Authentication implements IAuthentication {
|
||||||
|
cache = new NodeCache();
|
||||||
|
|
||||||
|
constructor(private authenticator: IAuthentication) {}
|
||||||
|
|
||||||
|
async authenticate(username: string, password: string): Promise<boolean> {
|
||||||
|
const cacheKey = `usr:${username}|pwd:${password}`;
|
||||||
|
const fromCache = this.cache.get(cacheKey) as undefined | boolean;
|
||||||
|
if (fromCache !== undefined) {
|
||||||
|
return fromCache;
|
||||||
|
}
|
||||||
|
|
||||||
|
const authResult = await this.authenticator.authenticate(username, password);
|
||||||
|
this.cache.set(cacheKey, authResult, 86400); // cache for one day
|
||||||
|
|
||||||
|
return authResult;
|
||||||
|
}
|
||||||
|
}
|
@ -1,26 +1,45 @@
|
|||||||
import * as NodeCache from 'node-cache';
|
|
||||||
|
|
||||||
import { Client, createClient } from 'ldapjs';
|
import { Client, createClient } from 'ldapjs';
|
||||||
import debug from 'debug';
|
import debug from 'debug';
|
||||||
|
import * as tls from 'tls';
|
||||||
import { IAuthentication } from '../types/Authentication';
|
import { IAuthentication } from '../types/Authentication';
|
||||||
|
|
||||||
const usernameFields = ['posixUid', 'mail'];
|
const usernameFields = ['posixUid', 'mail'];
|
||||||
|
|
||||||
const log = debug('radius:auth:ldap');
|
const log = debug('radius:auth:google-ldap');
|
||||||
// TLS:
|
// TLS:
|
||||||
// https://github.com/ldapjs/node-ldapjs/issues/307
|
// https://github.com/ldapjs/node-ldapjs/issues/307
|
||||||
|
|
||||||
|
interface IGoogleLDAPAuthOptions {
|
||||||
|
/** base DN
|
||||||
|
* e.g. 'dc=hokify,dc=com', */
|
||||||
|
base: string;
|
||||||
|
/** tls options
|
||||||
|
* e.g. {
|
||||||
|
key: fs.readFileSync('ldap.gsuite.hokify.com.40567.key'),
|
||||||
|
cert: fs.readFileSync('ldap.gsuite.hokify.com.40567.crt')
|
||||||
|
} */
|
||||||
|
tlsOptions: tls.TlsOptions;
|
||||||
|
}
|
||||||
|
|
||||||
export class GoogleLDAPAuth implements IAuthentication {
|
export class GoogleLDAPAuth implements IAuthentication {
|
||||||
cache = new NodeCache();
|
private ldap: Client;
|
||||||
|
|
||||||
ldap: Client;
|
private lastDNsFetch: Date;
|
||||||
|
|
||||||
lastDNsFetch: Date;
|
private allValidDNsCache: { [key: string]: string };
|
||||||
|
|
||||||
allValidDNsCache: { [key: string]: string };
|
private base: string;
|
||||||
|
|
||||||
constructor(private url: string, private base: string, tlsOptions?) {
|
constructor(config: IGoogleLDAPAuthOptions) {
|
||||||
this.ldap = createClient({ url, tlsOptions }).on('error', error => {
|
this.base = config.base;
|
||||||
|
|
||||||
|
this.ldap = createClient({
|
||||||
|
url: 'ldaps://ldap.google.com:636',
|
||||||
|
tlsOptions: {
|
||||||
|
...config.tlsOptions,
|
||||||
|
servername: 'ldap.google.com'
|
||||||
|
}
|
||||||
|
}).on('error', error => {
|
||||||
console.error('Error in ldap', error);
|
console.error('Error in ldap', error);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -74,12 +93,6 @@ export class GoogleLDAPAuth implements IAuthentication {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async authenticate(username: string, password: string, count = 0, forceFetching = false) {
|
async authenticate(username: string, password: string, count = 0, forceFetching = false) {
|
||||||
const cacheKey = `usr:${username}|pwd:${password}`;
|
|
||||||
const fromCache = this.cache.get(cacheKey);
|
|
||||||
if (fromCache !== undefined) {
|
|
||||||
return fromCache;
|
|
||||||
}
|
|
||||||
|
|
||||||
const cacheValidTime = new Date();
|
const cacheValidTime = new Date();
|
||||||
cacheValidTime.setHours(cacheValidTime.getHours() - 12);
|
cacheValidTime.setHours(cacheValidTime.getHours() - 12);
|
||||||
|
|
||||||
@ -100,14 +113,14 @@ export class GoogleLDAPAuth implements IAuthentication {
|
|||||||
if (!dnsFetched && !forceFetching) {
|
if (!dnsFetched && !forceFetching) {
|
||||||
return this.authenticate(username, password, count, true);
|
return this.authenticate(username, password, count, true);
|
||||||
}
|
}
|
||||||
console.error(`invalid username, not found in DN: ${username}`);
|
console.error(`invalid username, not found in DN: ${username}`, this.allValidDNsCache);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const authResult: boolean = await new Promise((resolve, reject) => {
|
const authResult: boolean = await new Promise((resolve, reject) => {
|
||||||
this.ldap.bind(dn, password, (err, res) => {
|
this.ldap.bind(dn, password, (err, res) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
if (err && (err as any).stack && (err as any).stack.includes(`${this.url} closed`)) {
|
if (err && (err as any).stack && (err as any).stack.includes(`ldap.google.com closed`)) {
|
||||||
count++;
|
count++;
|
||||||
// wait 1 second to give the ldap error handler time to reconnect
|
// wait 1 second to give the ldap error handler time to reconnect
|
||||||
setTimeout(() => resolve(this.authenticate(dn, password)), 2000);
|
setTimeout(() => resolve(this.authenticate(dn, password)), 2000);
|
||||||
@ -123,8 +136,6 @@ export class GoogleLDAPAuth implements IAuthentication {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
this.cache.set(cacheKey, authResult, 86400);
|
return !!authResult;
|
||||||
|
|
||||||
return authResult;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
64
src/auth/IMAPAuth.ts
Normal file
64
src/auth/IMAPAuth.ts
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
import * as imaps from 'imap-simple';
|
||||||
|
import { IAuthentication } from '../types/Authentication';
|
||||||
|
|
||||||
|
interface IIMAPAuthOptions {
|
||||||
|
host: string;
|
||||||
|
port?: number;
|
||||||
|
useSecureTransport?: boolean;
|
||||||
|
validHosts?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export class IMAPAuth implements IAuthentication {
|
||||||
|
private host: string;
|
||||||
|
|
||||||
|
private port = 143;
|
||||||
|
|
||||||
|
private useSecureTransport = false;
|
||||||
|
|
||||||
|
private validHosts?: string[];
|
||||||
|
|
||||||
|
constructor(config: IIMAPAuthOptions) {
|
||||||
|
this.host = config.host;
|
||||||
|
if (config.port !== undefined) {
|
||||||
|
this.port = config.port;
|
||||||
|
}
|
||||||
|
if (config.useSecureTransport !== undefined) {
|
||||||
|
this.useSecureTransport = config.useSecureTransport;
|
||||||
|
}
|
||||||
|
if (config.validHosts !== undefined) {
|
||||||
|
this.validHosts = config.validHosts;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async authenticate(username: string, password: string) {
|
||||||
|
if (this.validHosts) {
|
||||||
|
const domain = username.split('@').pop();
|
||||||
|
if (!domain || !this.validHosts.includes(domain)) {
|
||||||
|
console.info('invalid or no domain in username', username, domain);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let success = false;
|
||||||
|
try {
|
||||||
|
const connection = await imaps.connect({
|
||||||
|
imap: {
|
||||||
|
host: this.host,
|
||||||
|
port: this.port,
|
||||||
|
tls: this.useSecureTransport,
|
||||||
|
user: username,
|
||||||
|
password,
|
||||||
|
tlsOptions: {
|
||||||
|
servername: this.host // SNI (needs to be set for gmail)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
success = true;
|
||||||
|
|
||||||
|
connection.end();
|
||||||
|
} catch (err) {
|
||||||
|
console.error('imap auth failed', err);
|
||||||
|
}
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
}
|
57
src/auth/LDAPAuth.ts
Normal file
57
src/auth/LDAPAuth.ts
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
import * as LdapAuth from 'ldapauth-fork';
|
||||||
|
import { IAuthentication } from '../types/Authentication';
|
||||||
|
|
||||||
|
interface ILDAPAuthOptions {
|
||||||
|
/** ldap url
|
||||||
|
* e.g. ldaps://ldap.google.com
|
||||||
|
*/
|
||||||
|
url: string;
|
||||||
|
/** base DN
|
||||||
|
* e.g. 'dc=hokify,dc=com', */
|
||||||
|
base: string;
|
||||||
|
/** tls options
|
||||||
|
* e.g. {
|
||||||
|
key: fs.readFileSync('ldap.gsuite.hokify.com.40567.key'),
|
||||||
|
cert: fs.readFileSync('ldap.gsuite.hokify.com.40567.crt'),
|
||||||
|
servername: 'ldap.google.com'
|
||||||
|
} */
|
||||||
|
tlsOptions?: any;
|
||||||
|
/**
|
||||||
|
* searchFilter
|
||||||
|
*/
|
||||||
|
searchFilter?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class LDAPAuth implements IAuthentication {
|
||||||
|
private ldap: LdapAuth;
|
||||||
|
|
||||||
|
constructor(options: ILDAPAuthOptions) {
|
||||||
|
this.ldap = new LdapAuth({
|
||||||
|
url: options.url,
|
||||||
|
searchBase: options.base,
|
||||||
|
tlsOptions: options.tlsOptions,
|
||||||
|
searchFilter: options.searchFilter || '(uid={{username}})',
|
||||||
|
reconnect: true
|
||||||
|
});
|
||||||
|
this.ldap.on('error', function(err) {
|
||||||
|
console.error('LdapAuth: ', err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async authenticate(username: string, password: string) {
|
||||||
|
// console.log('AUTH', this.ldap);
|
||||||
|
const authResult: boolean = await new Promise((resolve, reject) => {
|
||||||
|
this.ldap.authenticate(username, password, function(err, user) {
|
||||||
|
if (err) {
|
||||||
|
resolve(false);
|
||||||
|
console.error('ldap error', err);
|
||||||
|
// reject(err);
|
||||||
|
}
|
||||||
|
if (user) resolve(user);
|
||||||
|
else reject();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return !!authResult;
|
||||||
|
}
|
||||||
|
}
|
68
src/auth/SMTPAuth.ts
Normal file
68
src/auth/SMTPAuth.ts
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
import { SMTPClient } from 'smtp-client';
|
||||||
|
import { IAuthentication } from '../types/Authentication';
|
||||||
|
|
||||||
|
interface ISMTPAuthOptions {
|
||||||
|
host: string;
|
||||||
|
port?: number;
|
||||||
|
useSecureTransport?: boolean;
|
||||||
|
validHosts?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export class SMTPAuth implements IAuthentication {
|
||||||
|
private host: string;
|
||||||
|
|
||||||
|
private port = 25;
|
||||||
|
|
||||||
|
private useSecureTransport = false;
|
||||||
|
|
||||||
|
private validHosts?: string[];
|
||||||
|
|
||||||
|
constructor(options: ISMTPAuthOptions) {
|
||||||
|
this.host = options.host;
|
||||||
|
|
||||||
|
if (options.port !== undefined) {
|
||||||
|
this.port = options.port;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.useSecureTransport !== undefined) {
|
||||||
|
this.useSecureTransport = options.useSecureTransport;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.validHosts !== undefined) {
|
||||||
|
this.validHosts = options.validHosts;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async authenticate(username: string, password: string) {
|
||||||
|
if (this.validHosts) {
|
||||||
|
const domain = username.split('@').pop();
|
||||||
|
if (!domain || !this.validHosts.includes(domain)) {
|
||||||
|
console.info('invalid or no domain in username', username, domain);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const s = new SMTPClient({
|
||||||
|
host: this.host,
|
||||||
|
port: this.port,
|
||||||
|
secure: this.useSecureTransport,
|
||||||
|
tlsOptions: {
|
||||||
|
servername: this.host // SNI (needs to be set for gmail)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let success = false;
|
||||||
|
try {
|
||||||
|
await s.connect();
|
||||||
|
await s.greet({ hostname: 'mx.domain.com' }); // runs EHLO command or HELO as a fallback
|
||||||
|
await s.authPlain({ username, password }); // authenticates a user
|
||||||
|
|
||||||
|
success = true;
|
||||||
|
|
||||||
|
s.close(); // runs QUIT command
|
||||||
|
} catch (err) {
|
||||||
|
console.error('imap auth failed', err);
|
||||||
|
}
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
}
|
22
src/auth/StaticAuth.ts
Normal file
22
src/auth/StaticAuth.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import { IAuthentication } from '../types/Authentication';
|
||||||
|
|
||||||
|
interface IStaticAuthOtions {
|
||||||
|
validCrentials: {
|
||||||
|
username: string;
|
||||||
|
password: string;
|
||||||
|
}[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export class StaticAuth implements IAuthentication {
|
||||||
|
private validCredentials: { username: string; password: string }[];
|
||||||
|
|
||||||
|
constructor(options: IStaticAuthOtions) {
|
||||||
|
this.validCredentials = options.validCrentials;
|
||||||
|
}
|
||||||
|
|
||||||
|
async authenticate(username: string, password: string) {
|
||||||
|
return !!this.validCredentials.find(
|
||||||
|
credential => credential.username === username && credential.password === password
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -1,3 +1,6 @@
|
|||||||
|
// https://tools.ietf.org/html/rfc5281 TTLS v0
|
||||||
|
// https://tools.ietf.org/html/draft-funk-eap-ttls-v1-00 TTLS v1 (not implemented)
|
||||||
|
|
||||||
/* eslint-disable no-bitwise */
|
/* eslint-disable no-bitwise */
|
||||||
import * as tls from 'tls';
|
import * as tls from 'tls';
|
||||||
import * as NodeCache from 'node-cache';
|
import * as NodeCache from 'node-cache';
|
||||||
|
@ -38,7 +38,7 @@ export class UDPServer extends events.EventEmitter implements IServer {
|
|||||||
|
|
||||||
// retry up to MAX_RETRIES to send this message,
|
// retry up to MAX_RETRIES to send this message,
|
||||||
// we automatically retry if there is no confirmation (=any incoming message from client)
|
// we automatically retry if there is no confirmation (=any incoming message from client)
|
||||||
// if expectAcknowledgment (e.g. Access-Accept or Access-Reject) is set, we do not retry
|
// if expectAcknowledgment is false (e.g. Access-Accept or Access-Reject), we do not retry
|
||||||
const identifierForRetry = `${address}:${port}`;
|
const identifierForRetry = `${address}:${port}`;
|
||||||
if (expectAcknowledgment && retried < UDPServer.MAX_RETRIES) {
|
if (expectAcknowledgment && retried < UDPServer.MAX_RETRIES) {
|
||||||
this.timeout[identifierForRetry] = setTimeout(sendResponse, 600 * (retried + 1));
|
this.timeout[identifierForRetry] = setTimeout(sendResponse, 600 * (retried + 1));
|
||||||
|
@ -1,9 +1,7 @@
|
|||||||
{
|
{
|
||||||
"extends": "./tsconfig.json",
|
"extends": "./tsconfig.json",
|
||||||
"include": [
|
"include": ["src/**/*.ts", "*.js", "*.ts", "__tests__/**/*.ts"],
|
||||||
"src/**/*.ts",
|
"exclude": ["node_modules"],
|
||||||
"*.js"
|
|
||||||
],
|
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
"checkJs": true
|
"checkJs": true
|
||||||
|
Loading…
Reference in New Issue
Block a user