diff --git a/types/http-auth/index.d.ts b/types/http-auth/index.d.ts new file mode 100644 index 0000000000..1814a03e74 --- /dev/null +++ b/types/http-auth/index.d.ts @@ -0,0 +1,153 @@ +// Type definitions for http-auth 4.1 +// Project: https://github.com/http-auth/http-auth +// Definitions by: nokazn +// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped +// TypeScript Version: 4.0 + +/// + +import * as http from 'http'; +import { EventEmitter } from 'events'; + +type Qop = 'auth' | 'none'; +type Algorithm = 'MD5' | 'MD5-sess'; + +type CheckedRequestListener = (req: http.IncomingMessage & { user?: string }, res: http.ServerResponse) => void; +type BasicChecker = (username: string, password: string, callback: (isAuthorized: boolean) => void) => void; +type DigestChecker = (username: string, callback: (hash?: string) => void) => void; + +interface BasicResult { + user?: string; + pass?: T; +} +type DigestResult = BasicResult & { + stale?: true; +}; +type ResultEmitter = (result: BasicResult | DigestResult | Error) => void; + +type Nonce = [string, number, number]; + +interface ClientOptions { + username: string; + realm: string; + nonce: string; + uri: string; + algorithm: Algorithm; + response: string; + qop?: 'auth'; + nc?: string; + cnonce?: string; +} + +interface BasicOptions { + /** + * Authentication realm, by default it is 'users'. + * @default 'users' + */ + realm?: string; + + /** + * File where user details are stored. + * - Line format is {user:pass} or {user:passHash} for basic access. + * - Line format is {user:realm:passHash} for digest access. + */ + file?: string; + + /** + * Message for failed authentication 401 page. + * @default '401 Unauthorized' + */ + msg401?: string; + + /** + * Message for failed authentication 407 page. + * @default '407 Proxy authentication required + */ + msg407?: string; + + /** + * Content type for failed authentication page. + * @default 'text/plain' + */ + contentType?: string; + + /** + * Set this to true, if you want to use it with http-proxy (https://github.com/http-party/node-http-proxy). + * @default false + */ + proxy?: boolean; + + /** + * Set this to true, if you don't want req.user to be filled with authentication info. + */ + skipUser?: boolean; +} + +type DigestOptions = BasicOptions & { + /** + * Quality of protection that is used only for digest access authentication + * - 'auth' is set by default. + * - 'none' this option is disabling protection. + * @default 'auth + */ + qop?: Qop; + + /** + * Algorithm that will be used only for digest access authentication. + * 'MD5' or 'MD5-sess' can be set. + * @default 'MD5' + */ + algorithm?: Algorithm; +}; + +declare abstract class Base extends EventEmitter { + constructor(options: BasicOptions, checker?: BasicChecker | DigestChecker); + + on(event: 'success', callback: (result: BasicResult | DigestResult) => void): this; + on(event: 'fail', callback: (result: BasicResult | DigestResult) => void): this; + on(event: 'error', callback: (err: Error) => void): this; + + check(callback?: CheckedRequestListener): CheckedRequestListener; + abstract processLine(userLine: string): void; + abstract parseAuthorization(header: string): string | ClientOptions | undefined; + abstract findUser( + req: http.IncomingMessage, + hashOrClientOptions: string | ClientOptions, + callback: ResultEmitter, + ): void; + abstract generateHeader(result?: DigestResult): string; + private ask(res: http.ServerResponse, result: BasicResult | DigestResult): void; + private isAuthenticated(req: http.IncomingMessage, callback: ResultEmitter): void; + private loadUsers(): void; +} + +declare class Basic extends Base { + constructor(options: BasicOptions, checker?: BasicChecker); + + private validate(hash: string, password: string): boolean; + + processLine(userLine: string): void; + generateHeader(): string; + parseAuthorization(header: string): string | undefined; + findUser(req: http.IncomingMessage, hash: string, callback: ResultEmitter): void; +} + +declare class Digest extends Base { + constructor(options: DigestOptions, checker?: DigestChecker); + + private nonces: Nonce[]; + private validate(ha2: string, clientOptions: ClientOptions, hash: string): boolean; + private removeNonces(nonces: Nonce[]): void; + private validateNonce(nonce: string, qop: Qop, nc: string): boolean; + private askNonce(): string; + + processLine(userLine: string): void; + generateHeader(result: DigestResult): string; + parseAuthorization(header: string): string | undefined; + findUser(req: http.IncomingMessage, clientOptions: ClientOptions, callback: ResultEmitter): void; +} + +declare function basic(options: BasicOptions, checker?: BasicChecker): Basic; +declare function digest(options: DigestOptions, checker?: DigestChecker): Digest; + +export { basic, digest, BasicOptions, DigestOptions, BasicChecker, DigestChecker }; diff --git a/types/http-auth/test/http-auth-tests.basic.ts b/types/http-auth/test/http-auth-tests.basic.ts new file mode 100644 index 0000000000..45b5713cb2 --- /dev/null +++ b/types/http-auth/test/http-auth-tests.basic.ts @@ -0,0 +1,20 @@ +import * as http from 'http'; +import * as auth from 'http-auth'; + +const basic = auth.basic({ + realm: 'Simon Area.', + file: __dirname + '/../data/users.htpasswd', // gevorg:gpass, Sarah:testpass +}); + +const server = http.createServer(); +const a = auth.digest({}); +a.check(); +// Creating new HTTP server. +http.createServer( + basic.check((req, res) => { + res.end(`Welcome to private area - ${req.user}!`); + }), +).listen(1337, () => { + // Log URL. + console.log('Server running at http://127.0.0.1:1337/'); +}); diff --git a/types/http-auth/test/http-auth-tests.basic_nofile.ts b/types/http-auth/test/http-auth-tests.basic_nofile.ts new file mode 100644 index 0000000000..0a493033ed --- /dev/null +++ b/types/http-auth/test/http-auth-tests.basic_nofile.ts @@ -0,0 +1,22 @@ +import * as http from 'http'; +import * as auth from 'http-auth'; + +const basic = auth.basic( + { + realm: 'Simon Area.', + }, + (username, password, callback) => { + // Custom authentication method. + callback(username === 'Tina' && password === 'Bullock'); + }, +); + +// Creating new HTTP server. +http.createServer( + basic.check((req, res) => { + res.end(`Welcome to private area - ${req.user}!`); + }), +).listen(1337, () => { + // Log URL. + console.log('Server running at http://127.0.0.1:1337/'); +}); diff --git a/types/http-auth/test/http-auth-tests.digest.ts b/types/http-auth/test/http-auth-tests.digest.ts new file mode 100644 index 0000000000..cfd55fb176 --- /dev/null +++ b/types/http-auth/test/http-auth-tests.digest.ts @@ -0,0 +1,35 @@ +import * as http from 'http'; +import * as auth from 'http-auth'; +import * as crypto from 'crypto'; + +const md5 = (input: string) => { + const hash = crypto.createHash('MD5'); + hash.update(input); + return hash.digest('hex'); +}; + +const digest = auth.digest( + { + realm: 'Simon Area.', + }, + (username, callback) => { + // Expecting md5(username:realm:password) in callback. + if (username === 'simon') { + callback(md5('simon:Simon Area.:smart')); + } else if (username === 'tigran') { + callback(md5('tigran:Simon Area.:great')); + } else { + callback(); + } + }, +); + +// Creating new HTTP server. +http.createServer( + digest.check((req, res) => { + res.end(`Welcome to private area - ${req.user}!`); + }), +).listen(1337, () => { + // Log URL. + console.log('Server running at http://127.0.0.1:1337/'); +}); diff --git a/types/http-auth/test/http-auth-tests.digest_nofile.ts b/types/http-auth/test/http-auth-tests.digest_nofile.ts new file mode 100644 index 0000000000..e08473e857 --- /dev/null +++ b/types/http-auth/test/http-auth-tests.digest_nofile.ts @@ -0,0 +1,17 @@ +import * as http from 'http'; +import * as auth from 'http-auth'; + +const digest = auth.digest({ + realm: 'Simon Area.', + file: __dirname + '/../data/users.htdigest', // vivi:anna, sona:testpass +}); + +// Creating new HTTP server. +http.createServer( + digest.check((req, res) => { + res.end(`Welcome to private area - ${req.user}!`); + }), +).listen(1337, () => { + // Log URL. + console.log('Server running at http://127.0.0.1:1337/'); +}); diff --git a/types/http-auth/test/http-auth-tests.events.ts b/types/http-auth/test/http-auth-tests.events.ts new file mode 100644 index 0000000000..923e8ebd67 --- /dev/null +++ b/types/http-auth/test/http-auth-tests.events.ts @@ -0,0 +1,30 @@ +import * as http from 'http'; +import * as auth from 'http-auth'; + +const basic = auth.basic({ + realm: 'Simon Area.', + file: __dirname + '/../data/users.htpasswd', // gevorg:gpass, Sarah:testpass +}); + +// Adding event listeners. +basic.on('success', result => { + console.log(`User authenticated: ${result.user}`); +}); + +basic.on('fail', result => { + console.log(`User authentication failed: ${result.user}`); +}); + +basic.on('error', error => { + console.log(`Authentication error: ${error.message}`); +}); + +// Creating new HTTP server. +http.createServer( + basic.check((req, res) => { + res.end(`Welcome to private area - ${req.user}!`); + }), +).listen(1337, () => { + // Log URL. + console.log('Server running at http://127.0.0.1:1337/'); +}); diff --git a/types/http-auth/test/http-auth-tests.https.ts b/types/http-auth/test/http-auth-tests.https.ts new file mode 100644 index 0000000000..b8cc5f96cc --- /dev/null +++ b/types/http-auth/test/http-auth-tests.https.ts @@ -0,0 +1,27 @@ +import * as https from 'https'; +import * as fs from 'fs'; +import * as auth from 'http-auth'; + +const basic = auth.basic({ + realm: 'Simon Area.', + file: __dirname + '/../data/users.htpasswd', // gevorg:gpass, Sarah:testpass +}); + +// HTTPS server options. +const options = { + key: fs.readFileSync(__dirname + '/../data/server.key'), + cert: fs.readFileSync(__dirname + '/../data/server.crt'), +}; + +// Starting server. +https + .createServer( + options, + basic.check((req, res) => { + res.end(`Welcome to private area - ${req.user}!`); + }), + ) + .listen(1337, () => { + // Log URL. + console.log('Server running at https://127.0.0.1:1337/'); + }); diff --git a/types/http-auth/test/http-auth-tests.proxy.ts b/types/http-auth/test/http-auth-tests.proxy.ts new file mode 100644 index 0000000000..e209b7a6f9 --- /dev/null +++ b/types/http-auth/test/http-auth-tests.proxy.ts @@ -0,0 +1,29 @@ +import * as http from 'http'; +import * as fs from 'fs'; +import * as auth from 'http-auth'; +import * as httpProxy from 'http-proxy'; + +const basic = auth.basic({ + realm: 'Simon Area.', + file: __dirname + '/../data/users.htpasswd', // gevorg:gpass, Sarah:testpass + proxy: true, +}); + +// Create your proxy server. +const proxy = httpProxy.createProxyServer({}); +http.createServer( + basic.check((req, res) => { + proxy.web(req, res, { target: 'http://127.0.0.1:1338' }); + }), +).listen(1337); + +// Create your target server. +http.createServer((req, res) => { + res.end('Request successfully proxied!'); +}).listen(1338, () => { + // Log URL. + console.log('Server running at http://127.0.0.1:1338/'); +}); + +// You can test proxy authentication using curl. +// $ curl -x 127.0.0.1:1337 127.0.0.1:1337 -U gevorg diff --git a/types/http-auth/tsconfig.json b/types/http-auth/tsconfig.json new file mode 100644 index 0000000000..ca73e1f23d --- /dev/null +++ b/types/http-auth/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "module": "commonjs", + "lib": ["es6"], + "noImplicitAny": true, + "noImplicitThis": true, + "strictFunctionTypes": true, + "strictNullChecks": true, + "baseUrl": "../", + "typeRoots": ["../"], + "types": [], + "noEmit": true, + "forceConsistentCasingInFileNames": true + }, + "files": [ + "index.d.ts", + "test/http-auth-tests.basic.ts", + "test/http-auth-tests.basic_nofile.ts", + "test/http-auth-tests.digest.ts", + "test/http-auth-tests.digest_nofile.ts", + "test/http-auth-tests.events.ts", + "test/http-auth-tests.https.ts", + "test/http-auth-tests.proxy.ts" + ] +} diff --git a/types/http-auth/tslint.json b/types/http-auth/tslint.json new file mode 100644 index 0000000000..3db14f85ea --- /dev/null +++ b/types/http-auth/tslint.json @@ -0,0 +1 @@ +{ "extends": "dtslint/dt.json" }