From 3f600c664ffa7315053d47773c7f9d5060b68d32 Mon Sep 17 00:00:00 2001 From: simon Date: Sun, 23 Feb 2020 20:42:36 +0100 Subject: [PATCH] feat: add more auth providers and cleanup google auth it's no longer needed to use stunnel ;-) --- README.md | 112 ++- {tests => __tests__}/.gitignore | 0 __tests__/auth/google-ldap.test.ts | 21 + __tests__/auth/imap.test.ts | 18 + __tests__/auth/ldap.test.ts | 23 + __tests__/auth/smtp.test.ts | 18 + {tests => __tests__}/eapol_test | Bin __tests__/tsconfig.json | 7 + {tests => __tests__}/ttls-pap.conf | 0 config.js | 44 +- package-lock.json | 899 ++++++++++++++++-- package.json | 17 +- src/app.ts | 30 +- src/auth.ts | 25 + .../{google-ldap.ts => GoogleLDAPAuth.ts} | 51 +- src/auth/IMAPAuth.ts | 64 ++ src/auth/LDAPAuth.ts | 57 ++ src/auth/SMTPAuth.ts | 68 ++ src/auth/StaticAuth.ts | 22 + src/radius/handler/eapMethods/EAPTTLS.ts | 3 + src/server/UDPServer.ts | 2 +- tsconfig.eslint.json | 16 +- 22 files changed, 1348 insertions(+), 149 deletions(-) rename {tests => __tests__}/.gitignore (100%) create mode 100644 __tests__/auth/google-ldap.test.ts create mode 100644 __tests__/auth/imap.test.ts create mode 100644 __tests__/auth/ldap.test.ts create mode 100644 __tests__/auth/smtp.test.ts rename {tests => __tests__}/eapol_test (100%) create mode 100644 __tests__/tsconfig.json rename {tests => __tests__}/ttls-pap.conf (100%) create mode 100644 src/auth.ts rename src/auth/{google-ldap.ts => GoogleLDAPAuth.ts} (76%) create mode 100644 src/auth/IMAPAuth.ts create mode 100644 src/auth/LDAPAuth.ts create mode 100644 src/auth/SMTPAuth.ts create mode 100644 src/auth/StaticAuth.ts diff --git a/README.md b/README.md index acd122a..ce679c8 100644 --- a/README.md +++ b/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. +## 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 npm install @@ -28,7 +55,7 @@ you need: 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 -- configure authentication (passport config), e.g. for LDAP +- configure authentication e.g. for LDAP ```js var config = { @@ -46,15 +73,86 @@ var config = { 4. Install und build server: npm install && npm run build 5. Start server "npm run start" -## Authentications +## Configuration + +see config.js in root + + + +### Authentications + +#### Google LDAP + +google ldap optimized authenticiation implementaiton -right now only one simple ldap implementation is done, -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). +#### 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 -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 diff --git a/tests/.gitignore b/__tests__/.gitignore similarity index 100% rename from tests/.gitignore rename to __tests__/.gitignore diff --git a/__tests__/auth/google-ldap.test.ts b/__tests__/auth/google-ldap.test.ts new file mode 100644 index 0000000..40aa08b --- /dev/null +++ b/__tests__/auth/google-ldap.test.ts @@ -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); + }); +}); diff --git a/__tests__/auth/imap.test.ts b/__tests__/auth/imap.test.ts new file mode 100644 index 0000000..c7356ca --- /dev/null +++ b/__tests__/auth/imap.test.ts @@ -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); + }); +}); diff --git a/__tests__/auth/ldap.test.ts b/__tests__/auth/ldap.test.ts new file mode 100644 index 0000000..a869f60 --- /dev/null +++ b/__tests__/auth/ldap.test.ts @@ -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); + }); +}); diff --git a/__tests__/auth/smtp.test.ts b/__tests__/auth/smtp.test.ts new file mode 100644 index 0000000..468d165 --- /dev/null +++ b/__tests__/auth/smtp.test.ts @@ -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); + }); +}); diff --git a/tests/eapol_test b/__tests__/eapol_test similarity index 100% rename from tests/eapol_test rename to __tests__/eapol_test diff --git a/__tests__/tsconfig.json b/__tests__/tsconfig.json new file mode 100644 index 0000000..60d0836 --- /dev/null +++ b/__tests__/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + + }, + "include": ["__tests__/*.ts", "*.ts", "../src/**/*.ts"] +} diff --git a/tests/ttls-pap.conf b/__tests__/ttls-pap.conf similarity index 100% rename from tests/ttls-pap.conf rename to __tests__/ttls-pap.conf diff --git a/config.js b/config.js index c2cb426..57184c5 100644 --- a/config.js +++ b/config.js @@ -19,20 +19,46 @@ module.exports = { ] }, - // authentication - authentication: 'ldap', + // GoogleLDAPAuth (optimized for google auth) + authentication: 'GoogleLDAPAuth', authenticationOptions: { - url: 'ldap://127.0.0.1:1636', 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'), 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, + /** IMAP AUTH + authentication: 'IMAPAuth', + authenticationOptions: { + host: 'imap.gmail.com', + port: 993, + useSecureTransport: true, + validHosts: ['hokify.com'] + } + */ - // This is necessary only if the client uses the self-signed certificate. - ca: [fs.readFileSync('ldap.gsuite.hokify.com.40567.key')] - } + /** SMTP AUTH + authentication: 'IMAPAuth', + authenticationOptions: { + host: 'smtp.gmail.com', + port: 465, + useSecureTransport: true, + validHosts: ['gmail.com'] } + */ }; diff --git a/package-lock.json b/package-lock.json index 44a9147..b68c0e6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -57,53 +57,23 @@ "eslint-plugin-unicorn": "^16.1.1" } }, - "@types/body-parser": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.0.tgz", - "integrity": "sha512-W98JrE0j2K78swW4ukqMleo8R7h/pFETjM2DQ90MF6XK2i4LO4W3gQ71Lt4w3bfm2EvVSyWHplECvB5sK22yFQ==", - "requires": { - "@types/connect": "*", - "@types/node": "*" - } + "@types/chai": { + "version": "4.2.9", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.2.9.tgz", + "integrity": "sha512-NeXgZj+MFL4izGqA4sapdYzkzQG+MtGra9vhQ58dnmDY++VgJaRUws+aLVV5zRJCYJl/8s9IjMmhiUw1WsKSmw==", + "dev": true }, "@types/color-name": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==" }, - "@types/connect": { - "version": "3.4.33", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.33.tgz", - "integrity": "sha512-2+FrkXY4zllzTNfJth7jOqEHC+enpLeGslEhpnTAkg21GkRrWV4SsAtqchtT4YS9/nODBU2/ZfsBY2X4J/dX7A==", - "requires": { - "@types/node": "*" - } - }, "@types/eslint-visitor-keys": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz", "integrity": "sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag==", "dev": true }, - "@types/express": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.2.tgz", - "integrity": "sha512-5mHFNyavtLoJmnusB8OKJ5bshSzw+qkMIBAobLrIM48HJvunFva9mOa6aBwh64lBFyNwBbs0xiEFuj4eU/NjCA==", - "requires": { - "@types/body-parser": "*", - "@types/express-serve-static-core": "*", - "@types/serve-static": "*" - } - }, - "@types/express-serve-static-core": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.2.tgz", - "integrity": "sha512-El9yMpctM6tORDAiBwZVLMcxoTMcqqRO9dVyYcn7ycLWbvR8klrDn8CAOwRfZujZtWD7yS/mshTdz43jMOejbg==", - "requires": { - "@types/node": "*", - "@types/range-parser": "*" - } - }, "@types/ioredis": { "version": "4.14.7", "resolved": "https://registry.npmjs.org/@types/ioredis/-/ioredis-4.14.7.tgz", @@ -132,10 +102,11 @@ "@types/node": "*" } }, - "@types/mime": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@types/mime/-/mime-2.0.1.tgz", - "integrity": "sha512-FwI9gX75FgVBJ7ywgnq/P7tw+/o1GUbtP0KzbtusLigAOgIgNISRK0ZPl4qertvXSIE8YbsVJueQ90cDt9YYyw==" + "@types/mocha": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-7.0.1.tgz", + "integrity": "sha512-L/Nw/2e5KUaprNJoRA33oly+M8X8n0K+FwLTbYqwTcR14wdPWeRkigBLfSFpN/Asf9ENZTMZwLxjtjeYucAA4Q==", + "dev": true }, "@types/node": { "version": "13.7.1", @@ -156,14 +127,6 @@ "integrity": "sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA==", "dev": true }, - "@types/passport": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@types/passport/-/passport-1.0.2.tgz", - "integrity": "sha512-Pf39AYKf8q+YoONym3150cEwfUD66dtwHJWvbeOzKxnA0GZZ/vAXhNWv9vMhKyRQBQZiQyWQnhYBEBlKW6G8wg==", - "requires": { - "@types/express": "*" - } - }, "@types/radius": { "version": "0.0.28", "resolved": "https://registry.npmjs.org/@types/radius/-/radius-0.0.28.tgz", @@ -173,11 +136,6 @@ "@types/node": "*" } }, - "@types/range-parser": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.3.tgz", - "integrity": "sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA==" - }, "@types/redis": { "version": "2.8.15", "resolved": "https://registry.npmjs.org/@types/redis/-/redis-2.8.15.tgz", @@ -186,15 +144,6 @@ "@types/node": "*" } }, - "@types/serve-static": { - "version": "1.13.3", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.3.tgz", - "integrity": "sha512-oprSwp094zOglVrXdlo/4bAHtKTAxX6VT8FOZlBKrmyLbNvE1zxZyJ6yikMVtHIvwP45+ZQGJn+FdXGKTozq0g==", - "requires": { - "@types/express-serve-static-core": "*", - "@types/mime": "*" - } - }, "@typescript-eslint/eslint-plugin": { "version": "2.20.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.20.0.tgz", @@ -292,6 +241,12 @@ "integrity": "sha512-RO1ibKvd27e6FEShVFfPALuHI3WjSVNeK5FIsmme/LYRNxjKuNj+Dt7bucLa6NdSv3JcVTyMlm9kGR84z1XpaQ==", "dev": true }, + "ansi-colors": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz", + "integrity": "sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==", + "dev": true + }, "ansi-escapes": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.0.tgz", @@ -315,6 +270,16 @@ "color-convert": "^2.0.1" } }, + "anymatch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", + "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, "arg": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", @@ -414,6 +379,12 @@ "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" }, + "assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true + }, "ast-types-flow": { "version": "0.0.7", "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz", @@ -462,6 +433,12 @@ "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", "dev": true }, + "binary-extensions": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.0.0.tgz", + "integrity": "sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==", + "dev": true + }, "bn.js": { "version": "4.11.8", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", @@ -477,12 +454,27 @@ "concat-map": "0.0.1" } }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, "brorand": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=", "dev": true }, + "browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, "browserify-aes": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", @@ -604,6 +596,20 @@ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" }, + "chai": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.2.0.tgz", + "integrity": "sha512-XQU3bhBukrOsQCuwZndwGcCVQHyZi53fQ6Ys1Fym7E4olpIqqZZhhoFJoaKVvV17lWQoXYwgWN2nF5crA8J2jw==", + "dev": true, + "requires": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.2", + "deep-eql": "^3.0.1", + "get-func-name": "^2.0.0", + "pathval": "^1.1.0", + "type-detect": "^4.0.5" + } + }, "chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", @@ -652,6 +658,28 @@ "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz", "integrity": "sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc=" }, + "check-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", + "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", + "dev": true + }, + "chokidar": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.3.0.tgz", + "integrity": "sha512-dGmKLDdT3Gdl7fBUe8XK+gAtGmzy5Fn0XkkWQuYxGIgWVPPse2CxFA5mtrlD0TOHaHjEUqkWNyP1XdHoJES/4A==", + "dev": true, + "requires": { + "anymatch": "~3.1.1", + "braces": "~3.0.2", + "fsevents": "~2.1.1", + "glob-parent": "~5.1.0", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.2.0" + } + }, "ci-info": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", @@ -875,6 +903,15 @@ "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" }, + "deep-eql": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", + "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", + "dev": true, + "requires": { + "type-detect": "^4.0.0" + } + }, "deep-is": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", @@ -1742,6 +1779,15 @@ "flat-cache": "^2.0.1" } }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, "find-cache-dir": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-0.1.1.tgz", @@ -1768,6 +1814,23 @@ "path-exists": "^4.0.0" } }, + "flat": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/flat/-/flat-4.1.0.tgz", + "integrity": "sha512-Px/TiLIznH7gEDlPXcUD4KnBusa6kR6ayRUVcnEAbreRIuhkqow/mun59BuRXwoYk7ZQOLW1ZM05ilIvK38hFw==", + "dev": true, + "requires": { + "is-buffer": "~2.0.3" + }, + "dependencies": { + "is-buffer": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.4.tgz", + "integrity": "sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A==", + "dev": true + } + } + }, "flat-cache": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz", @@ -1827,6 +1890,13 @@ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "dev": true }, + "fsevents": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.2.tgz", + "integrity": "sha512-R4wDiBwZ0KzpgOWetKDug1FZcYhqYnUYKtfZYt4mD5SBz76q0KR4Q9o7GIPamsVPGmW3EYPPJ0dOOjvx32ldZA==", + "dev": true, + "optional": true + }, "function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", @@ -1844,6 +1914,12 @@ "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" }, + "get-func-name": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", + "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", + "dev": true + }, "get-stdin": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-6.0.0.tgz", @@ -1899,6 +1975,12 @@ "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==", "dev": true }, + "growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "dev": true + }, "has": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", @@ -1940,6 +2022,12 @@ "minimalistic-assert": "^1.0.1" } }, + "he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true + }, "hmac-drbg": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", @@ -1967,7 +2055,6 @@ "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dev": true, "requires": { "safer-buffer": ">= 2.1.2 < 3" } @@ -1984,6 +2071,51 @@ "integrity": "sha512-MzbUSahkTW1u7JpKKjY7LCARd1fU5W2rLdxlM4kdkayuCwZImjkpluF9CM1aLewYJguPDqewLam18Y6AU69A8A==", "dev": true }, + "imap": { + "version": "0.8.19", + "resolved": "https://registry.npmjs.org/imap/-/imap-0.8.19.tgz", + "integrity": "sha1-NniHOTSrCc6mukh0HyhNoq9Z2NU=", + "requires": { + "readable-stream": "1.1.x", + "utf7": ">=1.0.2" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + }, + "readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" + } + } + }, + "imap-simple": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/imap-simple/-/imap-simple-4.3.0.tgz", + "integrity": "sha512-SW3LtfEJFjlJKS/h2CmpX2IKpya2RXobR3ENJJW4iMQ3QYPxWxf5oeaz1K3P4eGUwfGEndkqt7uVDKnEyG9zeQ==", + "requires": { + "iconv-lite": "~0.4.13", + "imap": "^0.8.18", + "nodeify": "^1.0.0", + "quoted-printable": "^1.0.0", + "utf8": "^2.1.1", + "uuencode": "0.0.4" + } + }, "import-fresh": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.1.tgz", @@ -2081,6 +2213,15 @@ "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", "dev": true }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "requires": { + "binary-extensions": "^2.0.0" + } + }, "is-buffer": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", @@ -2118,6 +2259,12 @@ "is-extglob": "^2.1.1" } }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, "is-promise": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", @@ -2283,6 +2430,11 @@ "type-check": "~0.3.2" } }, + "line-buffer": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/line-buffer/-/line-buffer-0.1.4.tgz", + "integrity": "sha1-4H3hgzELYBIv3kszXPZgZUP/sdM=" + }, "lines-and-columns": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", @@ -2378,6 +2530,15 @@ "integrity": "sha1-7GZi5IlkCO1KtsVCo5kLcswIACA=", "dev": true }, + "log-symbols": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", + "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==", + "dev": true, + "requires": { + "chalk": "^2.0.1" + } + }, "loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -2484,6 +2645,236 @@ } } }, + "mocha": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-7.0.1.tgz", + "integrity": "sha512-9eWmWTdHLXh72rGrdZjNbG3aa1/3NRPpul1z0D979QpEnFdCG0Q5tv834N+94QEN2cysfV72YocQ3fn87s70fg==", + "dev": true, + "requires": { + "ansi-colors": "3.2.3", + "browser-stdout": "1.3.1", + "chokidar": "3.3.0", + "debug": "3.2.6", + "diff": "3.5.0", + "escape-string-regexp": "1.0.5", + "find-up": "3.0.0", + "glob": "7.1.3", + "growl": "1.10.5", + "he": "1.2.0", + "js-yaml": "3.13.1", + "log-symbols": "2.2.0", + "minimatch": "3.0.4", + "mkdirp": "0.5.1", + "ms": "2.1.1", + "node-environment-flags": "1.0.6", + "object.assign": "4.1.0", + "strip-json-comments": "2.0.1", + "supports-color": "6.0.0", + "which": "1.3.1", + "wide-align": "1.1.3", + "yargs": "13.3.0", + "yargs-parser": "13.1.1", + "yargs-unparser": "1.6.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "cliui": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "dev": true, + "requires": { + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true + }, + "supports-color": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.0.0.tgz", + "integrity": "sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "wrap-ansi": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" + } + }, + "yargs": { + "version": "13.3.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.0.tgz", + "integrity": "sha512-2eehun/8ALW8TLoIl7MVaRUrg+yCnenu8B4kBlRxj3GJGDKU1Og7sMXPNm1BYyM1DOJmTZ4YeN/Nwxv+8XJsUA==", + "dev": true, + "requires": { + "cliui": "^5.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^13.1.1" + } + }, + "yargs-parser": { + "version": "13.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.1.tgz", + "integrity": "sha512-oVAVsHz6uFrg3XQheFII8ESO2ssAf9luWuAd6Wexsu4F3OtIW0o8IribPXYrD4WC24LWtPrJlGy87y5udK+dxQ==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + } + } + }, "moment": { "version": "2.24.0", "resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz", @@ -2555,6 +2946,24 @@ "clone": "2.x" } }, + "node-environment-flags": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.6.tgz", + "integrity": "sha512-5Evy2epuL+6TM0lCQGpFIj6KwiEsGh1SrHUhTbNX+sLbBtjidPZFAnVK9y5yU1+h//RitLbRHTIMyxQPtxMdHw==", + "dev": true, + "requires": { + "object.getownpropertydescriptors": "^2.0.3", + "semver": "^5.7.0" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } + } + }, "node-libs-browser": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.2.1.tgz", @@ -2586,6 +2995,22 @@ "vm-browserify": "^1.0.1" } }, + "nodeify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/nodeify/-/nodeify-1.0.1.tgz", + "integrity": "sha1-ZKtpp7268DzhB7TwM1yHwLnpGx0=", + "requires": { + "is-promise": "~1.0.0", + "promise": "~1.3.0" + }, + "dependencies": { + "is-promise": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-1.0.1.tgz", + "integrity": "sha1-MVc3YcBX4zwukaq56W2gjO++duU=" + } + } + }, "normalize-package-data": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", @@ -2606,6 +3031,12 @@ } } }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -2666,6 +3097,16 @@ "has": "^1.0.3" } }, + "object.getownpropertydescriptors": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz", + "integrity": "sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1" + } + }, "object.values": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.1.tgz", @@ -2780,29 +3221,6 @@ "error-ex": "^1.2.0" } }, - "passport-ldapauth": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/passport-ldapauth/-/passport-ldapauth-2.1.3.tgz", - "integrity": "sha512-23n425UTasN6XhcXG0qQ0h0YrS/zfo8kNIEhSLfPsNpglhYhhQFfB1pmDc5RrH+Kiz5fKLkki5BpvkKHCwkixg==", - "requires": { - "@types/node": "^10.12.26", - "@types/passport": "^1.0.0", - "ldapauth-fork": "^4.2.0", - "passport-strategy": "^1.0.0" - }, - "dependencies": { - "@types/node": { - "version": "10.17.16", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.16.tgz", - "integrity": "sha512-A4283YSA1OmnIivcpy/4nN86YlnKRiQp8PYwI2KdPCONEBN093QTb0gCtERtkLyVNGKKIGazTZ2nAmVzQU51zA==" - } - } - }, - "passport-strategy": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz", - "integrity": "sha1-tVOaqPwiWj0a0XlHbd8ja0QPUuQ=" - }, "path-browserify": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.1.tgz", @@ -2840,6 +3258,12 @@ "pify": "^2.0.0" } }, + "pathval": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz", + "integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=", + "dev": true + }, "pbkdf2": { "version": "3.0.17", "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.17.tgz", @@ -2853,6 +3277,12 @@ "sha.js": "^2.4.8" } }, + "picomatch": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.1.tgz", + "integrity": "sha512-ISBaA8xQNmwELC7eOjqFKMESB2VIqt4PPDD0nsS95b/9dZXvVKOlz9keMSnoGGKcOHXfTvDD6WMaRoSc9UuhRA==", + "dev": true + }, "pify": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", @@ -2948,6 +3378,26 @@ "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", "dev": true }, + "promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/promise/-/promise-1.3.0.tgz", + "integrity": "sha1-5cyaTIJ45GZP/twBx9qEhCsEAXU=", + "requires": { + "is-promise": "~1" + }, + "dependencies": { + "is-promise": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-1.0.1.tgz", + "integrity": "sha1-MVc3YcBX4zwukaq56W2gjO++duU=" + } + } + }, + "promised-timeout": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/promised-timeout/-/promised-timeout-0.2.0.tgz", + "integrity": "sha1-Rer89e2UV+LmDvp3wshg1u8dgW4=" + }, "prop-types": { "version": "15.7.2", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", @@ -2991,6 +3441,14 @@ "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=", "dev": true }, + "quoted-printable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/quoted-printable/-/quoted-printable-1.0.1.tgz", + "integrity": "sha1-nuv16z0R7vAismT9LStrK7O4TMM=", + "requires": { + "utf8": "^2.1.0" + } + }, "radius": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/radius/-/radius-1.1.4.tgz", @@ -3131,6 +3589,15 @@ } } }, + "readdirp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.2.0.tgz", + "integrity": "sha512-crk4Qu3pmXwgxdSgGhgA/eXiJAPQiX4GMOZZMXnqKxHX7TaoL+3gQVo/WeuAiogr07DpnfjIMpXXa+PAIvwPGQ==", + "dev": true, + "requires": { + "picomatch": "^2.0.4" + } + }, "regenerator-runtime": { "version": "0.13.3", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.3.tgz", @@ -3261,8 +3728,7 @@ "safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "schema-utils": { "version": "2.6.4", @@ -3380,6 +3846,24 @@ } } }, + "smtp-channel": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/smtp-channel/-/smtp-channel-0.2.2.tgz", + "integrity": "sha1-W+sbYjmMUXP9fIWJ8P4LAX5bfuw=", + "requires": { + "line-buffer": "0.1.x", + "promised-timeout": "0.2.x" + } + }, + "smtp-client": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/smtp-client/-/smtp-client-0.3.1.tgz", + "integrity": "sha1-PbR29bH4azc7ebnPeuOzBdyfH6Q=", + "requires": { + "promised-timeout": "0.2.x", + "smtp-channel": "0.2.2" + } + }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -3641,6 +4125,15 @@ "integrity": "sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=", "dev": true }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, "ts-node": { "version": "8.6.2", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-8.6.2.tgz", @@ -3707,6 +4200,12 @@ "prelude-ls": "~1.1.2" } }, + "type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true + }, "type-fest": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", @@ -3760,6 +4259,26 @@ } } }, + "utf7": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/utf7/-/utf7-1.0.2.tgz", + "integrity": "sha1-lV9JCq5lO6IguUVqCod2wZk2CZE=", + "requires": { + "semver": "~5.3.0" + }, + "dependencies": { + "semver": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz", + "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=" + } + } + }, + "utf8": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/utf8/-/utf8-2.1.2.tgz", + "integrity": "sha1-H6DZJw6b6FDZsFAn9jUZv0ZFfZY=" + }, "util": { "version": "0.11.1", "resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz", @@ -3783,6 +4302,11 @@ "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", "dev": true }, + "uuencode": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/uuencode/-/uuencode-0.0.4.tgz", + "integrity": "sha1-yNUDcIhWY4eThas34zPH6OOwIYw=" + }, "v8-compile-cache": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.0.tgz", @@ -3847,6 +4371,48 @@ "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=" }, + "wide-align": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "dev": true, + "requires": { + "string-width": "^1.0.2 || 2" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, "word-wrap": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", @@ -3920,6 +4486,165 @@ "decamelize": "^1.2.0" } }, + "yargs-unparser": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-1.6.0.tgz", + "integrity": "sha512-W9tKgmSn0DpSatfri0nx52Joq5hVXgeLiqR/5G0sZNDoLZFOr/xjBUDcShCOGNsBnEMNo1KAMBkTej1Hm62HTw==", + "dev": true, + "requires": { + "flat": "^4.1.0", + "lodash": "^4.17.15", + "yargs": "^13.3.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "cliui": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "dev": true, + "requires": { + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + }, + "wrap-ansi": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" + } + }, + "yargs": { + "version": "13.3.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.0.tgz", + "integrity": "sha512-2eehun/8ALW8TLoIl7MVaRUrg+yCnenu8B4kBlRxj3GJGDKU1Og7sMXPNm1BYyM1DOJmTZ4YeN/Nwxv+8XJsUA==", + "dev": true, + "requires": { + "cliui": "^5.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^13.1.1" + } + }, + "yargs-parser": { + "version": "13.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.1.tgz", + "integrity": "sha512-oVAVsHz6uFrg3XQheFII8ESO2ssAf9luWuAd6Wexsu4F3OtIW0o8IribPXYrD4WC24LWtPrJlGy87y5udK+dxQ==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + } + } + }, "yn": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", diff --git a/package.json b/package.json index 2fefea1..efd7a11 100644 --- a/package.json +++ b/package.json @@ -1,34 +1,41 @@ { "name": "radius-server", - "description": "radius server for google LDAP and TTLT", + "description": "radius server for google LDAP and TTLS", "version": "0.0.1", "scripts": { "debug": "DEBUG=radius:* ../node/node dist/app.js", "start": "../node/node dist/app.js", "build": "tsc", "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", "create-certificate": "sh ./ssl/create.sh && sh ./ssl/sign.sh" }, "dependencies": { "debug": "^4.1.1", + "imap-simple": "^4.3.0", + "ldapauth-fork": "^4.3.1", "ldapjs": "^1.0.2", "md5": "^2.2.1", "native-duplexpair": "^1.0.0", "node-cache": "^5.1.0", - "passport-ldapauth": "^2.1.3", "radius": "~1.1.4", "ts-node": "^8.6.2", "type-cacheable": "^4.0.0", - "yargs": "~15.1.0" + "yargs": "~15.1.0", + "smtp-client": "^0.3.1" }, "license": "GPLv3", "devDependencies": { + "@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", - "@hokify/eslint-config": "^0.2.4", + "chai": "^4.2.0", "eslint": "^6.8.0", + "mocha": "^7.0.1", "prettier": "^1.19.1", "typescript": "^3.8.2" } diff --git a/src/app.ts b/src/app.ts index af62654..3592a73 100644 --- a/src/app.ts +++ b/src/app.ts @@ -1,24 +1,32 @@ -import { GoogleLDAPAuth } from './auth/google-ldap'; import { UDPServer } from './server/UDPServer'; import { RadiusService } from './radius/RadiusService'; import * as config from '../config'; +import { Authentication } from './auth'; +import { IAuthentication } from './types/Authentication'; console.log(`Listener Port: ${config.port || 1812}`); console.log(`RADIUS Secret: ${config.secret}`); console.log(`Auth Mode: ${config.authentication}`); -// const ldap = new LDAPAuth({url: 'ldap://ldap.google.com', base: 'dc=hokify,dc=com', uid: 'uid', tlsOptions}); - -const ldap = new GoogleLDAPAuth( - config.authenticationOptions.url, - config.authenticationOptions.base -); - -const server = new UDPServer(config.port); -const radiusService = new RadiusService(config.secret, ldap); - (async () => { + /* configure auth mechansim */ + let auth: IAuthentication; + try { + 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 radiusService = new RadiusService(config.secret, authentication); + server.on('message', async (msg, rinfo) => { const response = await radiusService.handleMessage(msg); diff --git a/src/auth.ts b/src/auth.ts new file mode 100644 index 0000000..aaf260c --- /dev/null +++ b/src/auth.ts @@ -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 { + 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; + } +} diff --git a/src/auth/google-ldap.ts b/src/auth/GoogleLDAPAuth.ts similarity index 76% rename from src/auth/google-ldap.ts rename to src/auth/GoogleLDAPAuth.ts index 3836ab9..6989ce9 100644 --- a/src/auth/google-ldap.ts +++ b/src/auth/GoogleLDAPAuth.ts @@ -1,26 +1,45 @@ -import * as NodeCache from 'node-cache'; - import { Client, createClient } from 'ldapjs'; import debug from 'debug'; +import * as tls from 'tls'; import { IAuthentication } from '../types/Authentication'; const usernameFields = ['posixUid', 'mail']; -const log = debug('radius:auth:ldap'); +const log = debug('radius:auth:google-ldap'); // TLS: // 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 { - cache = new NodeCache(); + private ldap: Client; + + private lastDNsFetch: Date; - ldap: Client; + private allValidDNsCache: { [key: string]: string }; - lastDNsFetch: Date; + private base: string; - allValidDNsCache: { [key: string]: string }; + constructor(config: IGoogleLDAPAuthOptions) { + this.base = config.base; - constructor(private url: string, private base: string, tlsOptions?) { - this.ldap = createClient({ url, tlsOptions }).on('error', error => { + 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); }); @@ -74,12 +93,6 @@ export class GoogleLDAPAuth implements IAuthentication { } 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(); cacheValidTime.setHours(cacheValidTime.getHours() - 12); @@ -100,14 +113,14 @@ export class GoogleLDAPAuth implements IAuthentication { if (!dnsFetched && !forceFetching) { 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; } const authResult: boolean = await new Promise((resolve, reject) => { this.ldap.bind(dn, password, (err, res) => { 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++; // wait 1 second to give the ldap error handler time to reconnect 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; } } diff --git a/src/auth/IMAPAuth.ts b/src/auth/IMAPAuth.ts new file mode 100644 index 0000000..2798a9e --- /dev/null +++ b/src/auth/IMAPAuth.ts @@ -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; + } +} diff --git a/src/auth/LDAPAuth.ts b/src/auth/LDAPAuth.ts new file mode 100644 index 0000000..cda9543 --- /dev/null +++ b/src/auth/LDAPAuth.ts @@ -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; + } +} diff --git a/src/auth/SMTPAuth.ts b/src/auth/SMTPAuth.ts new file mode 100644 index 0000000..fc24536 --- /dev/null +++ b/src/auth/SMTPAuth.ts @@ -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; + } +} diff --git a/src/auth/StaticAuth.ts b/src/auth/StaticAuth.ts new file mode 100644 index 0000000..49dbe16 --- /dev/null +++ b/src/auth/StaticAuth.ts @@ -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 + ); + } +} diff --git a/src/radius/handler/eapMethods/EAPTTLS.ts b/src/radius/handler/eapMethods/EAPTTLS.ts index 80cd768..180b443 100644 --- a/src/radius/handler/eapMethods/EAPTTLS.ts +++ b/src/radius/handler/eapMethods/EAPTTLS.ts @@ -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 */ import * as tls from 'tls'; import * as NodeCache from 'node-cache'; diff --git a/src/server/UDPServer.ts b/src/server/UDPServer.ts index 501aef2..2e0de07 100644 --- a/src/server/UDPServer.ts +++ b/src/server/UDPServer.ts @@ -38,7 +38,7 @@ export class UDPServer extends events.EventEmitter implements IServer { // retry up to MAX_RETRIES to send this message, // 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}`; if (expectAcknowledgment && retried < UDPServer.MAX_RETRIES) { this.timeout[identifierForRetry] = setTimeout(sendResponse, 600 * (retried + 1)); diff --git a/tsconfig.eslint.json b/tsconfig.eslint.json index 3ff9ab1..e80e947 100644 --- a/tsconfig.eslint.json +++ b/tsconfig.eslint.json @@ -1,11 +1,9 @@ { - "extends": "./tsconfig.json", - "include": [ - "src/**/*.ts", - "*.js" - ], - "compilerOptions": { - "allowJs": true, - "checkJs": true - } + "extends": "./tsconfig.json", + "include": ["src/**/*.ts", "*.js", "*.ts", "__tests__/**/*.ts"], + "exclude": ["node_modules"], + "compilerOptions": { + "allowJs": true, + "checkJs": true + } }