This commit is contained in:
		| @@ -1 +1,2 @@ | ||||
| /dist/ | ||||
| *.d.ts | ||||
|   | ||||
							
								
								
									
										22
									
								
								.eslintrc.js
									
									
									
									
									
								
							
							
						
						
									
										22
									
								
								.eslintrc.js
									
									
									
									
									
								
							| @@ -1,22 +1,24 @@ | ||||
| /* | ||||
|  * Copyright (C) Sapphirecode - All Rights Reserved | ||||
|  * This file is part of auth-server-helper which is released under MIT. | ||||
|  * This file is part of Auth-Server-Helper which is released under MIT. | ||||
|  * See file 'LICENSE' for full license details. | ||||
|  * Created by Timo Hocker <timo@scode.ovh>, May 2020 | ||||
|  * Created by Timo Hocker <timo@scode.ovh>, December 2020 | ||||
|  */ | ||||
|  | ||||
| 'use strict'; | ||||
|  | ||||
| module.exports = { | ||||
|   env: { | ||||
|     commonjs: true, | ||||
|     es6:      true, | ||||
|     node:     true | ||||
|     es6: true, | ||||
|     node: true | ||||
|   }, | ||||
|   extends: [ '@sapphirecode' ], | ||||
|   extends: [ | ||||
|     '@sapphirecode' | ||||
|   ], | ||||
|   globals: { | ||||
|     Atomics:           'readonly', | ||||
|     Atomics: 'readonly', | ||||
|     SharedArrayBuffer: 'readonly' | ||||
|   }, | ||||
|   parserOptions: { ecmaVersion: 2018 } | ||||
| }; | ||||
|   parserOptions: { | ||||
|     ecmaVersion: 2018 | ||||
|   } | ||||
| } | ||||
|   | ||||
							
								
								
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -2,6 +2,3 @@ | ||||
| /dist/ | ||||
| /.nyc_output/ | ||||
| /coverage/ | ||||
| /db.sqlite | ||||
| # stryker temp files | ||||
| .stryker-tmp | ||||
|   | ||||
| @@ -4,5 +4,5 @@ | ||||
|   "author": "Timo Hocker", | ||||
|   "company": "Sapphirecode", | ||||
|   "email": "timo@scode.ovh", | ||||
|   "software": "auth-server-helper" | ||||
|   "software": "Auth-Server-Helper" | ||||
| } | ||||
| @@ -1,9 +0,0 @@ | ||||
| # Changelog | ||||
|  | ||||
| ## 1.1.0 | ||||
|  | ||||
| add user_id to res.connection, so request handlers can access the current user | ||||
|  | ||||
| ## 1.0.0 | ||||
|  | ||||
| initial release | ||||
							
								
								
									
										53
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										53
									
								
								README.md
									
									
									
									
									
								
							| @@ -1,63 +1,22 @@ | ||||
| # @sapphirecode/auth-server-helper | ||||
| # auth-server-helper | ||||
|  | ||||
| version: 1.1.x | ||||
| version: 0.0.0 | ||||
|  | ||||
| authentication middleware for express | ||||
| undefined | ||||
|  | ||||
| ## Installation | ||||
|  | ||||
| npm: | ||||
|  | ||||
| > npm i --save @sapphirecode/auth-server-helper | ||||
| > npm i --save auth-server-helper | ||||
|  | ||||
| yarn: | ||||
|  | ||||
| > yarn add @sapphirecode/auth-server-helper | ||||
| > yarn add auth-server-helper | ||||
|  | ||||
| ## Usage | ||||
|  | ||||
| ```js | ||||
| const auth = require('@sapphirecode/auth-server-helper'); | ||||
| const password_helper = require('@sapphirecode/password_helper'); | ||||
|  | ||||
| const users = { | ||||
|   foo: { | ||||
|     id: 0 | ||||
|     password: await password_helper.hash('bar'), | ||||
|     salt: '123' | ||||
|   } | ||||
| } | ||||
|  | ||||
| // add cookieParser to allow session management via cookies | ||||
| app.use(cookieParser()); | ||||
|  | ||||
| // the middleware needs a function to determine user data | ||||
| // this function can also return a promise | ||||
| app.use(auth((user_name) => { | ||||
|   if (!users[user_name]) | ||||
|     return null; | ||||
|   return users[user_name]; | ||||
| })); | ||||
|  | ||||
| ``` | ||||
|  | ||||
| when a client logs in, it will set a header called 'session' that the client can | ||||
| use to authorize the following requests. it also sets a cookie to make | ||||
| requesting from the client more simple. (cookie parser is needed to make | ||||
| authentication with cookies possible) | ||||
|  | ||||
| the id of the logged in user will be available in `req.connection.user_id` in | ||||
| all of the following request handlers. | ||||
|  | ||||
| ### Excluding routes | ||||
|  | ||||
| exceptions to the auth module can be added by adding an array of regular | ||||
| expressions a specific method can also be filtered for by giving an object | ||||
| instead of a plain regular expression. | ||||
|  | ||||
| ```js | ||||
| auth(..., [/no-auth/, {regex: '/no-auth-post/', method: 'POST'}]); | ||||
| ``` | ||||
| TODO: Add usage | ||||
|  | ||||
| ## License | ||||
|  | ||||
|   | ||||
							
								
								
									
										206
									
								
								index.js
									
									
									
									
									
								
							
							
						
						
									
										206
									
								
								index.js
									
									
									
									
									
								
							| @@ -1,206 +0,0 @@ | ||||
| /* | ||||
|  * Copyright (C) Sapphirecode - All Rights Reserved | ||||
|  * This file is part of auth-server-helper which is released under MIT. | ||||
|  * See file 'LICENSE' for full license details. | ||||
|  * Created by Timo Hocker <timo@scode.ovh>, May 2020 | ||||
|  */ | ||||
|  | ||||
| // @ts-nocheck | ||||
| /* eslint-disable no-magic-numbers */ | ||||
|  | ||||
| 'use strict'; | ||||
|  | ||||
| const password_helper = require ('@sapphirecode/password-helper'); | ||||
| const crypto = require ('@sapphirecode/crypto-helper'); | ||||
| const consts = require ('@sapphirecode/consts'); | ||||
|  | ||||
| const me = {}; | ||||
|  | ||||
| /** | ||||
|  * initializes the module | ||||
|  * | ||||
|  * @param {Function<Promise|object>} get_user | ||||
|  * function that returns {id:number, password:string, salt:string} | ||||
|  * for a given user identifier | ||||
|  * @param {Array<RegExp>} ignore_paths array of regex to skip auth | ||||
|  * @returns {Function} request handler | ||||
|  */ | ||||
| function init (get_user, ignore_paths = []) { | ||||
|   me.get_user = get_user; | ||||
|   me.session_timeout_milliseconds = 300000; | ||||
|   me.ignore_paths = ignore_paths; | ||||
|   me.jwt_secret = crypto.create_salt (); | ||||
|   me.app_id = crypto.create_salt (); | ||||
|   return request_handler; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * tries to authenticate a user | ||||
|  * | ||||
|  * @param {string} user name or email of the given user | ||||
|  * @param {string} password hashed password | ||||
|  * @param {any} req request object | ||||
|  * @returns {Promise<string>} session key if successful | ||||
|  */ | ||||
| async function authenticate (user, password, req) { | ||||
|   const user_entry | ||||
|     = await new Promise ((res) => res (me.get_user (user))); | ||||
|  | ||||
|   if (!user_entry) | ||||
|     return null; | ||||
|  | ||||
|   if (!await password_helper.verify (user_entry.password, password)) | ||||
|     return null; | ||||
|  | ||||
|   req.connection.user_id = user_entry.id; | ||||
|  | ||||
|   const session_key = crypto.sign_object ( | ||||
|     { id: user_entry.id }, | ||||
|     me.jwt_secret | ||||
|   ); | ||||
|  | ||||
|   return session_key; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * gets the correct salt for a given user | ||||
|  * | ||||
|  * @param {string} user user name or email to query | ||||
|  */ | ||||
| async function salt (user) { | ||||
|   const user_entry | ||||
|   = await new Promise ((res) => res (me.get_user (user))); | ||||
|   if (!user_entry) | ||||
|     return null; | ||||
|  | ||||
|   return user_entry.salt; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * block if no auth header found | ||||
|  * | ||||
|  * @param {string} session session key | ||||
|  * @param {string} user user name | ||||
|  * @param {any} res response object | ||||
|  * @returns {boolean} true if handler blocked request | ||||
|  */ | ||||
| function request_handler_block (session, user, res) { | ||||
|   if (typeof session === 'undefined' && typeof user === 'undefined') { | ||||
|     res.status (consts.http.status_unauthorized); | ||||
|     res.end (); | ||||
|     return true; | ||||
|   } | ||||
|   return false; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * handle authentication | ||||
|  * | ||||
|  * @param {string} session session key | ||||
|  * @param {string} user user name | ||||
|  * @param {string} key user hash | ||||
|  * @param {any} req request object | ||||
|  * @param {any} res response object | ||||
|  * @param {any} next next handler | ||||
|  * @returns {Promise<boolean>} true if handler authenticated | ||||
|  */ | ||||
| // eslint-disable-next-line max-len, max-params | ||||
| async function request_handler_authenticate (session, user, key, req, res, next) { | ||||
|   if (typeof session === 'undefined' && typeof user !== 'undefined') { | ||||
|     if (typeof key === 'undefined') { | ||||
|       const user_salt = await salt (user); | ||||
|       res.status ( | ||||
|         user_salt === null | ||||
|           ? consts.http.status_forbidden | ||||
|           : consts.http.status_ok | ||||
|       ); | ||||
|       res.end (user_salt); | ||||
|       return true; | ||||
|     } | ||||
|  | ||||
|     const session_key = await authenticate (user, key, req); | ||||
|  | ||||
|     res.status ( | ||||
|       session_key === null | ||||
|         ? consts.http.status_forbidden | ||||
|         : consts.http.status_ok | ||||
|     ) | ||||
|       .cookie (me.app_id, session_key, { maxAge: 900000, httpOnly: true }) | ||||
|       .end (session_key); | ||||
|     return true; | ||||
|   } | ||||
|   try { | ||||
|     const jwt = crypto.verify_signature ( | ||||
|       session, | ||||
|       me.jwt_secret, | ||||
|       me.session_timeout_milliseconds | ||||
|     ); | ||||
|     res.locals.user_id = jwt.id; | ||||
|     const new_user_token = crypto.sign_object ( | ||||
|       { id: jwt.id }, | ||||
|       me.jwt_secret | ||||
|     ); | ||||
|     req.connection.user_id = jwt.id; | ||||
|     res.cookie ( | ||||
|       me.app_id, | ||||
|       new_user_token, | ||||
|       { maxAge: 900000, httpOnly: true } | ||||
|     ) | ||||
|       .header ('session', new_user_token); | ||||
|     next (); | ||||
|     return true; | ||||
|   } | ||||
|   catch (err) { | ||||
|     return false; | ||||
|   } | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * check if a filter matches a request | ||||
|  * | ||||
|  * @param {any} req request | ||||
|  * @param {any} filter filter | ||||
|  * @returns {boolean} true if filter matches | ||||
|  */ | ||||
| function filter_matches (req, filter) { | ||||
|   if (filter instanceof RegExp && filter.test (req.url)) | ||||
|     return true; | ||||
|   return req.method === filter.method | ||||
|     && filter.regex | ||||
|     && filter.regex.test (req.url); | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * handles http requests | ||||
|  * | ||||
|  * @param {any} req request | ||||
|  * @param {any} res response | ||||
|  * @param {any} next next handler | ||||
|  */ | ||||
| async function request_handler (req, res, next) { | ||||
|   if (Array.isArray (me.ignore_paths)) { | ||||
|     for (const ignore of me.ignore_paths) { | ||||
|       if (filter_matches (req, ignore)) { | ||||
|         next (); | ||||
|         return; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   const { user, key, session: header_session } = req.headers; | ||||
|   const cookie_session = typeof req.cookies === 'undefined' | ||||
|     ? null | ||||
|     : req.cookies[me.app_id]; | ||||
|  | ||||
|   const session = cookie_session || header_session; | ||||
|  | ||||
|   if (request_handler_block (session, user, res)) | ||||
|     return; | ||||
|   if (await request_handler_authenticate (session, user, key, req, res, next)) | ||||
|     return; | ||||
|  | ||||
|   res.status (consts.http.status_forbidden); | ||||
|   res.end (); | ||||
| } | ||||
|  | ||||
| module.exports = init; | ||||
							
								
								
									
										24
									
								
								lib/.eslintrc.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								lib/.eslintrc.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,24 @@ | ||||
| /* | ||||
|  * Copyright (C) Sapphirecode - All Rights Reserved | ||||
|  * This file is part of Auth-Server-Helper which is released under MIT. | ||||
|  * See file 'LICENSE' for full license details. | ||||
|  * Created by Timo Hocker <timo@scode.ovh>, December 2020 | ||||
|  */ | ||||
|  | ||||
| module.exports = { | ||||
|   env: { | ||||
|     commonjs: true, | ||||
|     es6: true, | ||||
|     node: true | ||||
|   }, | ||||
|   extends: [ | ||||
|     '@sapphirecode/eslint-config-ts' | ||||
|   ], | ||||
|   globals: { | ||||
|     Atomics: 'readonly', | ||||
|     SharedArrayBuffer: 'readonly' | ||||
|   }, | ||||
|   parserOptions: { | ||||
|     ecmaVersion: 2018 | ||||
|   } | ||||
| } | ||||
| @@ -1,55 +0,0 @@ | ||||
| /* | ||||
|  * Copyright (C) Sapphirecode - All Rights Reserved | ||||
|  * This file is part of auth-server-helper which is released under MIT. | ||||
|  * See file 'LICENSE' for full license details. | ||||
|  * Created by Timo Hocker <timo@scode.ovh>, May 2020 | ||||
|  */ | ||||
|  | ||||
| /* eslint-disable no-magic-numbers */ | ||||
| // @ts-nocheck | ||||
| 'use strict'; | ||||
|  | ||||
| const express = require ('express'); | ||||
| const auth = require ('./index'); | ||||
| const consts = require ('@sapphirecode/consts'); | ||||
| const crypto = require ('@sapphirecode/crypto-helper'); | ||||
| const password_helper = require ('@sapphirecode/password-helper'); | ||||
|  | ||||
|  | ||||
| /** | ||||
|  * start the server | ||||
|  */ | ||||
| async function start_server () { | ||||
|   const app = express (); | ||||
|  | ||||
|   const id = 69; | ||||
|   const name = 'testuser'; | ||||
|   const salt = crypto.create_salt (); | ||||
|   const password = await password_helper.hash ( | ||||
|     crypto.hash_sha512 ('foo', salt) | ||||
|   ); | ||||
|   const user = { id, name, salt, password }; | ||||
|  | ||||
|   app.use (auth ((user_name) => { | ||||
|     if (user.name === user_name) | ||||
|       return user; | ||||
|  | ||||
|     return null; | ||||
|   }, [ | ||||
|     /noauthreg/u, | ||||
|     { method: 'POST', regex: /noauthobj/u } | ||||
|   ])); | ||||
|  | ||||
|   app.use ((req, res) => { | ||||
|     res.status (consts.http.status_ok) | ||||
|       .end (`foo:${req.connection.user_id}`); | ||||
|   }); | ||||
|  | ||||
|   return new Promise ((res) => { | ||||
|     const listener = app.listen (0, () => { | ||||
|       res (listener.address ().port); | ||||
|     }); | ||||
|   }); | ||||
| } | ||||
|  | ||||
| module.exports = { start_server }; | ||||
							
								
								
									
										39
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										39
									
								
								package.json
									
									
									
									
									
								
							| @@ -1,44 +1,33 @@ | ||||
| { | ||||
|   "name": "@sapphirecode/auth-server-helper", | ||||
|   "version": "1.1.4", | ||||
|   "version": "2.0.0", | ||||
|   "main": "index.js", | ||||
|   "author": { | ||||
|     "name": "Timo Hocker", | ||||
|     "email": "timo@scode.ovh" | ||||
|   }, | ||||
|   "bugs": "https://redmine.scode.ovh/projects/auth-server-helper", | ||||
|   "license": "MIT", | ||||
|   "description": "authentication middleware for express", | ||||
|   "repository": { | ||||
|     "type": "git", | ||||
|     "directory": "https://git.scode.ovh:timo/auth-server-helper.git" | ||||
|   }, | ||||
|   "license": "MIT", | ||||
|   "devDependencies": { | ||||
|     "@sapphirecode/auth-client-helper": "^1.0.45", | ||||
|     "@sapphirecode/eslint-config": "^2.1.4", | ||||
|     "@stryker-mutator/core": "^4.0.0", | ||||
|     "@stryker-mutator/jasmine-runner": "^4.0.0", | ||||
|     "@types/jasmine": "^3.5.14", | ||||
|     "eslint": "^7.0.0", | ||||
|     "express": "^4.17.1", | ||||
|     "jasmine": "^3.6.1", | ||||
|     "node-fetch": "^2.6.0", | ||||
|     "nyc": "^15.0.1" | ||||
|     "@sapphirecode/eslint-config-ts": "^1.1.27", | ||||
|     "@types/jasmine": "^3.6.2", | ||||
|     "jasmine": "^3.6.3", | ||||
|     "jasmine-ts": "^0.3.0", | ||||
|     "nyc": "^15.1.0", | ||||
|     "ts-node": "^8.0.0", | ||||
|     "typescript": "^4.1.2" | ||||
|   }, | ||||
|   "scripts": { | ||||
|     "lint": "eslint . --ext .js,.jsx,.ts,.tsx,.vue,.mjs", | ||||
|     "test": "nyc jasmine --config=\"jasmine.json\"", | ||||
|     "test": "nyc jasmine-ts --config=\"jasmine.json\"", | ||||
|     "mutate": "stryker run", | ||||
|     "compile": "tsc --allowJs --declaration --emitDeclarationOnly index.js" | ||||
|   }, | ||||
|   "dependencies": { | ||||
|     "@sapphirecode/consts": "^1.1.18", | ||||
|     "@sapphirecode/crypto-helper": "^1.1.44", | ||||
|     "@sapphirecode/password-helper": "^1.0.35" | ||||
|     "compile": "tsc" | ||||
|   }, | ||||
|   "files": [ | ||||
|     "LICENSE", | ||||
|     "index.js" | ||||
|     "*.js", | ||||
|     "*.ts", | ||||
|     "*.d.ts" | ||||
|   ], | ||||
|   "keywords": [ | ||||
|     "authentication", | ||||
|   | ||||
| @@ -1,23 +0,0 @@ | ||||
| /* | ||||
|  * Copyright (C) Sapphirecode - All Rights Reserved | ||||
|  * This file is part of auth-server-helper which is released under MIT. | ||||
|  * See file 'LICENSE' for full license details. | ||||
|  * Created by Timo Hocker <timo@scode.ovh>, May 2020 | ||||
|  */ | ||||
|  | ||||
| 'use strict'; | ||||
|  | ||||
| /** | ||||
|  * @type {import('@stryker-mutator/api/core').StrykerOptions} | ||||
|  */ | ||||
| module.exports = { | ||||
|   packageManager:   'yarn', | ||||
|   reporters:        [ | ||||
|     'clear-text', | ||||
|     'progress' | ||||
|   ], | ||||
|   testRunner:        'jasmine', | ||||
|   jasmineConfigFile: 'jasmine.json', | ||||
|   coverageAnalysis:  'perTest', | ||||
|   mutate:            [ 'index.js' ] | ||||
| }; | ||||
| @@ -1,123 +0,0 @@ | ||||
| /* | ||||
|  * Copyright (C) Sapphirecode - All Rights Reserved | ||||
|  * This file is part of auth-server-helper which is released under MIT. | ||||
|  * See file 'LICENSE' for full license details. | ||||
|  * Created by Timo Hocker <timo@scode.ovh>, May 2020 | ||||
|  */ | ||||
|  | ||||
| // @ts-nocheck | ||||
| /* eslint-disable no-undef */ | ||||
| 'use strict'; | ||||
|  | ||||
| const mock_server = require ('../../mock_server'); | ||||
| const client = require ('@sapphirecode/auth-client-helper'); | ||||
| const consts = require ('@sapphirecode/consts'); | ||||
| const fetch = require ('node-fetch'); | ||||
|  | ||||
| let port = 0; | ||||
|  | ||||
| // eslint-disable-next-line max-lines-per-function | ||||
| describe ('server-helper', () => { | ||||
|   beforeAll (async () => { | ||||
|     port = await mock_server.start_server (); | ||||
|   }); | ||||
|  | ||||
|   it ('should login', async () => { | ||||
|     const session = await client.login ( | ||||
|       'testuser', | ||||
|       'foo', | ||||
|       `http://localhost:${port}` | ||||
|     ); | ||||
|     expect (typeof session) | ||||
|       .toEqual ('string'); | ||||
|  | ||||
|     const resp = await fetch ( | ||||
|       `http://localhost:${port}`, | ||||
|       { headers: { session } } | ||||
|     ); | ||||
|  | ||||
|     expect (resp.status) | ||||
|       .toEqual (consts.http.status_ok); | ||||
|     expect (await resp.text ()) | ||||
|       .toEqual ('foo:69'); | ||||
|   }); | ||||
|  | ||||
|   it ('should allow access to excluded paths', async () => { | ||||
|     const resp = await fetch (`http://localhost:${port}/noauthreg`); | ||||
|  | ||||
|     expect (resp.status) | ||||
|       .toEqual (consts.http.status_ok); | ||||
|     expect (await resp.text ()) | ||||
|       .toEqual ('foo:undefined'); | ||||
|   }); | ||||
|  | ||||
|   it ( | ||||
|     'should allow access to excluded paths with correct method', | ||||
|     async () => { | ||||
|       const resp = await fetch ( | ||||
|         `http://localhost:${port}/noauthobj`, | ||||
|         { method: 'POST' } | ||||
|       ); | ||||
|  | ||||
|       expect (resp.status) | ||||
|         .toEqual (consts.http.status_ok); | ||||
|       expect (await resp.text ()) | ||||
|         .toEqual ('foo:undefined'); | ||||
|     } | ||||
|   ); | ||||
|  | ||||
|   it ('should reject access to excluded paths with wrong method', async () => { | ||||
|     const resp = await fetch ( | ||||
|       `http://localhost:${port}/noauthobj` | ||||
|     ); | ||||
|  | ||||
|     expect (resp.status) | ||||
|       .toEqual (consts.http.status_unauthorized); | ||||
|   }); | ||||
|  | ||||
|   it ('should reject invalid user', async () => { | ||||
|     await expectAsync (client.login ( | ||||
|       'foo', | ||||
|       'foo', | ||||
|       `http://localhost:${port}` | ||||
|     )) | ||||
|       .toBeRejectedWithError ('user or password invalid'); | ||||
|   }); | ||||
|  | ||||
|   it ('should reject and recover', async () => { | ||||
|     await expectAsync (client.login ( | ||||
|       'testuser', | ||||
|       'bar', | ||||
|       `http://localhost:${port}` | ||||
|     )) | ||||
|       .toBeRejectedWithError ('user or password invalid'); | ||||
|  | ||||
|     const session = await client.login ( | ||||
|       'testuser', | ||||
|       'foo', | ||||
|       `http://localhost:${port}` | ||||
|     ); | ||||
|     expect (typeof session) | ||||
|       .toEqual ('string'); | ||||
|  | ||||
|     const resp = await fetch ( | ||||
|       `http://localhost:${port}`, | ||||
|       { headers: { session } } | ||||
|     ); | ||||
|  | ||||
|     expect (resp.status) | ||||
|       .toEqual (consts.http.status_ok); | ||||
|     expect (await resp.text ()) | ||||
|       .toEqual ('foo:69'); | ||||
|   }); | ||||
|  | ||||
|   it ('should reject invalid password', async () => { | ||||
|     await expectAsync (client.login ( | ||||
|       'testuser', | ||||
|       'bar', | ||||
|       `http://localhost:${port}` | ||||
|     )) | ||||
|       .toBeRejectedWithError ('user or password invalid'); | ||||
|   }); | ||||
| }); | ||||
|  | ||||
							
								
								
									
										12
									
								
								tsconfig.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								tsconfig.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | ||||
| { | ||||
|   "compilerOptions": { | ||||
|     "target": "es5", | ||||
|     "module": "commonjs", | ||||
|     "outDir": "./dist",  | ||||
|     "rootDir": "./lib", | ||||
|     "strict": true,  | ||||
|     "esModuleInterop": true, | ||||
|     "forceConsistentCasingInFileNames": true, | ||||
|     "declaration": true   | ||||
|   } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user