├── .eslintignore ├── .eslintrc ├── .gitignore ├── .npmignore ├── .travis.yml ├── .vscode └── launch.json ├── CHANGELOG.md ├── Gruntfile.js ├── LICENSE ├── README.md ├── RELEASES.md ├── SECURITY-NOTICE.MD ├── contributing.md ├── lib ├── aadutils.js ├── bearerstrategy.js ├── constants.js ├── cookieContentHandler.js ├── errors │ ├── badrequesterror.js │ ├── internaloautherror.js │ └── internalopeniderror.js ├── index.js ├── jsconfig.json ├── jsonWebToken.js ├── jwe.js ├── logging.js ├── metadata.js ├── oidcstrategy.js ├── pem.js ├── sessionContentHandler.js └── validator.js ├── package-lock.json ├── package.json ├── test ├── Chai-passport_test │ ├── aadutils_test.js │ ├── b2c_oidc_hybrid_and_code_flow_test.js │ ├── b2c_oidc_implicit_flow_test.js │ ├── b2c_oidc_incoming_request_test.js │ ├── bearer_flow_test.js │ ├── bearer_token_in_req_test.js │ ├── clock_skew_test.js │ ├── constants_test.js │ ├── cookie_test.js │ ├── json_web_token_test.js │ ├── jwe_test.js │ ├── oidc_common_endpoint_test.js │ ├── oidc_dynamic_tenant_test.js │ ├── oidc_hybrid_and_code_flow_test.js │ ├── oidc_implicit_flow_test.js │ ├── oidc_incoming_request_test.js │ └── sessionContentHandler_test.js ├── End_to_end_test │ ├── app │ │ ├── api.js │ │ ├── app.js │ │ ├── app_for_cookie.js │ │ ├── app_for_jwe.js │ │ ├── app_for_testing_sts.js │ │ ├── client_for_api.js │ │ └── views │ │ │ ├── apiResult.ejs │ │ │ ├── index.ejs │ │ │ ├── index_b2c.ejs │ │ │ └── result.ejs │ ├── bearer_b2c_test.js │ ├── bearer_test.js │ ├── driver.js │ ├── oidc_b2c_test.js │ ├── oidc_cookie_test.js │ ├── oidc_jwe_test.js │ ├── oidc_testing_sts_test.js │ ├── oidc_v1_test.js │ ├── oidc_v2_test.js │ ├── package.json │ ├── script.js │ └── test_parameters.js ├── Nodeunit_test │ ├── bearer_test.js │ ├── hash_test.js │ ├── metadata_test.js │ ├── oidc_b2c_test.js │ ├── oidc_test.js │ ├── oidc_v2_test.js │ ├── pem_test.js │ └── validator_test.js └── resource │ ├── correctKeyOrCert.js │ ├── private.pem │ └── public.pem └── typings ├── browser.d.ts ├── browser └── ambient │ ├── express-serve-static-core │ └── index.d.ts │ ├── express │ └── index.d.ts │ ├── node │ └── index.d.ts │ └── serve-static │ └── index.d.ts ├── main.d.ts └── main └── ambient ├── express-serve-static-core └── index.d.ts ├── express └── index.d.ts ├── node └── index.d.ts └── serve-static └── index.d.ts /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/**/*.js 2 | /examples/**/*.js 3 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "oniyi" 3 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | test/End_to_end_test/node_modules/ 3 | test/End_to_end_test/debug.log 4 | .idea/ 5 | npm-debug.log 6 | federationmetadata.xml 7 | *.sublime-workspace 8 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .vscode/ 3 | test/ 4 | .eslint* 5 | .travis.yml 6 | Gruntfile.js 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | dist: trusty 3 | language: node_js 4 | node_js: 5 | - "12" 6 | - "10" 7 | - "8" 8 | before_install: npm install -g grunt-cli 9 | install: npm install 10 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Launch", 6 | "type": "node", 7 | "request": "launch", 8 | "program": "${workspaceRoot}/examples/login-oidc-b2c/app.js", 9 | "stopOnEntry": false, 10 | "args": [], 11 | "cwd": "${workspaceRoot}", 12 | "preLaunchTask": null, 13 | "runtimeExecutable": null, 14 | "runtimeArgs": [ 15 | "--nolazy" 16 | ], 17 | "env": { 18 | "NODE_ENV": "development" 19 | }, 20 | "externalConsole": false, 21 | "sourceMaps": false, 22 | "outDir": null 23 | }, 24 | { 25 | "name": "Attach", 26 | "type": "node", 27 | "request": "attach", 28 | "port": 5858, 29 | "address": "localhost", 30 | "restart": false, 31 | "sourceMaps": false, 32 | "outDir": null, 33 | "localRoot": "${workspaceRoot}", 34 | "remoteRoot": null 35 | } 36 | ] 37 | } -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function loadGrunt(grunt) { 4 | // Project configuration. 5 | grunt.initConfig({ 6 | pkg: grunt.file.readJSON('package.json'), 7 | nodeunit: { 8 | files: ['test/Nodeunit_test/*_test.js'], 9 | }, 10 | mochaTest: { 11 | test: { 12 | options: { 13 | reporter: 'spec', 14 | clearRequireCache: true 15 | }, 16 | src: ['test/Chai-passport_test/*_test.js'], 17 | }, 18 | } 19 | }); 20 | 21 | // These plugins provide necessary tasks. 22 | grunt.loadNpmTasks('grunt-contrib-nodeunit'); 23 | grunt.loadNpmTasks('grunt-mocha-test'); 24 | 25 | grunt.registerTask('printMsg_nodeunit', () => { 26 | grunt.log.writeln('\n\n\n======= Running tests in test/nodeunit_test =======\n\n\n'); 27 | }); 28 | grunt.registerTask('printMsg_chai-passport', () => { 29 | grunt.log.writeln('\n\n\n======= Running tests in test/chai-passport_test =======\n\n\n'); 30 | }); 31 | grunt.registerTask('printMsg_end_to_end_Test', () => { 32 | grunt.log.writeln('\n\n\n======= Running end to end tests in test/End_to_end_test =======\n\n\n'); 33 | }); 34 | grunt.registerTask('end_to_end_test', () => { 35 | grunt.config('mochaTest.test.src', 'test/End_to_end_test/*_test.js'); 36 | grunt.task.run(['mochaTest']); 37 | }); 38 | grunt.registerTask('e2e', ['printMsg_end_to_end_Test', 'end_to_end_test']); 39 | grunt.registerTask('run_tests_with_e2e', ['printMsg_chai-passport', 'mochaTest', 'printMsg_nodeunit', 'nodeunit', 'printMsg_end_to_end_Test', 'end_to_end_test']); 40 | grunt.registerTask('run_tests', ['printMsg_chai-passport', 'mochaTest', 'printMsg_nodeunit', 'nodeunit']); 41 | grunt.registerTask('default', 'run_tests'); 42 | }; 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright (c) Microsoft Corporation 3 | All rights reserved. 4 | 5 | Copyright (c) 2015 Kiyofumi Kondoh 6 | Copyright (c) 2012 Henri Bergius 7 | Copyright (c) 2011 Michael Bosworth 8 | 9 | Permission is hereby granted, free of charge, to any person obtaining a copy of 10 | this software and associated documentation files (the "Software"), to deal in 11 | the Software without restriction, including without limitation the rights to 12 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 13 | the Software, and to permit persons to whom the Software is furnished to do so, 14 | subject to the following conditions: 15 | 16 | The above copyright notice and this permission notice shall be included in all 17 | copies or substantial portions of the Software. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 21 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 22 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 23 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 24 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /RELEASES.md: -------------------------------------------------------------------------------- 1 | # Microsoft Identity SDK Versioning and Servicing FAQ 2 | 3 | We have adopted the semantic versioning flow that is industry standard for OSS projects. It gives the maximum amount of control on what risk you take with what versions. If you know how semantic versioning works with node.js, java, and ruby none of this will be new. 4 | 5 | ##Semantic Versioning and API stability promises 6 | 7 | Microsoft Identity libraries are independent open source libraries that are used by partners both internal and external to Microsoft. As with the rest of Microsoft, we have moved to a rapid iteration model where bugs are fixed daily and new versions are produced as required. To communicate these frequent changes to external partners and customers, we use semantic versioning for all our public Microsoft Identity SDK libraries. This follows the practices of other open source libraries on the internet. This allows us to support our downstream partners which will lock on certain versions for stability purposes, as well as providing for the distribution over NuGet, CocoaPods, and Maven. 8 | 9 | The semantics are: MAJOR.MINOR.PATCH (example 1.1.5) 10 | 11 | We will update our code distributions to use the latest PATCH semantic version number in order to make sure our customers and partners get the latest bug fixes. Downstream partner needs to pull the latest PATCH version. Most partners should try lock on the latest MINOR version number in their builds and accept any updates in the PATCH number. 12 | 13 | Examples: 14 | Using Cocapods, the following in the podfile will take the latest ADALiOS build that is > 1.1 but not 1.2. 15 | ``` 16 | pod 'ADALiOS', '~> 1.1' 17 | ``` 18 | 19 | Using NuGet, this ensures all 1.1.0 to 1.1.x updates are included when building your code, but not 1.2. 20 | 21 | ``` 22 | 26 | ``` 27 | 28 | | Version | Description | Example | 29 | |:-------:|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------:|:---------------------------------------------------------------------------------------------------------:| 30 | | x.x.x | PATCH version number. Incrementing these numbers is for bug fixes and updates but do not introduce new features. This is used for close partners who build on our platform release (ex. Azure AD Fabric, Office, etc.),In addition, Cocoapods, NuGet, and Maven use this number to deliver the latest release to customers.,This will update frequently (sometimes within the same day),There is no new features, and no regressions or API surface changes. Code will continue to work unless affected by a particular code fix. | ADAL for iOS 1.0.10,(this was a fix for the Storyboard display that was fixed for a specific Office team) | 31 | | x.x | MINOR version numbers. Incrementing these second numbers are for new feature additions that do not impact existing features or introduce regressions. They are purely additive, but may require testing to ensure nothing is impacted.,All x.x.x bug fixes will also roll up in to this number.,There is no regressions or API surface changes. Code will continue to work unless affected by a particular code fix or needs this new feature. | ADAL for iOS 1.1.0,(this added WPJ capability to ADAL, and rolled all the updates from 1.0.0 to 1.0.12) | 32 | | x | MAJOR version numbers. This should be considered a new, supported version of Microsoft Identity SDK and begins the Azure two year support cycle anew. Major new features are introduced and API changes can occur.,This should only be used after a large amount of testing and used only if those features are needed.,We will continue to service MAJOR version numbers with bug fixes up to the two year support cycle. | ADAL for iOS 1.0,(our first official release of ADAL) | 33 | 34 | 35 | 36 | ## Serviceability 37 | 38 | When we release a new MINOR version, the previous MINOR version is abandoned. 39 | 40 | When we release a new MAJOR version, we will continue to apply bug fixes to the existing features in the previous MAJOR version for up to the 2 year support cycle for Azure. 41 | Example: We release ADALiOS 2.0 in the future which supports unified Auth for AAD and MSA. Later, we then have a fix in Conditional Access for ADALiOS. Since that feature exists both in ADALiOS 1.1 and ADALiOS 2.0, we will fix both. It will roll up in a PATCH number for each. Customers that are still locked down on ADALiOS 1.1 will receive the benefit of this fix. 42 | 43 | ## Microsoft Identity SDKs and Azure Active Directory 44 | 45 | Microsoft Identity SDKs major versions will maintain backwards compatibility with Azure Active Directory web services through the support period. This means that the API surface area defined in a MAJOR version will continue to work for 2 years after release. 46 | 47 | We will respond to bugs quickly from our partners and customers submitted through GitHub and through our private alias (tellaad@microsoft.com) for security issues and update the PATCH version number. We will also submit a change summary for each PATCH number. 48 | Occasionally, there will be security bugs or breaking bugs from our partners that will require an immediate fix and a publish of an update to all partners and customers. When this occurs, we will do an emergency roll up to a PATCH version number and update all our distribution methods to the latest. 49 | -------------------------------------------------------------------------------- /SECURITY-NOTICE.MD: -------------------------------------------------------------------------------- 1 | Security vulnerability details for passport-azure-ad <1.4.6, 2.0.0 2 | =================== 3 | 4 | Our team discovered a vulnerability in the Passport-Azure-AD for NodeJS library affecting versions <1.4.6 and 2.0.0. This vulnerability can allow a user to bypass certain authentication mechanisms. Developers using the Passport-Azure-AD for NodeJS library need to download the latest version of the Passport-Azure-AD for NodeJS library. 5 | 6 | > - **if you are currently on 1.x below 1.4.6, please update to 1.4.6 or 7 | > greater** 8 | > - **if you are currently on 2.0.0, please update to 2.0.1 or 9 | > greater** 10 | 11 | Updated packages are available on npm. To ensure you get additional bug fixes going forward, please ensure your package.json file is updated to take PATCH and MINOR level updates of our libraries. 12 | 13 | #### Example for 2.0 release: 14 | ```sh 15 | { 16 | "dependencies": { 17 | "passport-azure-ad": "^2.0.1" 18 | } 19 | } 20 | ``` 21 | #### Example for 1.4 release: 22 | ```sh 23 | { 24 | "dependencies": { 25 | "passport-azure-ad": "^1.4.6" 26 | } 27 | } 28 | ``` 29 | ### Upgrade Notes 30 | 31 | 1. This patch updates the library that your application runs, but does not change the current state of your users, including any sessions they had open. This applies to malicious users who could have exploited this vulnerability to gain access to your system. If your application has users with existing sessions open, after applying the patch, ensure all these sessions are terminated and users are required to sign in again. 32 | 33 | 34 | 2. In previous versions of the Passport-Azure-AD for NodeJS library, the issuer wasn't validated, even if you had set validateIssuer to true in your configuration. This is fixed in versions 1.4.6 and 2.0.1. However, this may mean you get 401s if you are using the common endpoint in the identityMetadata config setting and have validateIssuer to true. If you are using the "common" endpoint (which looks like "https://login.microsoftonline.com/common/.well-known/openid-configuration"), issuers cannot be validated. You can fix this in two ways: 35 | 36 | - If you are a single-tenant app, you can replace 'common' with your tenantId in the endpoint address. The issuer will be validated. IdentityMetadata set to support a single tenant should look like "https://login.microsoftonline.com/72f988bf-86f1-41af-91ab-2d7cd011dddd/.well-known/openid-configuration" with your tenant GUID replaced in the path or "https://login.microsoftonline.com/your-tenant-name.onmicrosoft.com/.well-known/openid-configuration" with your tenant name replaced in the path. 37 | 38 | - If you are a multi-tenant app and need to go against the common endpoint, you must set validateIssuer to false. Be aware that the issuer field of the token will not be validated and all issuers will be accepted. 39 | 40 | You can read more about the [issue here]. 41 | 42 | [//]: # (These are reference links used in the body of this note.) 43 | [issue here]: http://support.microsoft.com/kb/3187742 44 | -------------------------------------------------------------------------------- /contributing.md: -------------------------------------------------------------------------------- 1 | # Branch Structure 2 | * **master**: The latest official GA version 3 | * **release1x**: The latest official release of version 1.x. All 1.x contributions should be against the **release1x** branch. 4 | * **release2x**: The latest official release of version 2.x. All 2.x contributions should be against the **release2x** branch. 5 | * **dev**: The dev working branch of master. All 3.x contributions should be against the **dev** branch. 6 | 7 | If you are contributing code to 3.x, you should branch from **dev** and make a pull request for your topic branch against the **dev** branch. If you are contributing code to 2.x, you should branch from **release2x** and make a pull request for your topic branch against the **release2x** branch. If you are contributing code to 1.x, you should branch from **release1x** and make a pull request for your topic branch against the **release1x** branch. 8 | 9 | # Releases 10 | All the previous releases can be found [here](https://github.com/AzureAD/passport-azure-ad/releases). 11 | 12 | # Filing Bugs 13 | Please file issues you see in the [issue tracker](https://github.com/AzureAD/passport-azure-ad/issues). Include: 14 | 15 | - The version you are using. 16 | - The behavior you are seeing. If at all possible, please submit a reduced repro or test that demonstrates the issue. 17 | - What you expect to see. 18 | 19 | # Instructions for Contributing Code 20 | 21 | ## Contributing bug fixes 22 | 23 | We are currently accepting contributions in the form of bug fixes. A bug must have an issue tracking it in the issue tracker. Your pull request should include a link to the bug that you are fixing. If you've submitted a PR for a bug, please post a comment in the bug to avoid duplication of effort. 24 | 25 | ## Contributing features 26 | Features (things that add new or improved functionality) may be accepted, but will need to first be approved (tagged with "enhancement") in the issue. 27 | 28 | ## Legal 29 | You will need to complete a Contributor License Agreement (CLA). Briefly, this agreement testifies that you are granting us permission to use the submitted change according to the terms of the project's license, and that the work being submitted is under appropriate copyright. 30 | 31 | Please submit a Contributor License Agreement (CLA) before submitting a pull request. You may visit https://cla.microsoft.com to sign digitally. You only need to do this once. Once we have received the signed CLA, we'll review the request. 32 | 33 | ## Housekeeping 34 | Your pull request should: 35 | 36 | * Include a description of what your change intends to do 37 | * Be based on a reasonably recent pull in the **dev** branch 38 | * Please rebase and squash all commits into a single one 39 | * Make sure both your local test run and the automatic Travis test run pass. See the test instructions section below for more details 40 | * Have clear commit messages 41 | * Include new tests for bug fixes and new features 42 | * To avoid line ending issues, set `autocrlf = input` and `whitespace = cr-at-eol` in your git configuration 43 | 44 | ## Test instructions 45 | 46 | For the testing tools, we use both nodeunit and [chai-passport-strategy](https://github.com/jaredhanson/chai-passport-strategy). Nodeunit is used for general testing purposes where the passport framework is not involved, and chai-passport-strategy is used for the passport strategy workflow testing. Instructions on how to use chai-passport-strategy can be found [here](https://github.com/jaredhanson/chai-passport-strategy/blob/master/README.md). 47 | 48 | All the test files should have a _test suffix in their names and be placed in the correct subdirectory, depending on the testing tools used. The following is the rule: 49 | 50 | * **Nodeunit_test**: contains all nodeunit tests 51 | * **Chai-passport_test**: contains all chai-passport-strategy tests 52 | * **End_to_end_test**: contains all end to end tests using Selenium with chrome webdriver 53 | * **resource**: contains all shared resources for testing (for example, pem key file) 54 | 55 | ### How to run tests on your machine 56 | 57 | Please refer to section 6 in README.md for the test instructions. 58 | 59 | ### Automatic Travis test 60 | 61 | After you submit your pull request, Travis test will run automatically. The status of this test can be found at the bottom of your pull request page, and it may take some time to complete. After completion, a successful test run will show a "All checks have passed" status with a green check sign. 62 | -------------------------------------------------------------------------------- /lib/constants.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation 3 | * All Rights Reserved 4 | * MIT License 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this 7 | * software and associated documentation files (the 'Software'), to deal in the Software 8 | * without restriction, including without limitation the rights to use, copy, modify, 9 | * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 10 | * permit persons to whom the Software is furnished to do so, subject to the following 11 | * conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be 14 | * included in all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 17 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS 19 | * OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT 21 | * OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | */ 23 | 24 | 'use strict'; 25 | 26 | // constants that are not strategy specific 27 | 28 | var CONSTANTS = {}; 29 | 30 | CONSTANTS.POLICY_REGEX = /^b2c_1a?_[0-9a-z._-]+$/i; // policy is case insensitive 31 | 32 | CONSTANTS.CLOCK_SKEW = 300; // 5 minutes 33 | 34 | CONSTANTS.CLIENT_ASSERTION_JWT_LIFETIME = 600; // 10 minutes 35 | CONSTANTS.CLIENT_ASSERTION_TYPE = 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer'; 36 | 37 | module.exports = CONSTANTS; 38 | -------------------------------------------------------------------------------- /lib/cookieContentHandler.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation 3 | * All Rights Reserved 4 | * MIT License 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this 7 | * software and associated documentation files (the "Software"), to deal in the Software 8 | * without restriction, including without limitation the rights to use, copy, modify, 9 | * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 10 | * permit persons to whom the Software is furnished to do so, subject to the following 11 | * conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be 14 | * included in all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS 19 | * OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT 21 | * OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | */ 23 | 24 | 'use restrict'; 25 | 26 | var crypto = require('crypto'); 27 | var createBuffer = require('./jwe').createBuffer; 28 | var sameSiteNotAllowed = require('./aadutils').sameSiteNotAllowed; 29 | 30 | /* 31 | * the handler for state/nonce/policy 32 | * @maxAmount - the max amount of {state: x, nonce: x, policy: x} tuples you want to save in cookie 33 | * @maxAge - when a tuple in session expires in seconds 34 | * @cookieEncryptionKeys 35 | * - keys used to encrypt and decrypt cookie 36 | * @domain - sets the cookie's domain 37 | */ 38 | function CookieContentHandler(maxAmount, maxAge, cookieEncryptionKeys, domain, cookieSameSite) { 39 | if (!maxAge || (typeof maxAge !== 'number' || maxAge <= 0)) 40 | throw new Error('CookieContentHandler: maxAge must be a positive number'); 41 | this.maxAge = maxAge; // seconds 42 | 43 | if (!maxAmount || (typeof maxAmount !== 'number' || maxAmount <= 0 || maxAmount % 1 !== 0)) 44 | throw new Error('CookieContentHandler: maxAmount must be a positive integer'); 45 | this.maxAmount = maxAmount; 46 | 47 | if (!cookieEncryptionKeys || !Array.isArray(cookieEncryptionKeys) || cookieEncryptionKeys.length === 0) 48 | throw new Error('CookieContentHandler: cookieEncryptionKeys must be a non-emptry array'); 49 | 50 | if (typeof cookieSameSite !== 'boolean') { 51 | throw new Error('CookieContentHandler: cookieSameSite must be a boolean'); 52 | } 53 | this.cookieSameSite = cookieSameSite 54 | 55 | for (var i = 0; i < cookieEncryptionKeys.length; i++) { 56 | var item = cookieEncryptionKeys[i]; 57 | if (!item.key || !item.iv) 58 | throw new Error(`CookieContentHandler: array item ${i+1} in cookieEncryptionKeys must have the form { key: , iv: }`); 59 | if (item.key.length !== 32) 60 | throw new Error(`CookieContentHandler: key number ${i+1} is ${item.key.length} bytes, expected: 32 bytes`); 61 | if (item.iv.length !== 12) 62 | throw new Error(`CookieContentHandler: iv number ${i+1} is ${item.iv.length} bytes, expected: 12 bytes`); 63 | } 64 | 65 | this.cookieEncryptionKeys = cookieEncryptionKeys; 66 | 67 | this.domain = domain; 68 | } 69 | 70 | CookieContentHandler.prototype.findAndDeleteTupleByState = function(req, res, stateToFind) { 71 | if (!req.cookies) 72 | throw new Error('Cookie is not found in request. Did you forget to use cookie parsing middleware such as cookie-parser?'); 73 | 74 | var cookieEncryptionKeys = this.cookieEncryptionKeys; 75 | 76 | var tuple = null; 77 | 78 | // try every key and every cookie 79 | for (var i = 0; i < cookieEncryptionKeys.length; i++) { 80 | var item = cookieEncryptionKeys[i]; 81 | var key = createBuffer(item.key); 82 | var iv = createBuffer(item.iv); 83 | 84 | for (var cookie in req.cookies) { 85 | if (req.cookies.hasOwnProperty(cookie) && cookie.startsWith('passport-aad.')) { 86 | var encrypted = cookie.substring(13); 87 | 88 | try { 89 | var decrypted = decryptCookie(encrypted, key, iv); 90 | tuple = JSON.parse(decrypted); 91 | } catch (ex) { 92 | continue; 93 | } 94 | 95 | if (tuple.state === stateToFind) { 96 | res.clearCookie(cookie); 97 | return tuple; 98 | } 99 | } 100 | } 101 | } 102 | 103 | return null; 104 | }; 105 | 106 | CookieContentHandler.prototype.add = function(req, res, tupleToAdd) { 107 | var cookies = []; 108 | 109 | // collect the related cookies 110 | for (var cookie in req.cookies) { 111 | if (req.cookies.hasOwnProperty(cookie) && cookie.startsWith('passport-aad.')) 112 | cookies.push(cookie); 113 | } 114 | 115 | // only keep the most recent maxAmount-1 many cookies 116 | if (cookies.length > this.maxAmount - 1) { 117 | cookies.sort(); 118 | 119 | var numberToRemove = cookies.length - (this.maxAmount - 1); 120 | 121 | for (var i = 0; i < numberToRemove; i++) { 122 | res.clearCookie(cookies[0]); 123 | cookies.shift(); 124 | } 125 | } 126 | 127 | // add the new cookie 128 | 129 | var tupleString = JSON.stringify(tupleToAdd); 130 | 131 | var item = this.cookieEncryptionKeys[0]; 132 | var key = createBuffer(item.key); 133 | var iv = createBuffer(item.iv); 134 | 135 | var encrypted = encryptCookie(tupleString, key, iv); 136 | 137 | let options = { maxAge: this.maxAge * 1000, httpOnly: true } 138 | if (this.domain) { 139 | options.domain = this.domain; 140 | } 141 | 142 | if (this.cookieSameSite && !sameSiteNotAllowed(req.get('User-Agent'))) { 143 | options.sameSite = 'none'; 144 | options.secure = true; 145 | } 146 | 147 | res.cookie('passport-aad.' + Date.now() + '.' + encrypted, 0, options); 148 | }; 149 | 150 | var encryptCookie = function(content, key, iv) { 151 | var cipher = crypto.createCipheriv('aes-256-gcm', key, iv); 152 | 153 | var encrypted = cipher.update(content, 'utf8', 'hex'); 154 | encrypted += cipher.final('hex'); 155 | var authTag = cipher.getAuthTag().toString('hex'); 156 | 157 | return encrypted + '.' + authTag; 158 | }; 159 | 160 | var decryptCookie = function(encrypted, key, iv) { 161 | var parts = encrypted.split('.'); 162 | if (parts.length !== 3) 163 | throw new Error('invalid cookie'); 164 | 165 | // the first part is timestamp, ignore it 166 | var content = createBuffer(parts[1], 'hex'); 167 | var authTag = createBuffer(parts[2], 'hex'); 168 | 169 | var decipher = crypto.createDecipheriv('aes-256-gcm', key, iv); 170 | decipher.setAuthTag(authTag); 171 | var decrypted = decipher.update(content, 'hex', 'utf8'); 172 | decrypted += decipher.final('utf8'); 173 | 174 | return decrypted; 175 | }; 176 | 177 | exports.CookieContentHandler = CookieContentHandler; 178 | 179 | -------------------------------------------------------------------------------- /lib/errors/badrequesterror.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * `BadRequestError` error. 5 | * 6 | * @api public 7 | */ 8 | function BadRequestError(message) { 9 | Error.call(this); 10 | Error.captureStackTrace(this, BadRequestError); 11 | this.name = 'BadRequestError'; 12 | this.message = message || null; 13 | } 14 | 15 | /** 16 | * Inherit from `Error`. 17 | */ 18 | BadRequestError.prototype = Object.create(Error.prototype); 19 | 20 | /** 21 | * Expose `BadRequestError`. 22 | */ 23 | module.exports = BadRequestError; 24 | -------------------------------------------------------------------------------- /lib/errors/internaloautherror.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * `InternalOAuthError` error. 5 | * 6 | * InternalOAuthError wraps errors generated by node-oauth. By wrapping these 7 | * objects, error messages can be formatted in a manner that aids in debugging 8 | * OAuth issues. 9 | * 10 | * @api public 11 | */ 12 | function InternalOAuthError(message, err) { 13 | Error.call(this); 14 | Error.captureStackTrace(this, InternalOAuthError); 15 | this.name = 'InternalOAuthError'; 16 | this.message = message; 17 | this.oauthError = err; 18 | } 19 | 20 | /** 21 | * Inherit from `Error`. 22 | */ 23 | InternalOAuthError.prototype = Object.create(Error.prototype); 24 | 25 | /** 26 | * Returns a string representing the error. 27 | * 28 | * @return {String} 29 | * @api public 30 | */ 31 | InternalOAuthError.prototype.toString = function toString() { 32 | let m = this.message; 33 | if (this.oauthError) { 34 | if (this.oauthError instanceof Error) { 35 | m += ` (${this.oauthError})`; 36 | } else if (this.oauthError.statusCode && this.oauthError.data) { 37 | m += `(status: ${this.oauthError.statusCode} data: ${this.oauthError.data})`; 38 | } 39 | } 40 | return m; 41 | }; 42 | 43 | /** 44 | * Expose `InternalOAuthError`. 45 | */ 46 | module.exports = InternalOAuthError; 47 | -------------------------------------------------------------------------------- /lib/errors/internalopeniderror.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * `InternalOpenIDError` error. 5 | * 6 | * InternalOpenIDError wraps errors generated by node-openid. By wrapping these 7 | * objects, error messages can be formatted in a manner that aids in debugging 8 | * OpenID issues. 9 | * 10 | * @api public 11 | */ 12 | function InternalOpenIDError(message, err) { 13 | Error.call(this); 14 | Error.captureStackTrace(this, InternalOpenIDError); 15 | this.name = 'InternalOpenIDError'; 16 | this.message = message; 17 | this.openidError = err; 18 | } 19 | 20 | /** 21 | * Inherit from `Error`. 22 | */ 23 | InternalOpenIDError.prototype = Object.create(Error.prototype); 24 | 25 | /** 26 | * Returns a string representing the error. 27 | * 28 | * @return {String} 29 | * @api public 30 | */ 31 | InternalOpenIDError.prototype.toString = function toString() { 32 | let m = this.message; 33 | if (this.openidError) { 34 | if (this.openidError instanceof Error) { 35 | m += ` (${this.openidError})`; 36 | } else if (this.openidError.message) { 37 | m += ` (message: '${this.openidError.message}')`; 38 | } 39 | } 40 | return m; 41 | }; 42 | 43 | /** 44 | * Expose `InternalOpenIDError`. 45 | */ 46 | module.exports = InternalOpenIDError; 47 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation 3 | * All Rights Reserved 4 | * MIT License 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this 7 | * software and associated documentation files (the 'Software'), to deal in the Software 8 | * without restriction, including without limitation the rights to use, copy, modify, 9 | * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 10 | * permit persons to whom the Software is furnished to do so, subject to the following 11 | * conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be 14 | * included in all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 17 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS 19 | * OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT 21 | * OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | */ 23 | 24 | 'use strict'; 25 | 26 | /** 27 | * Export BearerStrategy and OIDCStrategy. 28 | */ 29 | module.exports.BearerStrategy = require('./bearerstrategy'); 30 | module.exports.OIDCStrategy = require('./oidcstrategy'); 31 | -------------------------------------------------------------------------------- /lib/jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "commonjs" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /lib/logging.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation 3 | * All Rights Reserved 4 | * MIT License 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this 7 | * software and associated documentation files (the 'Software'), to deal in the Software 8 | * without restriction, including without limitation the rights to use, copy, modify, 9 | * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 10 | * permit persons to whom the Software is furnished to do so, subject to the following 11 | * conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be 14 | * included in all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 17 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS 19 | * OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT 21 | * OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | */ 23 | 'use strict'; 24 | 25 | const bunyan = require('bunyan'); 26 | 27 | function getLogger(name) { 28 | const log = bunyan.createLogger({ 29 | name, 30 | streams: [{ 31 | stream: process.stderr, 32 | level: 'error', 33 | name: 'error', 34 | }, { 35 | stream: process.stdout, 36 | level: 'warn', 37 | name: 'console', 38 | }], 39 | }); 40 | return log; 41 | } 42 | 43 | exports.getLogger = getLogger; 44 | -------------------------------------------------------------------------------- /lib/metadata.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation 3 | * All Rights Reserved 4 | * MIT License 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this 7 | * software and associated documentation files (the 'Software'), to deal in the Software 8 | * without restriction, including without limitation the rights to use, copy, modify, 9 | * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 10 | * permit persons to whom the Software is furnished to do so, subject to the following 11 | * conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be 14 | * included in all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 17 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS 19 | * OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT 21 | * OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | */ 23 | 24 | 'use strict'; 25 | 26 | const request = require('request'); 27 | const async = require('async'); 28 | const aadutils = require('./aadutils'); 29 | const HttpsProxyAgent = require('https-proxy-agent'); 30 | const Log = require('./logging').getLogger; 31 | 32 | const log = new Log('AzureAD: Metadata Parser'); 33 | 34 | function Metadata(url, authtype, options) { 35 | if (!url) { 36 | throw new Error('Metadata: url is a required argument'); 37 | } 38 | if (!authtype || authtype !== 'oidc') { 39 | throw new Error(`Invalid authtype. authtype must be 'oidc'`); 40 | } 41 | 42 | // if logging level specified, switch to it. 43 | if (options.loggingLevel) { log.levels('console', options.loggingLevel); } 44 | 45 | this.url = url; 46 | this.metadata = null; 47 | this.authtype = authtype; 48 | this.loggingNoPII = options.loggingNoPII; 49 | if (options.proxy) { 50 | // if user has specified proxy settings instantiate agent 51 | this.httpsProxyAgent = new HttpsProxyAgent(options.proxy); 52 | } 53 | } 54 | 55 | Object.defineProperty(Metadata, 'url', { 56 | get: function getUrl() { 57 | return this.url; 58 | }, 59 | }); 60 | 61 | Object.defineProperty(Metadata, 'oidc', { 62 | get: function getOidc() { 63 | return this.oidc; 64 | }, 65 | }); 66 | 67 | Object.defineProperty(Metadata, 'metadata', { 68 | get: function getMetadata() { 69 | return this.metadata; 70 | }, 71 | }); 72 | 73 | Object.defineProperty(Metadata, 'httpsProxyAgent', { 74 | get: function getHttpsProxyAgent() { 75 | return this.httpsProxyAgent; 76 | } 77 | }); 78 | 79 | Metadata.prototype.updateOidcMetadata = function updateOidcMetadata(doc, next) { 80 | log.info('Request to update the Open ID Connect Metadata'); 81 | 82 | const self = this; 83 | 84 | var oidc = {}; 85 | oidc.algorithms = doc.id_token_signing_alg_values_supported; 86 | oidc.authorization_endpoint = doc.authorization_endpoint; 87 | oidc.end_session_endpoint = doc.end_session_endpoint; 88 | oidc.issuer = doc.issuer; 89 | oidc.token_endpoint = doc.token_endpoint; 90 | oidc.userinfo_endpoint = doc.userinfo_endpoint; 91 | self.oidc = oidc; 92 | 93 | const jwksUri = doc.jwks_uri; 94 | 95 | if (!self.loggingNoPII) { 96 | log.info('Algorithm retrieved was: ', self.oidc.algorithms); 97 | log.info('Issuer we are using is: ', self.oidc.issuer); 98 | log.info('Key Endpoint we will use is: ', jwksUri); 99 | log.info('Authentication endpoint we will use is: ', self.oidc.authorization_endpoint); 100 | log.info('Token endpoint we will use is: ', self.oidc.token_endpoint); 101 | log.info('User info endpoint we will use is: ', self.oidc.userinfo_endpoint); 102 | log.info('The logout endpoint we will use is: ', self.oidc.end_session_endpoint); 103 | } 104 | 105 | // fetch the signing keys 106 | request.get({ uri: jwksUri, agent: self.httpsProxyAgent, json: true }, (err, response, body) => { 107 | if (err) { 108 | return next(err); 109 | } 110 | if (response.statusCode !== 200) { 111 | return next(new Error(`Error: ${response.statusCode} Cannot get AAD Signing Keys`)); 112 | } 113 | self.oidc.keys = body.keys; 114 | return next(); 115 | }); 116 | }; 117 | 118 | Metadata.prototype.generateOidcPEM = function generateOidcPEM(kid) { 119 | const self = this; 120 | const keys = this && this.oidc && Array.isArray(this.oidc.keys) ? this.oidc.keys : null; 121 | let pubKey = null; 122 | let foundKey = false; 123 | 124 | if (!kid) { 125 | throw new Error('kid is missing'); 126 | } 127 | 128 | if (!keys) { 129 | throw new Error('keys is missing'); 130 | } 131 | 132 | keys.some((key) => { 133 | if (self.loggingNoPII) 134 | log.info('working on key'); 135 | else 136 | log.info('working on key:', key); 137 | 138 | // are we working on the right key? 139 | if (key.kid !== kid) { 140 | return false; 141 | } 142 | 143 | // check for `modulus` to be present 144 | if (!key.n) { 145 | if (self.loggingNoPII) 146 | log.warn('modulus is empty; corrupt key'); 147 | else 148 | log.warn('modulus is empty; corrupt key', key); 149 | return false; 150 | } 151 | 152 | // check for `exponent` to be present 153 | if (!key.e) { 154 | if (self.loggingNoPII) 155 | log.warn('exponent is empty; corrupt key'); 156 | else 157 | log.warn('exponent is empty; corrupt key', key); 158 | return false; 159 | } 160 | 161 | // generate PEM from `modulus` and `exponent` 162 | pubKey = aadutils.rsaPublicKeyPem(key.n, key.e); 163 | foundKey = true; 164 | 165 | return pubKey; 166 | }); 167 | 168 | if (!foundKey) { 169 | if (self.loggingNoPII) 170 | throw new Error('a key with the specific kid cannot be found'); 171 | else 172 | throw new Error(`a key with kid %s cannot be found`, kid); 173 | } 174 | 175 | if (!pubKey) { 176 | if (self.loggingNoPII) 177 | throw new Error('generating public key pem failed'); 178 | else 179 | throw new Error(`generating public key pem failed for kid: %s`, kid); 180 | } 181 | 182 | return pubKey; 183 | }; 184 | 185 | Metadata.prototype.fetch = function fetch(callback) { 186 | const self = this; 187 | 188 | async.waterfall([ 189 | // fetch the Federation metadata for the AAD tenant 190 | (next) => { 191 | request.get({ uri: self.url, agent: self.httpsProxyAgent }, (err, response, body) => { 192 | if (err) { 193 | return next(err); 194 | } 195 | if (response.statusCode !== 200) { 196 | if (self.loggingNoPII) { 197 | log.error('cannot get AAD Federation metadata from endpoint you specified'); 198 | return next(new Error('Cannot get AAD Federation metadata')); 199 | } else { 200 | log.error('Cannot get AAD Federation metadata from endpoint you specified', self.url); 201 | return next(new Error(`Error: ${response.statusCode} Cannot get AAD Federation metadata 202 | from ${self.url}`)); 203 | } 204 | } 205 | return next(null, body); 206 | }); 207 | }, 208 | // parse retrieved metadata 209 | (body, next) => { 210 | // use json parser for oidc authType 211 | log.info('Parsing JSON retreived from the endpoint'); 212 | self.metadata = JSON.parse(body); 213 | return next(null); 214 | }, 215 | // call update method for parsed metadata and authType 216 | (next) => { 217 | return self.updateOidcMetadata(self.metadata, next); 218 | }, 219 | ], (waterfallError) => { 220 | // return err or success (err === null) to callback 221 | callback(waterfallError); 222 | }); 223 | }; 224 | 225 | exports.Metadata = Metadata; 226 | -------------------------------------------------------------------------------- /lib/pem.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation 3 | * All Rights Reserved 4 | * MIT License 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this 7 | * software and associated documentation files (the "Software"), to deal in the Software 8 | * without restriction, including without limitation the rights to use, copy, modify, 9 | * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 10 | * permit persons to whom the Software is furnished to do so, subject to the following 11 | * conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be 14 | * included in all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS 19 | * OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT 21 | * OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | */ 23 | 24 | 'use strict'; 25 | 26 | /** 27 | * How to create SAML Logout signing keys on linux/mac 28 | * openssl req -x509 -nodes -days 365 -newkey rsa:2048 -sha1 -keyout private.pem -out public.pem 29 | */ 30 | 31 | 32 | /* 33 | * certificate string and private key string have the following format: 34 | * 1. certificate string: 35 | * "-----BEGIN CERTIFICATE----- content ----END CERTIFICATE-----" 36 | * 2. private key string: 37 | * "-----BEGIN PRIVATE KEY----- content -----END PRIVATE KEY-----" 38 | * 39 | * We define two regex (CERT_REGEX and PRIVATE_KEY_REGEX), in which the content 40 | * we are interested in is captured as a (and the only) capture group. We can 41 | * then use the getFirstCapturingGroup function below to get the content, and 42 | * use the removeRN function below to sanitize the content by removing the \r 43 | * and \n's in the content. 44 | */ 45 | 46 | /* eslint-disable max-len */ 47 | const CERT_REGEX = /-----BEGIN\sCERTIFICATE-----\s+([a-zA-Z0-9+\/=\r\n]+)\s+-----END\sCERTIFICATE-----/; 48 | const PRIVATE_KEY_REGEX = /-----BEGIN\sPRIVATE\sKEY-----\s+([a-zA-Z0-9+\/=\r\n]+)\s+-----END\sPRIVATE\sKEY-----/; 49 | /* eslint-enable max-len */ 50 | 51 | function getFirstCapturingGroup(str, regex) { 52 | if (typeof str !== 'string') { 53 | throw new Error(`'str' must be of type "String", actually is ${typeof str}`); 54 | } 55 | const matches = str.match(regex); 56 | // if str matches the regex (either CERT_REGEX or PRIVATE_KEY_REGEX), then 57 | // matches will always have two groups: 58 | // matches[0]: this is always the entire match, in this case, the certificate/key str 59 | // matches[1]: this is the first (and the only) group we want to capture in the regex, 60 | // which is the certifcate/key content 61 | if (!Array.isArray(matches) || matches.length !== 2) { 62 | return null; 63 | } 64 | return matches[1]; 65 | } 66 | 67 | function removeRN(str) { 68 | if (typeof str !== 'string') { 69 | throw new Error(`'str' must be of type "String", actually is ${typeof str}`); 70 | } 71 | return str.replace(/[\r\n]/g, ''); 72 | } 73 | 74 | exports.getCertificate = function getCertificate(pem) { 75 | return removeRN(getFirstCapturingGroup(pem, CERT_REGEX)); 76 | }; 77 | 78 | exports.getPrivateKey = function getPrivateKey(pem) { 79 | return removeRN(getFirstCapturingGroup(pem, PRIVATE_KEY_REGEX)); 80 | }; 81 | 82 | exports.certToPEM = function certToPEM(cert) { 83 | const BEGIN_CERT = '-----BEGIN CERTIFICATE-----'; 84 | const END_CERT = '-----END CERTIFICATE-----'; 85 | return [BEGIN_CERT] 86 | .concat(cert.match(/.{1,64}/g)) 87 | .concat([END_CERT]) 88 | .join('\n'); 89 | }; 90 | -------------------------------------------------------------------------------- /lib/sessionContentHandler.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation 3 | * All Rights Reserved 4 | * MIT License 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this 7 | * software and associated documentation files (the "Software"), to deal in the Software 8 | * without restriction, including without limitation the rights to use, copy, modify, 9 | * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 10 | * permit persons to whom the Software is furnished to do so, subject to the following 11 | * conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be 14 | * included in all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS 19 | * OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT 21 | * OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | */ 23 | 24 | 'use restrict'; 25 | 26 | const aadutils = require('./aadutils'); 27 | 28 | /* 29 | * the handler for state/nonce/policy 30 | * @maxAmout - the max amount of {state: x, nonce: x, policy: x, timeStamp: x} tuples you want to save in the session 31 | * @maxAge - when a tuple in session expires in seconds 32 | */ 33 | function SessionContentHandler(maxAmount, maxAge) { 34 | if (!maxAmount || (typeof maxAmount !== 'number' || maxAmount <= 0 || maxAmount % 1 !== 0)) 35 | throw new Error('SessionContentHandler: maxAmount must be a positive integer'); 36 | if (!maxAge || (typeof maxAge !== 'number' || maxAge <= 0)) 37 | throw new Error('SessionContentHandler: maxAge must be a positive number'); 38 | this.maxAge = maxAge; // seconds 39 | this.maxAmount = maxAmount; 40 | } 41 | 42 | SessionContentHandler.prototype.findAndDeleteTupleByState = function(req, sessionKey, stateToFind) { 43 | if (!req.session) 44 | throw new Error('OIDC strategy requires session support. Did you forget to use session middleware such as express-session?'); 45 | 46 | // the array in session 47 | var array = req.session[sessionKey] && req.session[sessionKey]['content']; 48 | if (!array) 49 | array = []; 50 | 51 | // remove the expired tuples in array 52 | aadutils.processArray(array, this.maxAmount, this.maxAge); 53 | 54 | // find the tuple by state value 55 | var tuple = aadutils.findAndDeleteTupleByState(array, stateToFind); 56 | 57 | // clear empty array, and clear the session if there is nothing inside 58 | if (req.session[sessionKey] && array.length === 0) 59 | delete req.session[sessionKey]['content']; 60 | if (req.session[sessionKey] && Object.keys(req.session[sessionKey]).length === 0) 61 | delete req.session[sessionKey]; 62 | 63 | return tuple; 64 | }; 65 | 66 | SessionContentHandler.prototype.add = function(req, sessionKey, tupleToAdd) { 67 | if (!req.session) 68 | req.session = {}; 69 | if (!req.session[sessionKey]) 70 | req.session[sessionKey] = {}; 71 | if (!req.session[sessionKey]['content']) 72 | req.session[sessionKey]['content'] = []; 73 | 74 | var array = req.session[sessionKey]['content']; 75 | aadutils.processArray(array, this.maxAmount-1, this.maxAge); 76 | 77 | array.push(tupleToAdd); 78 | }; 79 | 80 | exports.SessionContentHandler = SessionContentHandler; 81 | -------------------------------------------------------------------------------- /lib/validator.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation 3 | * All Rights Reserved 4 | * MIT License 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this 7 | * software and associated documentation files (the "Software"), to deal in the Software 8 | * without restriction, including without limitation the rights to use, copy, modify, 9 | * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 10 | * permit persons to whom the Software is furnished to do so, subject to the following 11 | * conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be 14 | * included in all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS 19 | * OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT 21 | * OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | */ 23 | 24 | 'use strict'; 25 | 26 | const UrlValidator = require('valid-url'); 27 | 28 | const types = {}; 29 | 30 | function Validator(config) { 31 | this.config = config; 32 | } 33 | 34 | Validator.prototype.validate = function validate(options) { 35 | const opts = options || {}; 36 | 37 | Object.keys(this.config).forEach((item) => { 38 | if (!this.config.hasOwnProperty(item)) { 39 | throw new TypeError(`Missing value for ${item}`); 40 | } 41 | const type = this.config[item]; 42 | if (!type) { 43 | return; // no need to validate 44 | } 45 | const checker = types[type]; 46 | if (!checker) { // missing required checker 47 | throw new TypeError(`No handler to validate type ${type} for item ${item}`); 48 | } 49 | 50 | if (!checker.validate(opts[item])) { 51 | throw new TypeError(`Invalid value for ${item}.${checker.error}`); 52 | } 53 | }); 54 | }; 55 | 56 | Validator.isNonEmpty = 'isNonEmpty'; 57 | types.isNonEmpty = { 58 | validate: (value) => { 59 | return value !== '' && value !== undefined && value !== null; 60 | }, 61 | error: 'The value cannot be empty', 62 | }; 63 | 64 | Validator.isTypeLegal = 'isTypeLegal'; 65 | types.isTypeLegal = { 66 | validate: (value) => { 67 | return value === 'id_token' || value === 'id_token code' || value === 'code id_token' || value === 'code'; 68 | }, 69 | error: 'The responseType: must be either id_token, id_token code, code id_token or code.', 70 | }; 71 | 72 | Validator.isModeLegal = 'isModeLegal'; 73 | types.isModeLegal = { 74 | validate: (value) => { 75 | return value === 'query' || value === 'form_post'; 76 | }, 77 | error: 'The responseMode: must be either query or form_post.', 78 | }; 79 | 80 | Validator.isURL = 'isURL'; 81 | types.isURL = { 82 | validate: (value) => { 83 | return UrlValidator.isHttpUri(value) || UrlValidator.isHttpsUri(value); 84 | }, 85 | error: 'The URL must be valid and be https:// or http://', 86 | }; 87 | 88 | Validator.isHttpURL = 'isHttpURL'; 89 | types.isHttpURL = { 90 | validate: (value) => { 91 | return UrlValidator.isHttpUri(value); 92 | }, 93 | error: 'The URL must be valid and be http://', 94 | }; 95 | 96 | Validator.isHttpsURL = 'isHttpsURL'; 97 | types.isHttpsURL = { 98 | validate: (value) => { 99 | return UrlValidator.isHttpsUri(value); 100 | }, 101 | error: 'The URL must be valid and be https://', 102 | }; 103 | 104 | Validator.isHttpsURLIfExists = 'isHttpsURLIfExists'; 105 | types.isHttpsURLIfExists = { 106 | validate: (value) => { 107 | if (value) 108 | return UrlValidator.isHttpsUri(value); 109 | else 110 | return true; 111 | }, 112 | error: 'The URL must be valid and be https://', 113 | }; 114 | 115 | exports.Validator = Validator; 116 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "passport-azure-ad", 3 | "version": "4.3.0", 4 | "license": "MIT", 5 | "keywords": [ 6 | "azure active directory", 7 | "aad", 8 | "adfs", 9 | "sso", 10 | "oidc", 11 | "bearer", 12 | "shibboleth" 13 | ], 14 | "description": "OIDC and Bearer Passport strategies for Azure Active Directory", 15 | "author": { 16 | "name": "azuread", 17 | "email": "nugetaad@microsoft.com", 18 | "url": "http://microsoft.com/" 19 | }, 20 | "repository": { 21 | "type": "git", 22 | "url": "git@github.com:AzureAD/passport-azure-ad.git" 23 | }, 24 | "main": "./lib", 25 | "devDependencies": { 26 | "chai": "2.x.x", 27 | "chai-passport-strategy": "1.x.x", 28 | "grunt": "^1.0.1", 29 | "grunt-contrib-nodeunit": "^2.1.0", 30 | "grunt-mocha-test": "^0.12.7", 31 | "mocha": "^5.2.0", 32 | "nodeunit": "^0.11.3" 33 | }, 34 | "dependencies": { 35 | "async": "^1.5.2", 36 | "base64url": "^3.0.0", 37 | "bunyan": "^1.8.14", 38 | "cache-manager": "2.10.2", 39 | "https-proxy-agent": "^2.2.2", 40 | "jwk-to-pem": "^2.0.4", 41 | "jws": "^3.1.3", 42 | "lodash": "^4.11.2", 43 | "oauth": "0.9.15", 44 | "passport": "^0.3.2", 45 | "request": "^2.72.0", 46 | "valid-url": "^1.0.6" 47 | }, 48 | "scripts": { 49 | "test": "grunt run_tests" 50 | }, 51 | "engines": { 52 | "node": ">= 8.0.0" 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /test/Chai-passport_test/aadutils_test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation 3 | * All Rights Reserved 4 | * MIT License 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this 7 | * software and associated documentation files (the 'Software'), to deal in the Software 8 | * without restriction, including without limitation the rights to use, copy, modify, 9 | * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 10 | * permit persons to whom the Software is furnished to do so, subject to the following 11 | * conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be 14 | * included in all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 17 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS 19 | * OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT 21 | * OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | */ 23 | 24 | 'use strict'; 25 | 26 | var chai = require('chai'); 27 | var expect = chai.expect; 28 | var aadutils = require('../../lib/aadutils'); 29 | 30 | const TEST_TIMEOUT = 1000000; // 1000 seconds 31 | 32 | describe('uid test', function() { 33 | this.timeout(TEST_TIMEOUT); 34 | 35 | it('should return an id with the required length and no url unsafe characters', function(done) { 36 | // generate and test uid 10 times 37 | 38 | for (let i = 0; i < 10; i++) { 39 | let uid = aadutils.uid(32); 40 | let uid_url_safe = uid.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, ''); 41 | 42 | expect(uid.length).to.equal(32); 43 | expect(uid).to.equal(uid_url_safe); 44 | } 45 | 46 | for (let i = 0; i < 10; i++) { 47 | let uid = aadutils.uid(24); 48 | let uid_url_safe = uid.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, ''); 49 | 50 | expect(uid.length).to.equal(24); 51 | expect(uid).to.equal(uid_url_safe); 52 | } 53 | 54 | done(); 55 | }); 56 | }); 57 | 58 | describe('concatUrl test', function() { 59 | it('should generate a valid url if a query parameter is already in place', function(done) { 60 | const currentUrl = 'http://example.com?foo=bar'; 61 | const newUrl = aadutils.concatUrl(currentUrl, ['bar=foo']); 62 | expect(newUrl).to.equal(`${currentUrl}&bar=foo`); 63 | done(); 64 | }); 65 | 66 | it('should generate a valid url if no query parameter is present', function(done) { 67 | const currentUrl = 'http://example.com'; 68 | const newUrl = aadutils.concatUrl(currentUrl, 'bar=foo'); 69 | expect(newUrl).to.equal(`${currentUrl}?bar=foo`); 70 | done(); 71 | }); 72 | 73 | it('should return the bare url if no additional arguments are present', function(done) { 74 | const currentUrl = 'http://example.com'; 75 | const newUrl = aadutils.concatUrl(currentUrl); 76 | expect(newUrl).to.equal(currentUrl); 77 | done(); 78 | }); 79 | 80 | it('should return parseable query parameters if no url is present', function(done) { 81 | const parameters = ['bar=foo&foo=bar']; 82 | const newUrl = aadutils.concatUrl(undefined, parameters) 83 | expect(newUrl).to.equal(`?${parameters}`); 84 | done(); 85 | }); 86 | }) 87 | -------------------------------------------------------------------------------- /test/Chai-passport_test/b2c_oidc_incoming_request_test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation 3 | * All Rights Reserved 4 | * MIT License 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this 7 | * software and associated documentation files (the "Software"), to deal in the Software 8 | * without restriction, including without limitation the rights to use, copy, modify, 9 | * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 10 | * permit persons to whom the Software is furnished to do so, subject to the following 11 | * conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be 14 | * included in all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS 19 | * OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT 21 | * OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | */ 23 | 24 | /* eslint-disable no-new */ 25 | 26 | 'use restrict'; 27 | 28 | var chai = require('chai'); 29 | var url = require('url'); 30 | var OIDCStrategy = require('../../lib/index').OIDCStrategy; 31 | 32 | chai.use(require('chai-passport-strategy')); 33 | 34 | const TEST_TIMEOUT = 1000000; // 1000 seconds 35 | 36 | // Mock options required to create a OIDC strategy 37 | var options = { 38 | redirectUrl: 'https://returnURL', 39 | clientID: 'my_client_id', 40 | clientSecret: 'my_client_secret', 41 | identityMetadata: 'https://login.microsoftonline.com/xxx.onmicrosoft.com/v2.0/.well-known/openid-configuration', 42 | responseType: 'id_token', 43 | responseMode: 'form_post', 44 | validateIssuer: true, 45 | passReqToCallback: false, 46 | isB2C: true, 47 | loggingNoPII: false, 48 | sessionKey: 'my_key' //optional sessionKey 49 | }; 50 | 51 | var testStrategy = new OIDCStrategy(options, function(profile, done) {}); 52 | 53 | // Mock `setOptions` 54 | testStrategy.setOptions = function(params, oauthConfig, optionsToValidate, done) { 55 | oauthConfig.authorization_endpoint = 'https://login.microsoftonline.com/sijun1b2c.onmicrosoft.com/oauth2/v2.0/authorize?p=b2c_1_signin'; 56 | oauthConfig.token_endpoint = 'https://login.microsoftonline.com/sijun1b2c.onmicrosoft.com/oauth2/v2.0/token?p=b2c_1_signin'; 57 | 58 | done(); 59 | }; 60 | 61 | 62 | describe('OIDCStrategy incoming state and nonce checking', function() { 63 | this.timeout(TEST_TIMEOUT); 64 | 65 | var redirectUrl; 66 | var request; 67 | var challenge; 68 | 69 | var testPrepare = function(policy) { 70 | return function(done) { 71 | chai.passport 72 | .use(testStrategy) 73 | .fail(function(c) { challenge = c; done(); }) 74 | .redirect(function(u) {redirectUrl = u; done(); }) 75 | .req(function(req) { 76 | request = req; 77 | req.session = {}; 78 | req.query = {p: policy}; 79 | }) 80 | .authenticate({}); 81 | }; 82 | }; 83 | 84 | describe('state/nonce/policy checking', function() { 85 | before(testPrepare('B2C_1_signin')); 86 | 87 | it('should have the same state/nonce/policy', function() { 88 | var u = url.parse(redirectUrl, true); 89 | chai.expect(request.session['my_key']['content'][0]['state']).to.equal(u.query.state); 90 | chai.expect(request.session['my_key']['content'][0]['nonce']).to.equal(u.query.nonce); 91 | // policy should be changed to lower case 92 | chai.expect(request.session['my_key']['content'][0]['policy']).to.equal('b2c_1_signin'); 93 | }); 94 | }); 95 | 96 | describe('missing policy', function() { 97 | before(testPrepare(null)); 98 | 99 | it('should fail if policy is missing', function() { 100 | chai.expect(challenge).to.equal('In collectInfoFromReq: policy is missing'); 101 | }); 102 | }); 103 | 104 | describe('wrong policy', function() { 105 | before(testPrepare('wrong_policy_not_starting_with_b2c_1_')); 106 | 107 | it('should fail if policy is wrong', function() { 108 | chai.expect(challenge).to.equal('In _flowInitializationHandler: the given policy wrong_policy_not_starting_with_b2c_1_ given in the request is invalid'); 109 | }); 110 | }); 111 | }); 112 | 113 | describe('OIDCStrategy error flow checking', function() { 114 | this.timeout(TEST_TIMEOUT); 115 | 116 | var challenge; 117 | 118 | var testPrepare = function() { 119 | return function(done) { 120 | chai.passport 121 | .use(testStrategy) 122 | .fail(function(c) { challenge = c; done(); }) 123 | .req(function(req) { 124 | var time = Date.now(); 125 | req.session = {'my_key': {'content': [{'state': 'my_state', 'nonce': 'my_nonce', 'policy': 'b2c_1_signin', 'timeStamp': time}]}}; 126 | req.query = {}; 127 | req.body = {state: 'my_state', error: 'my_error'}; 128 | }) 129 | .authenticate({}); 130 | }; 131 | }; 132 | 133 | describe('error checking', function() { 134 | before(testPrepare()); 135 | 136 | it('should have the same error', function() { 137 | chai.expect(challenge).to.equal('my_error'); 138 | }); 139 | }); 140 | }); 141 | 142 | describe('OIDCStrategy token in request checking', function() { 143 | this.timeout(TEST_TIMEOUT); 144 | 145 | var challenge; 146 | 147 | var testPrepare = function(access_token, refresh_token, query_or_body) { 148 | return function(done) { 149 | chai.passport 150 | .use(testStrategy) 151 | .fail(function(c) { 152 | challenge = c; done(); 153 | }) 154 | .req(function(req) { 155 | req.query = {}; 156 | 157 | if (query_or_body == 'query') 158 | req.query = {'access_token' : access_token, 'refresh_token' : refresh_token}; 159 | else if (query_or_body == 'body') 160 | req.body = {'access_token' : access_token, 'refresh_token' : refresh_token}; 161 | }) 162 | .authenticate({}); 163 | }; 164 | }; 165 | 166 | describe('access_token in query', function() { 167 | before(testPrepare('my_access_token', null, 'query')); 168 | 169 | it('should fail', function() { 170 | chai.expect(challenge).to.equal('In collectInfoFromReq: neither access token nor refresh token is expected in the incoming request'); 171 | }); 172 | }); 173 | describe('access_token in body', function() { 174 | before(testPrepare('my_access_token', null, 'body')); 175 | 176 | it('should fail', function() { 177 | chai.expect(challenge).to.equal('In collectInfoFromReq: neither access token nor refresh token is expected in the incoming request'); 178 | }); 179 | }); 180 | describe('refresh_token in query', function() { 181 | before(testPrepare('my_refresh_token', null, 'query')); 182 | 183 | it('should fail', function() { 184 | chai.expect(challenge).to.equal('In collectInfoFromReq: neither access token nor refresh token is expected in the incoming request'); 185 | }); 186 | }); 187 | describe('refresh_token in body', function() { 188 | before(testPrepare('my_refresh_token', null, 'body')); 189 | 190 | it('should fail', function() { 191 | chai.expect(challenge).to.equal('In collectInfoFromReq: neither access token nor refresh token is expected in the incoming request'); 192 | }); 193 | }); 194 | }); 195 | -------------------------------------------------------------------------------- /test/Chai-passport_test/bearer_flow_test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation 3 | * All Rights Reserved 4 | * MIT License 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this 7 | * software and associated documentation files (the "Software"), to deal in the Software 8 | * without restriction, including without limitation the rights to use, copy, modify, 9 | * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 10 | * permit persons to whom the Software is furnished to do so, subject to the following 11 | * conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be 14 | * included in all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS 19 | * OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT 21 | * OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | */ 23 | 24 | /* eslint-disable no-new */ 25 | 26 | 'use strict'; 27 | 28 | var chai = require('chai'); 29 | chai.use(require('chai-passport-strategy')); 30 | var BearerStrategy = require('../../lib/index').BearerStrategy; 31 | 32 | const TEST_TIMEOUT = 1000000; // 1000 seconds 33 | 34 | var PemKey = 35 | "-----BEGIN RSA PUBLIC KEY-----\n" + 36 | "MIIBCgKCAQEAvbcFrj193Gm6zeo5e2/y54Jx49sIgScv+2JO+n6NxNqQaKVnMkHc\n" + 37 | "z+S1j2FfpFngotwGMzZIKVCY1SK8SKZMFfRTU3wvToZITwf3W1Qq6n+h+abqpyJT\n" + 38 | "aqIcfhA0d6kEAM5NsQAKhfvw7fre1QicmU9LWVWUYAayLmiRX6o3tktJq6H58pUz\n" + 39 | "Ttx/D0Dprnx6z5sW+uiMipLXbrgYmOez7htokJVgDg8w+yDFCxZNo7KVueUkLkxh\n" + 40 | "NjYGkGfnt18s7ZW036WoTmdaQmW4CChf/o4TLE5VyGpYWm7I/+nV95BBvwlzokVV\n" + 41 | "KzveKf3l5UU3c6PkGy+BB3E/ChqFm6sPWwIDAQAB\n" + 42 | "-----END RSA PUBLIC KEY-----"; 43 | 44 | var access_token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6IlliUkFRUlljRV9tb3RXVkpLSHJ3TEJiZF85cyIsImtpZCI6IlliUkFRUlljRV9tb3RXVkpLSHJ3TEJiZF85cyJ9.eyJhdWQiOiJzcG46NjUxNGE4Y2EtZDllNC00MTU1LWIyOTItNjUyNTgzOThmM2FhIiwiaXNzIjoiaHR0cHM6Ly9zdHMud2luZG93cy5uZXQvMjY4ZGExYTEtOWRiNC00OGI5LWIxZmUtNjgzMjUwYmE5MGNjLyIsImlhdCI6MTQ2NzMxMTI0OCwibmJmIjoxNDY3MzExMjQ4LCJleHAiOjE0NjczMTUxNDgsImFjciI6IjEiLCJhbXIiOlsicHdkIl0sImFwcGlkIjoiYmViZmFkNTctZWZkNy00MTliLWI3NGItNGI5ZGFiN2JkNDcwIiwiYXBwaWRhY3IiOiIxIiwiZmFtaWx5X25hbWUiOiJvbmUiLCJnaXZlbl9uYW1lIjoicm9ib3QiLCJpcGFkZHIiOiIxMzEuMTA3LjE2MC4yMjYiLCJuYW1lIjoicm9ib3QgMSIsIm9pZCI6Ijc5MTJmZTdiLWI1YWItNDI1Yi1iYjFmLTBlODNiOTlmY2E3ZiIsInNjcCI6InVzZXJfaW1wZXJzb25hdGlvbiIsInN1YiI6Ikt1Mi1GdDlsWTlpMkJ2ZmhtcTQxNjZaSDNrV0g0V1h0bXpHOU0tOE1GYWMiLCJ0aWQiOiIyNjhkYTFhMS05ZGI0LTQ4YjktYjFmZS02ODMyNTBiYTkwY2MiLCJ1bmlxdWVfbmFtZSI6InJvYm90QHNpanVuLm9ubWljcm9zb2Z0LmNvbSIsInVwbiI6InJvYm90QHNpanVuLm9ubWljcm9zb2Z0LmNvbSIsInZlciI6IjEuMCJ9.VTg8AqnbSzfC7nUmf3xKnNrS_3BcOSGqz_CBPi6Th2piwNc--3Aq_K6SOt2QlbP7yni8IOqeY2ooqDgj0CvcvV3HHHHFatS7X8Kppg4z35lB4b67DJuIeHgCYYBR75qMVC1z5n4dgYGoNE-JNvlZZmaeHnrO8FAmQBKJUOrIyCNpoBjIsUXgXJKTPdL7HQL9nFz6h9sUmvbvpwqk1NgfmfTsJ0wHuSNHjHmryZ7vGnnjJHUC1zQmo9nesF0t7ad2Gk2RdlU93FbcZEW0hFE5Rtu0SbjOZAQdDVsBj_Voi7iQ_Kr-CnC14vuZ5kE9ACSMf2VG5wfcg6z4pyQdw-LpjQ"; 45 | 46 | var options = { 47 | identityMetadata: 'https://login.microsoftonline.com/xxx.onmicrosoft.com/.well-known/openid-configuration', 48 | clientID: '6514a8ca-d9e4-4155-b292-65258398f3aa', 49 | validateIssuer: true, 50 | passReqToCallback: false, 51 | ignoreExpiration: true, 52 | }; 53 | 54 | var newStrategy = function(_options) { 55 | _options = Object.assign({}, options, _options) 56 | 57 | var strategy = new BearerStrategy(_options, function(req, token, done) { 58 | if (!done) { 59 | done = token; 60 | token = req; 61 | done(null, token.oid); 62 | } 63 | else { 64 | done(null, req); 65 | } 66 | }); 67 | 68 | strategy.loadMetadata = function(params, next) { 69 | var metadata = {oidc: {issuer: 'https://sts.windows.net/268da1a1-9db4-48b9-b1fe-683250ba90cc/', algorithms: ['RS256']}}; 70 | metadata.generateOidcPEM = ()=> { return PemKey; }; 71 | return next(null, metadata); 72 | }; 73 | 74 | return strategy; 75 | }; 76 | 77 | 78 | describe('bearer flow', function() { 79 | this.timeout(TEST_TIMEOUT); 80 | 81 | context('passReqToCallback = true', function() { 82 | var req = null; 83 | 84 | before(function (done) { 85 | chai.passport 86 | .use(newStrategy({ passReqToCallback: true })) 87 | .success(function (r, u, i) { 88 | req = r; 89 | done(); 90 | }) 91 | .req(function (req) { 92 | req.headers.authorization = 'Bearer ' + access_token; 93 | }) 94 | .authenticate(); 95 | }); 96 | 97 | it('should cause req to be passed to callback', function() { 98 | chai.expect(req.url).to.equal('/'); 99 | }); 100 | }); 101 | 102 | context('with valid params', function() { 103 | var challenge = null; 104 | var user = null; 105 | 106 | before(function (done) { 107 | chai.passport 108 | .use(newStrategy()) 109 | .success(function (u, i) { 110 | user = u; 111 | done(); 112 | }) 113 | .req(function (req) { 114 | req.headers.authorization = 'Bearer ' + access_token; 115 | }) 116 | .fail(function (c) { 117 | challenge = c; 118 | done(); 119 | }) 120 | .authenticate(); 121 | }); 122 | 123 | it('should succeed', function() { 124 | chai.expect(user).to.equal('7912fe7b-b5ab-425b-bb1f-0e83b99fca7f'); 125 | }); 126 | }); 127 | }); 128 | -------------------------------------------------------------------------------- /test/Chai-passport_test/bearer_token_in_req_test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation 3 | * All Rights Reserved 4 | * MIT License 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this 7 | * software and associated documentation files (the "Software"), to deal in the Software 8 | * without restriction, including without limitation the rights to use, copy, modify, 9 | * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 10 | * permit persons to whom the Software is furnished to do so, subject to the following 11 | * conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be 14 | * included in all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS 19 | * OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT 21 | * OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | */ 23 | 24 | /* eslint-disable no-new */ 25 | 26 | 'use strict'; 27 | 28 | var chai = require('chai'); 29 | chai.use(require('chai-passport-strategy')); 30 | var BearerStrategy = require('../../lib/index').BearerStrategy; 31 | 32 | const TEST_TIMEOUT = 1000000; // 1000 seconds 33 | 34 | var options = { 35 | identityMetadata: 'https://login.microsoftonline.com/xxx.onmicrosoft.com/.well-known/openid-configuration', 36 | clientID: 'spn:6514a8ca-d9e4-4155-b292-65258398f3aa', 37 | validateIssuer: true, 38 | passReqToCallback: false, 39 | loggingNoPII: false 40 | }; 41 | 42 | var strategy = new BearerStrategy(options, function(token, done) { 43 | if (token === 'good_token') 44 | return done(null, {id: 'Mr noname'}, 'authentication successful'); 45 | return done(null, false, 'access token is invalid'); 46 | }); 47 | 48 | strategy.jwtVerify = function(req, token, metadata, optionsToValidate, done) { this._verify(token, done); }; 49 | 50 | strategy.loadMetadata = function(params, next) { 51 | var metadata = {oidc: {issuer: 'https://sts.windows.net/268da1a1-9db4-48b9-b1fe-683250ba90cc/', algorithms: ['RS256']}}; 52 | return next(null, metadata); 53 | }; 54 | 55 | describe('token mock test', function() { 56 | this.timeout(TEST_TIMEOUT); 57 | 58 | var challenge = ''; 59 | var success_user = ''; 60 | var success_info = ''; 61 | 62 | var beforeFunc = function(token, in_header, in_body, in_query, bad_header, lowercase_bearer) { 63 | return function(done) { 64 | chai.passport 65 | .use(strategy) 66 | .fail(function(c) { 67 | challenge = c; done(); 68 | }) 69 | .req(function(req) { 70 | if (token && in_header) { 71 | if (bad_header) 72 | req.headers.authorization = token; // missing 'Bearer' 73 | else if (lowercase_bearer) 74 | req.headers.authorization = 'bearer ' + token; 75 | else 76 | req.headers.authorization = 'Bearer ' + token; 77 | } 78 | if (token && in_query) { 79 | req.query = {}; 80 | req.query.access_token = token; 81 | } 82 | if (token && in_body) { 83 | req.body = {}; 84 | req.body.access_token = token; 85 | } 86 | 87 | // reset 88 | challenge = success_user = success_info = ''; 89 | }) 90 | .success(function(user, info) { 91 | success_user = user.id; 92 | success_info = info; 93 | done(); 94 | }) 95 | .authenticate(); 96 | }; 97 | }; 98 | 99 | describe('should fail with no token', function() { 100 | before(beforeFunc()); 101 | 102 | it('should fail with challenge', function() { 103 | chai.expect(challenge).to.be.a.string; 104 | chai.expect(challenge).to.equal('token is not found'); 105 | }) 106 | }); 107 | 108 | describe('should fail with invalid token', function() { 109 | before(beforeFunc('invalid_token', true)); 110 | 111 | it('should fail with challenge', function() { 112 | chai.expect(challenge).to.be.a.string; 113 | chai.expect(challenge).to.equal('error: invalid_token, error description: access token is invalid'); 114 | }); 115 | }); 116 | 117 | describe('should succeed with good token in header', function() { 118 | before(beforeFunc('good_token', true)); 119 | 120 | it('should succeed', function() { 121 | chai.expect(success_user).to.equal('Mr noname'); 122 | chai.expect(success_info).to.equal('authentication successful'); 123 | }); 124 | }); 125 | 126 | describe('should succeed with good token in body', function() { 127 | before(beforeFunc('good_token', false, true)); 128 | 129 | it('should succeed', function() { 130 | chai.expect(success_user).to.equal('Mr noname'); 131 | chai.expect(success_info).to.equal('authentication successful'); 132 | }); 133 | }); 134 | 135 | describe('should fail with token passed in query', function() { 136 | before(beforeFunc('good_token', false, false, true)); 137 | 138 | it('should fail', function() { 139 | chai.expect(challenge).to.equal('In Strategy.prototype.authenticate: access_token should be passed in request header or body. query is unsupported'); 140 | }); 141 | }); 142 | 143 | describe('should fail with token passed in both header and body', function() { 144 | before(beforeFunc('good_token', true, true, false)); 145 | 146 | it('should fail', function() { 147 | chai.expect(challenge).to.equal('In Strategy.prototype.authenticate: access_token cannot be passed in both request header and body'); 148 | }); 149 | }); 150 | 151 | describe('should fail with bad header', function() { 152 | before(beforeFunc('good_token', true, false, false, true)); 153 | 154 | it('should fail', function() { 155 | chai.expect(challenge).to.equal('token is not found'); 156 | }); 157 | }); 158 | 159 | describe('should succeed with lower case bearer', function() { 160 | before(beforeFunc('good_token', true, false, false, false, true)); 161 | 162 | it('should succeed', function() { 163 | chai.expect(success_user).to.equal('Mr noname'); 164 | chai.expect(success_info).to.equal('authentication successful'); 165 | }); 166 | }); 167 | }); 168 | 169 | 170 | -------------------------------------------------------------------------------- /test/Chai-passport_test/clock_skew_test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation 3 | * All Rights Reserved 4 | * MIT License 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this 7 | * software and associated documentation files (the "Software"), to deal in the Software 8 | * without restriction, including without limitation the rights to use, copy, modify, 9 | * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 10 | * permit persons to whom the Software is furnished to do so, subject to the following 11 | * conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be 14 | * included in all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS 19 | * OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT 21 | * OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | */ 23 | 24 | /* eslint-disable no-new */ 25 | 26 | 'use restrict'; 27 | 28 | var chai = require('chai'); 29 | var url = require('url'); 30 | var OIDCStrategy = require('../../lib/index').OIDCStrategy; 31 | var BearerStrategy = require('../../lib/index').BearerStrategy; 32 | var CONSTANTS = require('../../lib/constants'); 33 | 34 | chai.use(require('chai-passport-strategy')); 35 | 36 | const TEST_TIMEOUT = 1000000; // 1000 seconds 37 | 38 | var OIDC_options = { 39 | redirectUrl: 'https://returnURL', 40 | clientID: 'my_client_id', 41 | clientSecret: 'my_client_secret', 42 | identityMetadata: 'https://login.microsoftonline.com/xxx.onmicrosoft.com/.well-known/openid-configuration', 43 | responseType: 'id_token', 44 | responseMode: 'form_post', 45 | validateIssuer: true, 46 | passReqToCallback: false, 47 | }; 48 | 49 | var Bearer_options = { 50 | identityMetadata: 'https://login.microsoftonline.com/xxx.onmicrosoft.com/.well-known/openid-configuration', 51 | clientID: 'my_client_id', 52 | validateIssuer: true, 53 | passReqToCallback: false, 54 | ignoreExpiration: true, 55 | }; 56 | 57 | describe('OIDCStrategy clock skew test', function() { 58 | this.timeout(TEST_TIMEOUT); 59 | 60 | it('should have the default clock skew if none is given', function(done) { 61 | var testStrategy = new OIDCStrategy(OIDC_options, function(profile, done) {}); 62 | chai.expect(testStrategy._options.clockSkew).to.equal(CONSTANTS.CLOCK_SKEW); 63 | done(); 64 | }); 65 | 66 | it('should have the given clock skew', function(done) { 67 | OIDC_options.clockSkew = 123; 68 | var testStrategy = new OIDCStrategy(OIDC_options, function(profile, done) {}); 69 | chai.expect(testStrategy._options.clockSkew).to.equal(123); 70 | done(); 71 | }); 72 | 73 | it('should throw with negative clock skew', function(done) { 74 | OIDC_options.clockSkew = -123; 75 | chai.expect(() => { 76 | new OIDCStrategy(OIDC_options, function(profile, done) {}); 77 | }).to.throw('clockSkew must be a positive integer'); 78 | done(); 79 | }); 80 | 81 | it('should throw with non-integer clock skew', function(done) { 82 | OIDC_options.clockSkew = 1.23; 83 | chai.expect(() => { 84 | new OIDCStrategy(OIDC_options, function(profile, done) {}); 85 | }).to.throw('clockSkew must be a positive integer'); 86 | done(); 87 | }); 88 | }); 89 | 90 | describe('BearerStrategy clock skew test', function() { 91 | this.timeout(TEST_TIMEOUT); 92 | 93 | it('should have the default clock skew if none is given', function(done) { 94 | var testStrategy = new BearerStrategy(Bearer_options, function(profile, done) {}); 95 | chai.expect(testStrategy._options.clockSkew).to.equal(CONSTANTS.CLOCK_SKEW); 96 | done(); 97 | }); 98 | 99 | it('should have the given clock skew', function(done) { 100 | Bearer_options.clockSkew = 123; 101 | var testStrategy = new BearerStrategy(Bearer_options, function(profile, done) {}); 102 | chai.expect(testStrategy._options.clockSkew).to.equal(123); 103 | done(); 104 | }); 105 | 106 | it('should throw with negative clock skew', function(done) { 107 | Bearer_options.clockSkew = -123; 108 | chai.expect(() => { 109 | new BearerStrategy(Bearer_options, function(profile, done) {}); 110 | }).to.throw('clockSkew must be a positive integer'); 111 | done(); 112 | }); 113 | 114 | it('should throw with non-integer clock skew', function(done) { 115 | Bearer_options.clockSkew = 1.23; 116 | chai.expect(() => { 117 | new BearerStrategy(Bearer_options, function(profile, done) {}); 118 | }).to.throw('clockSkew must be a positive integer'); 119 | done(); 120 | }); 121 | }); 122 | 123 | -------------------------------------------------------------------------------- /test/Chai-passport_test/constants_test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation 3 | * All Rights Reserved 4 | * MIT License 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this 7 | * software and associated documentation files (the 'Software'), to deal in the Software 8 | * without restriction, including without limitation the rights to use, copy, modify, 9 | * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 10 | * permit persons to whom the Software is furnished to do so, subject to the following 11 | * conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be 14 | * included in all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 17 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS 19 | * OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT 21 | * OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | */ 23 | 24 | 'use strict'; 25 | 26 | var chai = require('chai'); 27 | var expect = chai.expect; 28 | var CONSTANTS = require('../../lib/constants'); 29 | 30 | const TEST_TIMEOUT = 1000000; // 1000 seconds 31 | 32 | describe('policy checking', function() { 33 | this.timeout(TEST_TIMEOUT); 34 | 35 | it('should pass with good policy name', function(done) { 36 | expect(CONSTANTS.POLICY_REGEX.test('b2c_1_signin')).to.equal(true); 37 | expect(CONSTANTS.POLICY_REGEX.test('B2C_1_SIGNIN')).to.equal(true); 38 | expect(CONSTANTS.POLICY_REGEX.test('B2C_1_My.SIGNIN')).to.equal(true); 39 | expect(CONSTANTS.POLICY_REGEX.test('B2C_1_My_SIGNIN')).to.equal(true); 40 | expect(CONSTANTS.POLICY_REGEX.test('B2C_1_My-SIGNIN')).to.equal(true); 41 | expect(CONSTANTS.POLICY_REGEX.test('b2c_1a_signin')).to.equal(true); 42 | expect(CONSTANTS.POLICY_REGEX.test('B2C_1a_SIGNIN')).to.equal(true); 43 | expect(CONSTANTS.POLICY_REGEX.test('B2C_1a_My.SIGNIN')).to.equal(true); 44 | expect(CONSTANTS.POLICY_REGEX.test('B2C_1A_My_SIGNIN')).to.equal(true); 45 | expect(CONSTANTS.POLICY_REGEX.test('B2C_1A_My-SIGNIN')).to.equal(true); 46 | done(); 47 | }); 48 | 49 | it('should fail with bad policy name', function(done) { 50 | expect(CONSTANTS.POLICY_REGEX.test('signin')).to.equal(false); 51 | expect(CONSTANTS.POLICY_REGEX.test('b2c_SIGNIN')).to.equal(false); 52 | expect(CONSTANTS.POLICY_REGEX.test('b2c_1_')).to.equal(false); 53 | expect(CONSTANTS.POLICY_REGEX.test('b2c_1_*SIGNIN')).to.equal(false); 54 | expect(CONSTANTS.POLICY_REGEX.test('signin')).to.equal(false); 55 | expect(CONSTANTS.POLICY_REGEX.test('b2c_a_SIGNIN')).to.equal(false); 56 | expect(CONSTANTS.POLICY_REGEX.test('b2c_1A_')).to.equal(false); 57 | expect(CONSTANTS.POLICY_REGEX.test('b2c_1A_*SIGNIN')).to.equal(false); 58 | done(); 59 | }); 60 | }); 61 | -------------------------------------------------------------------------------- /test/Chai-passport_test/json_web_token_test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation 3 | * All Rights Reserved 4 | * MIT License 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this 7 | * software and associated documentation files (the "Software"), to deal in the Software 8 | * without restriction, including without limitation the rights to use, copy, modify, 9 | * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 10 | * permit persons to whom the Software is furnished to do so, subject to the following 11 | * conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be 14 | * included in all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS 19 | * OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT 21 | * OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | */ 23 | 24 | 'use strict'; 25 | 26 | var chai = require('chai'); 27 | var jws = require('jws'); 28 | var jwt = require('../../lib/jsonWebToken'); 29 | 30 | const TEST_TIMEOUT = 1000000; // 1000 seconds 31 | 32 | const secret = "12345678901234567890123456789012"; // 512 bit symmetric key 33 | 34 | describe('json web token test', function() { 35 | this.timeout(TEST_TIMEOUT); 36 | 37 | var signStream = jws.createSign( 38 | { 39 | 'header': { 'alg': 'HS256', 'typ': 'JWT'}, 40 | 'payload': { 'nbf': Date.now() / 1000, 'exp': Date.now() / 1000 + 300, 'iat': Date.now() / 1000, 'iss': 'https://example.com', 'aud': 'audience' }, 41 | 'secret': secret 42 | }); 43 | 44 | var jwtString = signStream.sign(); // create the corresponding json web token 45 | var options = { audience: 'audience', algorithms: ['HS256'], issuer: 'https://example.com' }; // validation options 46 | 47 | it('should fail with missing sub error', function(done) { 48 | jwt.verify(jwtString, secret, options, (err, token) => { chai.expect(err.message).to.equal('invalid sub value in payload'); done(); }); 49 | }); 50 | 51 | it('should succeed if testing access token', function(done) { 52 | options.isAccessToken = true; 53 | jwt.verify(jwtString, secret, options, (err, token) => { chai.expect(err).to.equal(null); done(); }); 54 | }); 55 | }); 56 | -------------------------------------------------------------------------------- /test/Chai-passport_test/jwe_test.js: -------------------------------------------------------------------------------- 1 | 2 | 'use strict'; 3 | 4 | var chai = require('chai'); 5 | var expect = chai.expect; 6 | var jwe = require('../../lib/jwe'); 7 | var base64url = require('base64url'); 8 | 9 | const TEST_TIMEOUT = 1000000; // 1000 seconds 10 | 11 | var log = { 'info': (msg) => { } }; 12 | 13 | describe('positive test', function() { 14 | this.timeout(TEST_TIMEOUT); 15 | 16 | it('should pass with A128KW and A128GCM', function(done) { 17 | var jwk = [ { "kty": "oct", "kid": "81b20965-8332-43d9-a468-82160ad91ac8", "k": "GZy6sIZ6wl9NJOKB-jnmVQ" } ]; 18 | 19 | var jweString = 'eyJhbGciOiJBMTI4S1ciLCJraWQiOiI4MWIyMDk2NS04MzMyLTQzZDktYTQ2OC04MjE2MGFkOTFhYzgiLCJlbmMiOiJBMTI4R0NNIn0.CBI6oDw8MydIx1IBntf_lQcw2MmJKIQx.Qx0pmsDa8KnJc9Jo.AwliP-KmWgsZ37BvzCefNen6VTbRK3QMA4TkvRkH0tP1bTdhtFJgJxeVmJkLD61A1hnWGetdg11c9ADsnWgL56NyxwSYjU1ZEHcGkd3EkU0vjHi9gTlb90qSYFfeF0LwkcTtjbYKCsiNJQkcIp1yeM03OmuiYSoYJVSpf7ej6zaYcMv3WwdxDFl8REwOhNImk2Xld2JXq6BR53TSFkyT7PwVLuq-1GwtGHlQeg7gDT6xW0JqHDPn_H-puQsmthc9Zg0ojmJfqqFvETUxLAF-KjcBTS5dNy6egwkYtOt8EIHK-oEsKYtZRaa8Z7MOZ7UGxGIMvEmxrGCPeJa14slv2-gaqK0kEThkaSqdYw0FkQZF.ER7MWJZ1FBI_NKvn7Zb1Lw'; 20 | 21 | var decrypted; 22 | jwe.decrypt(jweString, jwk, log, (error, decrypted_token) => { decrypted = decrypted_token; }); 23 | 24 | expect(decrypted).to.equal('You can trust us to stick with you through thick and thin–to the bitter end. And you can trust us to keep any secret of yours–closer than you keep it yourself. But you cannot trust us to let you face trouble alone, and go off without a word. We are your friends, Frodo.'); 25 | done(); 26 | }); 27 | 28 | it('should pass with RSA-OAEP and A256GCM', function(done) { 29 | var jwk = [{ "kty":"RSA", 30 | "n":"oahUIoWw0K0usKNuOR6H4wkf4oBUXHTxRvgb48E-BVvxkeDNjbC4he8rUWcJoZmds2h7M70imEVhRU5djINXtqllXI4DFqcI1DgjT9LewND8MW2Krf3Spsk_ZkoFnilakGygTwpZ3uesH-PFABNIUYpOiN15dsQRkgr0vEhxN92i2asbOenSZeyaxziK72UwxrrKoExv6kc5twXTq4h-QChLOln0_mtUZwfsRaMStPs6mS6XrgxnxbWhojf663tuEQueGC-FCMfra36C9knDFGzKsNa7LZK2djYgyD3JR_MB_4NUJW_TqOQtwHYbxevoJArm-L5StowjzGy-_bq6Gw", 31 | "e":"AQAB", 32 | "d":"kLdtIj6GbDks_ApCSTYQtelcNttlKiOyPzMrXHeI-yk1F7-kpDxY4-WY5NWV5KntaEeXS1j82E375xxhWMHXyvjYecPT9fpwR_M9gV8n9Hrh2anTpTD93Dt62ypW3yDsJzBnTnrYu1iwWRgBKrEYY46qAZIrA2xAwnm2X7uGR1hghkqDp0Vqj3kbSCz1XyfCs6_LehBwtxHIyh8Ripy40p24moOAbgxVw3rxT_vlt3UVe4WO3JkJOzlpUf-KTVI2Ptgm-dARxTEtE-id-4OJr0h-K-VFs3VSndVTIznSxfyrj8ILL6MG_Uv8YAu7VILSB3lOW085-4qE3DzgrTjgyQ", 33 | "p":"1r52Xk46c-LsfB5P442p7atdPUrxQSy4mti_tZI3Mgf2EuFVbUoDBvaRQ-SWxkbkmoEzL7JXroSBjSrK3YIQgYdMgyAEPTPjXv_hI2_1eTSPVZfzL0lffNn03IXqWF5MDFuoUYE0hzb2vhrlN_rKrbfDIwUbTrjjgieRbwC6Cl0", 34 | "q":"wLb35x7hmQWZsWJmB_vle87ihgZ19S8lBEROLIsZG4ayZVe9Hi9gDVCOBmUDdaDYVTSNx_8Fyw1YYa9XGrGnDew00J28cRUoeBB_jKI1oma0Orv1T9aXIWxKwd4gvxFImOWr3QRL9KEBRzk2RatUBnmDZJTIAfwTs0g68UZHvtc", 35 | "dp":"ZK-YwE7diUh0qR1tR7w8WHtolDx3MZ_OTowiFvgfeQ3SiresXjm9gZ5KLhMXvo-uz-KUJWDxS5pFQ_M0evdo1dKiRTjVw_x4NyqyXPM5nULPkcpU827rnpZzAJKpdhWAgqrXGKAECQH0Xt4taznjnd_zVpAmZZq60WPMBMfKcuE", 36 | "dq":"Dq0gfgJ1DdFGXiLvQEZnuKEN0UUmsJBxkjydc3j4ZYdBiMRAy86x0vHCjywcMlYYg4yoC4YZa9hNVcsjqA3FeiL19rk8g6Qn29Tt0cj8qqyFpz9vNDBUfCAiJVeESOjJDZPYHdHY8v1b-o-Z2X5tvLx-TCekf7oxyeKDUqKWjis", 37 | "qi":"VIMpMYbPf47dT1w_zDUXfPimsSegnMOA1zTaX7aGk_8urY6R8-ZW1FxU7AlWAyLWybqq6t16VFd7hQd0y6flUK4SlOydB61gwanOsXGOAOv82cHq0E3eL4HrtZkUuKvnPrMnsUUFlfUdybVzxyjz9JF_XyaY14ardLSjf4L_FNY" 38 | }]; 39 | 40 | var jweString = "eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkEyNTZHQ00ifQ.OKOawDo13gRp2ojaHV7LFpZcgV7T6DVZKTyKOMTYUmKoTCVJRgckCL9kiMT03JGeipsEdY3mx_etLbbWSrFr05kLzcSr4qKAq7YN7e9jwQRb23nfa6c9d-StnImGyFDbSv04uVuxIp5Zms1gNxKKK2Da14B8S4rzVRltdYwam_lDp5XnZAYpQdb76FdIKLaVmqgfwX7XWRxv2322i-vDxRfqNzo_tETKzpVLzfiwQyeyPGLBIO56YJ7eObdv0je81860ppamavo35UgoRdbYaBcoh9QcfylQr66oc6vFWXRcZ_ZT2LawVCWTIy3brGPi6UklfCpIMfIjf7iGdXKHzg.48V1_ALb6US04U3b.5eym8TW_c8SuK0ltJ3rpYIzOeDQz7TALvtu6UG9oMo4vpzs9tX_EFShS8iB7j6jiSdiwkIr3ajwQzaBtQD_A.XFBoMYUZodetZdvTiFvSkQ"; 41 | 42 | var decrypted; 43 | jwe.decrypt(jweString, jwk, log, (error, decrypted_token) => { decrypted = decrypted_token; }); 44 | 45 | expect(decrypted).to.equal('The true sign of intelligence is not knowledge but imagination.'); 46 | done(); 47 | }); 48 | 49 | it('should pass with RSA1_5 and A128CBC-HS256', function(done) { 50 | var jwk = [{"kty":"RSA", 51 | "n":"sXchDaQebHnPiGvyDOAT4saGEUetSyo9MKLOoWFsueri23bOdgWp4Dy1WlUzewbgBHod5pcM9H95GQRV3JDXboIRROSBigeC5yjU1hGzHHyXss8UDprecbAYxknTcQkhslANGRUZmdTOQ5qTRsLAt6BTYuyvVRdhS8exSZEy_c4gs_7svlJJQ4H9_NxsiIoLwAEk7-Q3UXERGYw_75IDrGA84-lA_-Ct4eTlXHBIY2EaV7t7LjJaynVJCpkv4LKjTTAumiGUIuQhrNhZLuF_RJLqHpM2kgWFLU7-VTdL1VbC2tejvcI2BlMkEpk1BzBZI0KQB0GaDWFLN-aEAw3vRw", 52 | "e":"AQAB", 53 | "d":"VFCWOqXr8nvZNyaaJLXdnNPXZKRaWCjkU5Q2egQQpTBMwhprMzWzpR8Sxq1OPThh_J6MUD8Z35wky9b8eEO0pwNS8xlh1lOFRRBoNqDIKVOku0aZb-rynq8cxjDTLZQ6Fz7jSjR1Klop-YKaUHc9GsEofQqYruPhzSA-QgajZGPbE_0ZaVDJHfyd7UUBUKunFMScbflYAAOYJqVIVwaYR5zWEEceUjNnTNo_CVSj-VvXLO5VZfCUAVLgW4dpf1SrtZjSt34YLsRarSb127reG_DUwg9Ch-KyvjT1SkHgUWRVGcyly7uvVGRSDwsXypdrNinPA4jlhoNdizK2zF2CWQ", 54 | "p":"9gY2w6I6S6L0juEKsbeDAwpd9WMfgqFoeA9vEyEUuk4kLwBKcoe1x4HG68ik918hdDSE9vDQSccA3xXHOAFOPJ8R9EeIAbTi1VwBYnbTp87X-xcPWlEPkrdoUKW60tgs1aNd_Nnc9LEVVPMS390zbFxt8TN_biaBgelNgbC95sM", 55 | "q":"uKlCKvKv_ZJMVcdIs5vVSU_6cPtYI1ljWytExV_skstvRSNi9r66jdd9-yBhVfuG4shsp2j7rGnIio901RBeHo6TPKWVVykPu1iYhQXw1jIABfw-MVsN-3bQ76WLdt2SDxsHs7q7zPyUyHXmps7ycZ5c72wGkUwNOjYelmkiNS0", 56 | "dp":"w0kZbV63cVRvVX6yk3C8cMxo2qCM4Y8nsq1lmMSYhG4EcL6FWbX5h9yuvngs4iLEFk6eALoUS4vIWEwcL4txw9LsWH_zKI-hwoReoP77cOdSL4AVcraHawlkpyd2TWjE5evgbhWtOxnZee3cXJBkAi64Ik6jZxbvk-RR3pEhnCs", 57 | "dq":"o_8V14SezckO6CNLKs_btPdFiO9_kC1DsuUTd2LAfIIVeMZ7jn1Gus_Ff7B7IVx3p5KuBGOVF8L-qifLb6nQnLysgHDh132NDioZkhH7mI7hPG-PYE_odApKdnqECHWw0J-F0JWnUd6D2B_1TvF9mXA2Qx-iGYn8OVV1Bsmp6qU", 58 | "qi":"eNho5yRBEBxhGBtQRww9QirZsB66TrfFReG_CcteI1aCneT0ELGhYlRlCtUkTRclIfuEPmNsNDPbLoLqqCVznFbvdB7x-Tl-m0l_eFTj2KiqwGqE9PZB9nNTwMVvH3VRRSLWACvPnSiwP8N5Usy-WRXS-V7TbpxIhvepTfE0NNo" 59 | }]; 60 | 61 | var jweString = "eyJhbGciOiJSU0ExXzUiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0.UGhIOguC7IuEvf_NPVaXsGMoLOmwvc1GyqlIKOK1nN94nHPoltGRhWhw7Zx0-kFm1NJn8LE9XShH59_i8J0PH5ZZyNfGy2xGdULU7sHNF6Gp2vPLgNZ__deLKxGHZ7PcHALUzoOegEI-8E66jX2E4zyJKx-YxzZIItRzC5hlRirb6Y5Cl_p-ko3YvkkysZIFNPccxRU7qve1WYPxqbb2Yw8kZqa2rMWI5ng8OtvzlV7elprCbuPhcCdZ6XDP0_F8rkXds2vE4X-ncOIM8hAYHHi29NX0mcKiRaD0-D-ljQTP-cFPgwCp6X-nZZd9OHBv-B3oWh2TbqmScqXMR4gp_A.AxY8DCtDaGlsbGljb3RoZQ.KDlTtXchhZTGufMYmOYGS4HffxPSUrfmqCHXaI9wOGY.9hH0vgRfYgPnAHOd8stkvw"; 62 | 63 | var decrypted; 64 | jwe.decrypt(jweString, jwk, log, (error, decrypted_token) => { decrypted = decrypted_token; }); 65 | 66 | expect(decrypted).to.equal('Live long and prosper.'); 67 | done(); 68 | }); 69 | 70 | it('should pass with A128KW and A128CBC-HS256', function(done) { 71 | var jwk = [ { 'kty': 'oct', 'k': 'GawgguFyGrWKav7AX4VKUg' }]; 72 | var jweString = 'eyJhbGciOiJBMTI4S1ciLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0.6KB707dM9YTIgHtLvtgWQ8mKwboJW3of9locizkDTHzBC2IlrT1oOQ.AxY8DCtDaGlsbGljb3RoZQ.KDlTtXchhZTGufMYmOYGS4HffxPSUrfmqCHXaI9wOGY.U0m_YmjN04DJvceFICbCVQ'; 73 | 74 | var decrypted; 75 | jwe.decrypt(jweString, jwk, log, (error, decrypted_token) => { decrypted = decrypted_token; }); 76 | 77 | expect(decrypted).to.equal('Live long and prosper.'); 78 | done(); 79 | }) 80 | }); 81 | 82 | describe('negative test', function() { 83 | this.timeout(TEST_TIMEOUT); 84 | 85 | // the following parts are from a valid jwe using A128KW and A128CBC-HS256 86 | var header = 'eyJhbGciOiJBMTI4S1ciLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0'; 87 | var encrypted_key = '6KB707dM9YTIgHtLvtgWQ8mKwboJW3of9locizkDTHzBC2IlrT1oOQ'; 88 | var iv = 'AxY8DCtDaGlsbGljb3RoZQ'; 89 | var cipherText = 'KDlTtXchhZTGufMYmOYGS4HffxPSUrfmqCHXaI9wOGY'; 90 | var authTag = 'U0m_YmjN04DJvceFICbCVQ'; 91 | 92 | // the key for the above jwe string 93 | var jwk = [ { 'kty': 'oct', 'k': 'GawgguFyGrWKav7AX4VKUg' }]; 94 | 95 | it('should fail if jwe string does not have 5 parts', function(done) { 96 | var jweString = '1.2.3.4'; 97 | 98 | var err; 99 | jwe.decrypt(jweString, null, log, (error, decrypted_token) => { err = error; }); 100 | 101 | expect(err.message).to.equal('In jwe.decrypt: invalid JWE string, it has 4 parts instead of 5'); 102 | done(); 103 | }); 104 | 105 | it('should fail with invalid header', function(done) { 106 | var jweString = '1.2.3.4.5'; 107 | 108 | var err; 109 | jwe.decrypt(jweString, null, log, (error, decrypted_token) => { err = error; }); 110 | 111 | expect(err.message).to.equal('In jwe.decrypt: failed to parse JWE header'); 112 | done(); 113 | }); 114 | 115 | it('should fail with no encrypted_key', function(done) { 116 | var jweString = header + '..' + iv + '.' + cipherText + '.' + authTag; 117 | 118 | var err; 119 | jwe.decrypt(jweString, jwk, log, (error, decrypted_token) => { err = error; }); 120 | 121 | expect(err.message).to.equal('tried all keys to decrypt cek but none of them works'); 122 | done(); 123 | }); 124 | 125 | it('should fail with no iv', function(done) { 126 | var jweString = header + '.' + encrypted_key + '..' + cipherText + '.' + authTag; 127 | 128 | var err; 129 | jwe.decrypt(jweString, jwk, log, (error, decrypted_token) => { err = error; }); 130 | 131 | expect(err.message).to.equal('In decrypt_AES_CBC_HMAC_SHA2: iv has size 0, it must have size 16'); 132 | done(); 133 | }); 134 | 135 | it('should fail with no authTag', function(done) { 136 | var jweString = header + '.' + encrypted_key + '.' + iv +'.' + cipherText + '.'; 137 | 138 | var err; 139 | jwe.decrypt(jweString, jwk, log, (error, decrypted_token) => { err = error; }); 140 | 141 | expect(err.message).to.equal('In decrypt_AES_CBC_HMAC_SHA2: invalid authentication tag'); 142 | done(); 143 | }); 144 | }); -------------------------------------------------------------------------------- /test/Chai-passport_test/oidc_dynamic_tenant_test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation 3 | * All Rights Reserved 4 | * MIT License 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this 7 | * software and associated documentation files (the "Software"), to deal in the Software 8 | * without restriction, including without limitation the rights to use, copy, modify, 9 | * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 10 | * permit persons to whom the Software is furnished to do so, subject to the following 11 | * conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be 14 | * included in all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS 19 | * OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT 21 | * OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | */ 23 | 24 | /* eslint-disable no-new */ 25 | 26 | 'use restrict'; 27 | 28 | var chai = require('chai'); 29 | var url = require('url'); 30 | var OIDCStrategy = require('../../lib/index').OIDCStrategy; 31 | 32 | chai.use(require('chai-passport-strategy')); 33 | 34 | const TEST_TIMEOUT = 1000000; // 1000 seconds 35 | 36 | // Mock options required to create a OIDC strategy 37 | var options = { 38 | redirectUrl: 'https://returnURL', 39 | clientID: 'my_client_id', 40 | clientSecret: 'my_client_secret', 41 | identityMetadata: 'https://login.microsoftonline.com/common/.well-known/openid-configuration', 42 | responseType: 'id_token', 43 | responseMode: 'form_post', 44 | validateIssuer: true, 45 | passReqToCallback: false, 46 | loggingNoPII: false, 47 | sessionKey: 'my_key' //optional sessionKey 48 | }; 49 | 50 | describe('OIDCStrategy dynamic tenant test', function() { 51 | this.timeout(TEST_TIMEOUT); 52 | 53 | var redirectUrl; 54 | var challenge; 55 | var request; 56 | 57 | var testPrepare = function(validateIssuer, issuer, tenantIdOrName, isB2C, policy) { 58 | return function(done) { 59 | options.validateIssuer = validateIssuer; 60 | options.issuer = issuer; 61 | options.isB2C = isB2C; 62 | 63 | var testStrategy = new OIDCStrategy(options, function(profile, done) {}); 64 | 65 | chai.passport 66 | .use(testStrategy) 67 | .redirect(function(u) {redirectUrl = u; done(); }) 68 | .fail(function(c) {challenge = c; done(); }) 69 | .req(function(req) { 70 | request = req; 71 | req.session = {}; 72 | req.query = {}; 73 | challenge = null; 74 | }) 75 | .authenticate({ tenantIdOrName: tenantIdOrName }); 76 | }; 77 | }; 78 | 79 | describe('should succeed', function() { 80 | before(testPrepare(true, null, 'sijun.onmicrosoft.com', false)); 81 | 82 | it('should have replaced common with tenantIdOrName and saved tenantIdOrName in session', function() { 83 | var u = url.parse(redirectUrl, true); 84 | chai.expect(request.session['my_key']['content'][0]['tenantIdOrName']).to.equal('sijun.onmicrosoft.com'); 85 | chai.expect(u.pathname).to.equal('/268da1a1-9db4-48b9-b1fe-683250ba90cc/oauth2/authorize'); 86 | }); 87 | }); 88 | 89 | describe('should fail without issuer and tenantIdOrName for common endpoint', function() { 90 | before(testPrepare(true, null, '', false)); 91 | 92 | it('should fail with invalid tenant name', function() { 93 | chai.expect(challenge).to.equal('In collectInfoFromReq: issuer or tenantIdOrName must be provided in order to validate issuer on common endpoint'); 94 | }); 95 | }); 96 | 97 | describe('should fail with invalid tenant name', function() { 98 | before(testPrepare(true, null, 'xxx', false)); 99 | 100 | it('should have replaced common with tenantIdOrName and saved tenantIdOrName in session', function() { 101 | console.log(challenge); 102 | chai.expect(challenge.startsWith('Error: 400')).to.equal(true); 103 | }); 104 | }); 105 | }); 106 | 107 | describe('OIDCStrategy dynamic B2C tenant test', function() { 108 | this.timeout(TEST_TIMEOUT); 109 | 110 | var redirectUrl; 111 | var challenge; 112 | var request; 113 | 114 | var testPrepare = function(validateIssuer, issuer, tenantIdOrName) { 115 | return function(done) { 116 | options.validateIssuer = validateIssuer; 117 | options.issuer = issuer; 118 | options.isB2C = true; 119 | options.identityMetadata = 'https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration'; 120 | 121 | var testStrategy = new OIDCStrategy(options, function(profile, done) {}); 122 | 123 | chai.passport 124 | .use(testStrategy) 125 | .redirect(function(u) { redirectUrl = u; done(); }) 126 | .fail(function(c) {challenge = c; done(); }) 127 | .req(function(req) { 128 | request = req; 129 | req.session = {}; 130 | req.query = { p: 'b2c_1_signin' }; 131 | challenge = null; 132 | }) 133 | .authenticate({ tenantIdOrName: tenantIdOrName }); 134 | }; 135 | }; 136 | 137 | describe('should fail without tenantIdOrName for using B2C common endpoint', function() { 138 | before(testPrepare(true, ['my_issuer'], '', true)); 139 | 140 | it('should fail with invalid tenant name', function() { 141 | chai.expect(challenge).to.equal('In collectInfoFromReq: we are using common endpoint for B2C but tenantIdOrName is not provided'); 142 | }); 143 | }); 144 | }); 145 | 146 | -------------------------------------------------------------------------------- /test/Chai-passport_test/oidc_incoming_request_test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation 3 | * All Rights Reserved 4 | * MIT License 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this 7 | * software and associated documentation files (the "Software"), to deal in the Software 8 | * without restriction, including without limitation the rights to use, copy, modify, 9 | * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 10 | * permit persons to whom the Software is furnished to do so, subject to the following 11 | * conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be 14 | * included in all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS 19 | * OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT 21 | * OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | */ 23 | 24 | /* eslint-disable no-new */ 25 | 26 | 'use restrict'; 27 | 28 | var chai = require('chai'); 29 | var url = require('url'); 30 | var OIDCStrategy = require('../../lib/index').OIDCStrategy; 31 | 32 | chai.use(require('chai-passport-strategy')); 33 | 34 | const TEST_TIMEOUT = 1000000; // 1000 seconds 35 | 36 | // Mock options required to create a OIDC strategy 37 | var options = { 38 | redirectUrl: 'https://returnURL', 39 | clientID: 'my_client_id', 40 | clientSecret: 'my_client_secret', 41 | identityMetadata: 'https://login.microsoftonline.com/xxx.onmicrosoft.com/.well-known/openid-configuration', 42 | responseType: 'id_token', 43 | responseMode: 'form_post', 44 | validateIssuer: true, 45 | passReqToCallback: false, 46 | algorithms: ['RS256'], 47 | sessionKey: 'my_key' //optional sessionKey 48 | }; 49 | 50 | var testStrategy = new OIDCStrategy(options, function(profile, done) {}); 51 | 52 | // Mock `setOptions` 53 | testStrategy.setOptions = function(params, oauthConfig, optionsToValidate, done) { 54 | oauthConfig.clientID = options.clientID; 55 | oauthConfig.clientSecret = options.clientSecret; 56 | oauthConfig.authorizationURL = 'https://www.example.com/authorizationURL'; 57 | oauthConfig.tokenURL = 'https://www.example.com/tokenURL'; 58 | 59 | done(); 60 | }; 61 | 62 | 63 | describe('OIDCStrategy incoming state and nonce checking', function() { 64 | this.timeout(TEST_TIMEOUT); 65 | 66 | var redirectUrl; 67 | var request; 68 | 69 | var testPrepare = function(customState) { 70 | return function(done) { 71 | chai.passport 72 | .use(testStrategy) 73 | .redirect(function(u) {redirectUrl = u; done(); }) 74 | .req(function(req) { 75 | request = req; 76 | req.session = {}; 77 | req.query = {}; 78 | }) 79 | .authenticate({ customState : customState }); 80 | }; 81 | }; 82 | 83 | describe('state/nonce checking', function() { 84 | before(testPrepare()); 85 | 86 | it('should have the same state/nonce', function() { 87 | var u = url.parse(redirectUrl, true); 88 | chai.expect(request.session['my_key']['content'][0]['state']).to.equal(u.query.state); 89 | chai.expect(request.session['my_key']['content'][0]['nonce']).to.equal(u.query.nonce); 90 | }); 91 | }); 92 | 93 | describe('custom state checking', function() { 94 | before(testPrepare('custom_state')); 95 | 96 | it('should have used custom state', function() { 97 | var u = url.parse(redirectUrl, true); 98 | chai.expect(request.session['my_key']['content'][0]['state']).to.equal(u.query.state); 99 | chai.expect(request.session['my_key']['content'][0]['state'].substring(38)).to.equal('custom_state'); 100 | }); 101 | }); 102 | }); 103 | 104 | describe('OIDCStrategy error flow checking', function() { 105 | this.timeout(TEST_TIMEOUT); 106 | 107 | var challenge; 108 | 109 | var testPrepare = function() { 110 | return function(done) { 111 | chai.passport 112 | .use(testStrategy) 113 | .fail(function(c) { challenge = c; done(); }) 114 | .req(function(req) { 115 | var time = Date.now(); 116 | req.session = {'my_key': {'content': [{'state': 'my_state', 'timeStamp': time}]}}; 117 | req.query = {}; 118 | req.body = {state: 'my_state', error: 'my_error'}; 119 | }) 120 | .authenticate({}); 121 | }; 122 | }; 123 | 124 | describe('error checking', function() { 125 | before(testPrepare()); 126 | 127 | it('should have the same error', function() { 128 | chai.expect(challenge).to.equal('my_error'); 129 | }); 130 | }); 131 | }); 132 | 133 | describe('OIDCStrategy token in request checking', function() { 134 | this.timeout(TEST_TIMEOUT); 135 | 136 | var challenge; 137 | 138 | var testPrepare = function(access_token, refresh_token, query_or_body) { 139 | return function(done) { 140 | chai.passport 141 | .use(testStrategy) 142 | .fail(function(c) { 143 | challenge = c; done(); 144 | }) 145 | .req(function(req) { 146 | req.query = {}; 147 | 148 | if (query_or_body == 'query') 149 | req.query = {'access_token' : access_token, 'refresh_token' : refresh_token}; 150 | else if (query_or_body == 'body') 151 | req.body = {'access_token' : access_token, 'refresh_token' : refresh_token}; 152 | }) 153 | .authenticate({}); 154 | }; 155 | }; 156 | 157 | describe('access_token in query', function() { 158 | before(testPrepare('my_access_token', null, 'query')); 159 | 160 | it('should fail', function() { 161 | chai.expect(challenge).to.equal('In collectInfoFromReq: neither access token nor refresh token is expected in the incoming request'); 162 | }); 163 | }); 164 | describe('access_token in body', function() { 165 | before(testPrepare('my_access_token', null, 'body')); 166 | 167 | it('should fail', function() { 168 | chai.expect(challenge).to.equal('In collectInfoFromReq: neither access token nor refresh token is expected in the incoming request'); 169 | }); 170 | }); 171 | describe('refresh_token in query', function() { 172 | before(testPrepare('my_refresh_token', null, 'query')); 173 | 174 | it('should fail', function() { 175 | chai.expect(challenge).to.equal('In collectInfoFromReq: neither access token nor refresh token is expected in the incoming request'); 176 | }); 177 | }); 178 | describe('refresh_token in body', function() { 179 | before(testPrepare('my_refresh_token', null, 'body')); 180 | 181 | it('should fail', function() { 182 | chai.expect(challenge).to.equal('In collectInfoFromReq: neither access token nor refresh token is expected in the incoming request'); 183 | }); 184 | }); 185 | }); 186 | -------------------------------------------------------------------------------- /test/Chai-passport_test/sessionContentHandler_test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation 3 | * All Rights Reserved 4 | * MIT License 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this 7 | * software and associated documentation files (the "Software"), to deal in the Software 8 | * without restriction, including without limitation the rights to use, copy, modify, 9 | * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 10 | * permit persons to whom the Software is furnished to do so, subject to the following 11 | * conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be 14 | * included in all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS 19 | * OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT 21 | * OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | */ 23 | 24 | /* eslint-disable no-new */ 25 | 26 | 'use restrict'; 27 | 28 | const chai = require('chai'); 29 | const expect = chai.expect; 30 | const SessionContentHandler = require('../../lib/sessionContentHandler').SessionContentHandler; 31 | 32 | const TEST_TIMEOUT = 1000000; // 1000 seconds 33 | 34 | describe('checking constructor', function() { 35 | this.timeout(TEST_TIMEOUT); 36 | 37 | it('should throw with non-integer maxAmount', function(done) { 38 | expect(SessionContentHandler.bind(SessionContentHandler, 1.1, 1)). 39 | to.throw('SessionContentHandler: maxAmount must be a positive integer'); 40 | done(); 41 | }); 42 | 43 | it('should throw with negative maxAmount', function(done) { 44 | expect(SessionContentHandler.bind(SessionContentHandler, -1, 1)). 45 | to.throw('SessionContentHandler: maxAmount must be a positive integer'); 46 | done(); 47 | }); 48 | 49 | it('should throw with invalid maxAge', function(done) { 50 | expect(SessionContentHandler.bind(SessionContentHandler, 1, -1)). 51 | to.throw('SessionContentHandler: maxAge must be a positive number'); 52 | done(); 53 | }); 54 | }); 55 | 56 | describe('checking add function', function() { 57 | this.timeout(TEST_TIMEOUT); 58 | 59 | var req = {}; 60 | var handler = new SessionContentHandler(2, 0.1); 61 | 62 | it('should have the tuples we push in', function(done) { 63 | handler.add(req, 'key', {'state': 'state1', 'nonce': 'nonce1', 'policy': 'policy1', 'timeStamp': Date.now()}); 64 | handler.add(req, 'key', {'state': 'state2', 'nonce': 'nonce2', 'policy': 'policy2', 'timeStamp': Date.now()}); 65 | 66 | expect(req.session['key']['content'].length).to.equal(2); 67 | 68 | var tuple1 = req.session['key']['content'][0]; 69 | expect(tuple1['state']).to.equal('state1'); 70 | expect(tuple1['nonce']).to.equal('nonce1'); 71 | expect(tuple1['policy']).to.equal('policy1'); 72 | 73 | var tuple2 = req.session['key']['content'][1]; 74 | expect(tuple2['state']).to.equal('state2'); 75 | expect(tuple2['nonce']).to.equal('nonce2'); 76 | expect(tuple2['policy']).to.equal('policy2'); 77 | 78 | done(); 79 | }); 80 | 81 | it('should not exceed the maxAmount of items', function(done) { 82 | // we add a third item, but the maxAmount allowed is 2, so the first 83 | // state should be removed automatically 84 | handler.add(req, 'key', {'state': 'state3', 'nonce': 'nonce3', 'policy': 'policy3', 'timeStamp': Date.now()}); 85 | expect(req.session['key']['content'].length).to.equal(2); 86 | 87 | var tuple1 = req.session['key']['content'][0]; 88 | expect(tuple1['state']).to.equal('state2'); 89 | expect(tuple1['nonce']).to.equal('nonce2'); 90 | expect(tuple1['policy']).to.equal('policy2'); 91 | 92 | var tuple2 = req.session['key']['content'][1]; 93 | expect(tuple2['state']).to.equal('state3'); 94 | expect(tuple2['nonce']).to.equal('nonce3'); 95 | expect(tuple2['policy']).to.equal('policy3'); 96 | 97 | done(); 98 | }); 99 | 100 | it('should removed expired items', function(done) { 101 | // if we call 'add' function after the maxAge, all the expired ones should be 102 | // removed when we can 'add' function 103 | setTimeout(function() { 104 | handler.add(req, 'key', {'state': 'state4', 'nonce': 'nonce4', 'policy': 'policy4', 'timeStamp': Date.now()}); 105 | expect(req.session['key']['content'].length).to.equal(1); 106 | expect(req.session['key']['content'][0]['state']).to.equal('state4'); 107 | expect(req.session['key']['content'][0]['nonce']).to.equal('nonce4'); 108 | expect(req.session['key']['content'][0]['policy']).to.equal('policy4'); 109 | done(); 110 | }, 200); // maxAge is 0.1 second = 100 ms 111 | }); 112 | }); 113 | 114 | describe('checking findAndDeleteTupleByState function', function() { 115 | this.timeout(TEST_TIMEOUT); 116 | 117 | var req = {}; 118 | var handler = new SessionContentHandler(2, 0.1); 119 | 120 | it('should throw without session', function(done) { 121 | expect(handler.findAndDeleteTupleByState.bind(handler, req, 'key', 'test')). 122 | to.throw('OIDC strategy requires session support. Did you forget to use session middleware such as express-session?'); 123 | done(); 124 | }); 125 | 126 | it('should find the tuple we added, tuple should be deleted after verify', function(done) { 127 | handler.add(req, 'key', {'state': 'state1', 'nonce': 'nonce1', 'policy': 'policy1', 'timeStamp': Date.now()}); 128 | 129 | // should have the items we added 130 | var tuple = handler.findAndDeleteTupleByState(req, 'key', 'state1'); 131 | expect(tuple['state']).to.equal('state1'); 132 | 133 | // should be deleted after verify 134 | tuple = handler.findAndDeleteTupleByState(req, 'key', 'state1'); 135 | expect(tuple).to.equal(null); 136 | 137 | done(); 138 | }); 139 | 140 | it('should not find the expired item', function(done) { 141 | handler.add(req, 'key', {'state': 'state1', 'nonce': 'nonce1', 'policy': 'policy1', 'timeStamp': Date.now()}); 142 | 143 | setTimeout(function() { 144 | var tuple = handler.findAndDeleteTupleByState(req, 'key', 'state1'); 145 | expect(tuple).to.equal(null); 146 | done(); 147 | }, 200); // expire after 0.1 second = 100ms, wait a little bit longer here (for 0.2 seconds) 148 | }); 149 | }); 150 | -------------------------------------------------------------------------------- /test/End_to_end_test/app/api.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation 3 | * All Rights Reserved 4 | * MIT License 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this 7 | * software and associated documentation files (the 'Software'), to deal in the Software 8 | * without restriction, including without limitation the rights to use, copy, modify, 9 | * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 10 | * permit persons to whom the Software is furnished to do so, subject to the following 11 | * conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be 14 | * included in all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 17 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS 19 | * OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT 21 | * OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | */ 23 | 24 | 'use strict'; 25 | 26 | module.exports = function(strategyOptions) { 27 | var express = require('express'); 28 | var cookieParser = require('cookie-parser'); 29 | var bodyParser = require('body-parser'); 30 | var methodOverride = require('method-override'); 31 | var passport = require('passport'); 32 | var enableGracefulShutdown = require('server-graceful-shutdown'); 33 | var BearerStrategy = require('../../../lib/index').BearerStrategy; 34 | var hasReq = strategyOptions.passReqToCallback; 35 | 36 | // the verify function 37 | var verifyFunc = (profile, done) => { done(null, profile); }; 38 | var verifyFuncWithReq = (req, profile, done) => { done(null, profile); }; 39 | 40 | var strategy; 41 | if (hasReq) 42 | strategy = new BearerStrategy(strategyOptions, verifyFuncWithReq); 43 | else 44 | strategy = new BearerStrategy(strategyOptions, verifyFunc); 45 | passport.use(strategy); 46 | 47 | var app = express(); 48 | app.set('views', __dirname + '/views'); 49 | app.set('view engine', 'ejs'); 50 | app.use(express.logger()); 51 | app.use(methodOverride()); 52 | app.use(cookieParser()); 53 | app.use(bodyParser.urlencoded({ extended : true })); 54 | app.use(passport.initialize()); 55 | app.use(passport.session()); 56 | app.use(app.router); 57 | 58 | app.get('/api', passport.authenticate('oauth-bearer', { session: false, tenantIdOrName: strategyOptions.tenantIdOrName }), 59 | function(req, res) { 60 | res.send('succeeded'); 61 | } 62 | ); 63 | 64 | var server = app.listen(4000); 65 | enableGracefulShutdown(server); 66 | return server; 67 | }; 68 | 69 | 70 | -------------------------------------------------------------------------------- /test/End_to_end_test/app/app.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation 3 | * All Rights Reserved 4 | * MIT License 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this 7 | * software and associated documentation files (the 'Software'), to deal in the Software 8 | * without restriction, including without limitation the rights to use, copy, modify, 9 | * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 10 | * permit persons to whom the Software is furnished to do so, subject to the following 11 | * conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be 14 | * included in all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 17 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS 19 | * OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT 21 | * OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | */ 23 | 24 | 'use strict'; 25 | 26 | var users = []; 27 | 28 | module.exports = function(strategyOptions, authenicateOptions, verifyFuncNumber) { 29 | var express = require('express'); 30 | var cookieParser = require('cookie-parser'); 31 | var expressSession = require('express-session'); 32 | var bodyParser = require('body-parser'); 33 | var methodOverride = require('method-override'); 34 | var passport = require('passport'); 35 | var enableGracefulShutdown = require('server-graceful-shutdown'); 36 | var OIDCStrategy = require('../../../lib/index').OIDCStrategy; 37 | var hasReq = strategyOptions.passReqToCallback; 38 | 39 | users = []; 40 | var findBySub = function(sub, fn) { 41 | for (var i = 0, len = users.length; i < len; i++) { 42 | var user = users[i]; 43 | if (user.sub === sub) 44 | return fn(null, user); 45 | } 46 | return fn(null, null); 47 | }; 48 | 49 | const verifyArityArgsMap = { 50 | 8: ['iss', 'sub', 'profile', 'jwtClaims', 'access_token', 'refresh_token', 'params', 'done'], 51 | 7: ['iss', 'sub', 'profile', 'access_token', 'refresh_token', 'params', 'done'], 52 | 6: ['iss', 'sub', 'profile', 'access_token', 'refresh_token', 'done'], 53 | 4: ['iss', 'sub', 'profile', 'done'], 54 | 3: ['iss', 'sub', 'done'], 55 | 2: ['profile', 'done'] 56 | }; 57 | 58 | // we choose the 'function(profile, done)' or 'function(req, profile, done)' for the default verify callback 59 | if (!verifyFuncNumber) 60 | verifyFuncNumber = 2; 61 | 62 | // the verify function helper 63 | var verifyFuncHelper = function () { 64 | var args = {}; 65 | var length = hasReq ? (arguments.length - 1) : arguments.length; 66 | 67 | // save the stuff from arguments to args 68 | if (verifyArityArgsMap[length]) { 69 | for (let i = 0; i < length; i++) 70 | args[verifyArityArgsMap[length][i]] = hasReq ? arguments[i+1] : arguments[i]; 71 | } 72 | 73 | // normalize the result 74 | if (length === 2) 75 | args.sub = args.profile.sub; 76 | args.access_token = args.access_token ? "exists" : "none"; 77 | args.refresh_token = args.refresh_token ? "exists" : "none"; 78 | args.profile = args.profile ? args.profile : { upn : 'none', oid : 'none'}; 79 | 80 | findBySub(args.sub, (err, user) => { 81 | if (err) 82 | return done(err); 83 | if (!user) { 84 | users.push(args); 85 | return args.done(null, args); 86 | } 87 | return args.done(null, user); 88 | }); 89 | }; 90 | 91 | // register all the possible verify functions 92 | var funcs = {}; 93 | if (!hasReq) { 94 | funcs[8] = function(iss, sub, profile, jwtClaims, access_token, refresh_token, params, done) { 95 | verifyFuncHelper.apply(null, arguments); 96 | }; 97 | funcs[7] = function(iss, sub, profile, access_token, refresh_token, params, done) { 98 | verifyFuncHelper.apply(null, arguments); 99 | }; 100 | funcs[6] = function(iss, sub, profile, access_token, refresh_token, done) { 101 | verifyFuncHelper.apply(null, arguments); 102 | }; 103 | funcs[4] = function(iss, sub, profile, done) { 104 | verifyFuncHelper.apply(null, arguments); 105 | }; 106 | funcs[3] = function(iss, sub, done) { 107 | verifyFuncHelper.apply(null, arguments); 108 | }; 109 | funcs[2] = function(profile, done) { 110 | verifyFuncHelper.apply(null, arguments); 111 | }; 112 | } else { 113 | funcs[8] = function(req, iss, sub, profile, jwtClaims, access_token, refresh_token, params, done) { 114 | verifyFuncHelper.apply(null, arguments); 115 | }; 116 | funcs[7] = function(req, iss, sub, profile, access_token, refresh_token, params, done) { 117 | verifyFuncHelper.apply(null, arguments); 118 | }; 119 | funcs[6] = function(req, iss, sub, profile, access_token, refresh_token, done) { 120 | verifyFuncHelper.apply(null, arguments); 121 | }; 122 | funcs[4] = function(req, iss, sub, profile, done) { 123 | verifyFuncHelper.apply(null, arguments); 124 | }; 125 | funcs[3] = function(req, iss, sub, done) { 126 | verifyFuncHelper.apply(null, arguments); 127 | }; 128 | funcs[2] = function(req, profile, done) { 129 | verifyFuncHelper.apply(null, arguments); 130 | }; 131 | } 132 | 133 | var strategy = new OIDCStrategy(strategyOptions, funcs[verifyFuncNumber]); 134 | passport.use(strategy); 135 | passport.serializeUser(function(user, done) { done(null, user.sub); }); 136 | passport.deserializeUser(function(sub, done) { 137 | findBySub(sub, function (err, user) { 138 | done(err, user); 139 | }); 140 | }); 141 | 142 | var indexPage = strategyOptions.isB2C ? 'index_b2c' : 'index'; 143 | 144 | var app = express(); 145 | 146 | app.set('views', __dirname + '/views'); 147 | app.set('view engine', 'ejs'); 148 | app.use(express.logger()); 149 | app.use(methodOverride()); 150 | app.use(cookieParser()); 151 | app.use(expressSession({ secret: 'keyboard cat', resave: true, saveUninitialized: false })); 152 | app.use(bodyParser.urlencoded({ extended : true })); 153 | app.use(passport.initialize()); 154 | app.use(passport.session()); 155 | app.use(app.router); 156 | app.use(express.static(__dirname + '/../../public')); 157 | 158 | app.get('/', function(req, res) { 159 | console.log('##### homepage get called #####'); 160 | res.render(indexPage, { user: req.user }); 161 | }); 162 | 163 | app.get('/result', function(req, res) { 164 | res.render('result', { user: req.user }); 165 | }); 166 | 167 | app.get('/login', passport.authenticate('azuread-openidconnect', { 168 | tenantIdOrName: authenicateOptions.tenantIdOrName, 169 | resourceURL: authenicateOptions.resourceURL, 170 | customState: authenicateOptions.customState, 171 | prompt: authenicateOptions.prompt, 172 | domain_hint: authenicateOptions.domain_hint, 173 | login_hint: authenicateOptions.login_hint, 174 | extraReqQueryParams: authenicateOptions.extraReqQueryParams, 175 | failureRedirect: '/result' 176 | })); 177 | 178 | app.get('/auth/openid/return', 179 | passport.authenticate('azuread-openidconnect', { failureRedirect: '/result' }), 180 | function(req, res) { 181 | res.render('result', { user: req.user }); 182 | }); 183 | 184 | app.post('/auth/openid/return', 185 | passport.authenticate('azuread-openidconnect', { failureRedirect: '/result' }), 186 | function(req, res) { 187 | res.render('result', { user: req.user }); 188 | }); 189 | 190 | var server = app.listen(3000); 191 | enableGracefulShutdown(server); 192 | return server; 193 | }; 194 | 195 | 196 | -------------------------------------------------------------------------------- /test/End_to_end_test/app/app_for_cookie.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation 3 | * All Rights Reserved 4 | * MIT License 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this 7 | * software and associated documentation files (the 'Software'), to deal in the Software 8 | * without restriction, including without limitation the rights to use, copy, modify, 9 | * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 10 | * permit persons to whom the Software is furnished to do so, subject to the following 11 | * conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be 14 | * included in all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 17 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS 19 | * OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT 21 | * OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | */ 23 | 24 | 'use strict'; 25 | 26 | var users = []; 27 | 28 | module.exports = function(strategyOptions) { 29 | var base64url = require('base64url'); 30 | var express = require('express'); 31 | var cookieParser = require('cookie-parser'); 32 | var expressSession = require('express-session'); 33 | var bodyParser = require('body-parser'); 34 | var methodOverride = require('method-override'); 35 | var passport = require('passport'); 36 | var request = require('request'); 37 | var enableGracefulShutdown = require('server-graceful-shutdown'); 38 | var OIDCStrategy = require('../../../lib/index').OIDCStrategy; 39 | 40 | users = []; 41 | 42 | var findBySub = function(sub, fn) { 43 | for (var i = 0, len = users.length; i < len; i++) { 44 | var user = users[i]; 45 | if (user.sub === sub) 46 | return fn(null, user); 47 | } 48 | return fn(null, null); 49 | }; 50 | 51 | var strategy = new OIDCStrategy(strategyOptions, 52 | function(profile, done) { 53 | findBySub(profile.sub, function(err, user) { 54 | if (!user) { 55 | users.push(profile); 56 | return done(null, profile); 57 | } 58 | return done(null, user); 59 | }); 60 | } 61 | ); 62 | 63 | passport.use(strategy); 64 | passport.serializeUser(function(user, done) { done(null, user.sub); }); 65 | passport.deserializeUser(function(sub, done) { 66 | findBySub(sub, function (err, user) { 67 | done(err, user); 68 | }); 69 | }); 70 | 71 | var app = express(); 72 | 73 | app.set('views', __dirname + '/views'); 74 | app.set('view engine', 'ejs'); 75 | app.use(express.logger()); 76 | app.use(methodOverride()); 77 | app.use(cookieParser()); 78 | app.use(bodyParser.urlencoded({ extended : true })); 79 | app.use(passport.initialize()); 80 | app.use(passport.session()); 81 | app.use(app.router); 82 | 83 | app.get('/', function(req, res) { 84 | res.render('index', { user: null }); 85 | }); 86 | 87 | app.get('/login', (req, res, next) => { 88 | req.logout(); 89 | 90 | passport.authenticate('azuread-openidconnect', { response: res, failureRedirect: '/result' })(req, res, next); 91 | }, (req, res) => { 92 | res.render('apiResult', { result: 'succeeded' }); 93 | }); 94 | 95 | app.post('/auth/openid/return', (req, res, next) => { 96 | passport.authenticate('azuread-openidconnect', { response: res, failureRedirect: '/result' })(req, res, next); 97 | }, (req, res) => { 98 | res.render('apiResult', { result: 'succeeded' }); 99 | } 100 | ); 101 | 102 | app.get('/result', function(req, res) { 103 | res.render('apiResult', { result: 'failed' }); 104 | }); 105 | 106 | var server = app.listen(3000); 107 | enableGracefulShutdown(server); 108 | return server; 109 | }; 110 | 111 | 112 | -------------------------------------------------------------------------------- /test/End_to_end_test/app/app_for_jwe.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation 3 | * All Rights Reserved 4 | * MIT License 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this 7 | * software and associated documentation files (the 'Software'), to deal in the Software 8 | * without restriction, including without limitation the rights to use, copy, modify, 9 | * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 10 | * permit persons to whom the Software is furnished to do so, subject to the following 11 | * conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be 14 | * included in all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 17 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS 19 | * OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT 21 | * OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | */ 23 | 24 | 'use strict'; 25 | 26 | var users = []; 27 | 28 | module.exports = function(strategyOptions) { 29 | var base64url = require('base64url'); 30 | var express = require('express'); 31 | var cookieParser = require('cookie-parser'); 32 | var expressSession = require('express-session'); 33 | var bodyParser = require('body-parser'); 34 | var methodOverride = require('method-override'); 35 | var passport = require('passport'); 36 | var request = require('request'); 37 | var enableGracefulShutdown = require('server-graceful-shutdown'); 38 | var OIDCStrategy = require('../../../lib/index').OIDCStrategy; 39 | 40 | users = []; 41 | 42 | var findBySub = function(sub, fn) { 43 | for (var i = 0, len = users.length; i < len; i++) { 44 | var user = users[i]; 45 | if (user.sub === sub) 46 | return fn(null, user); 47 | } 48 | return fn(null, null); 49 | }; 50 | 51 | var strategy = new OIDCStrategy(strategyOptions, 52 | function(profile, done) { 53 | findBySub(profile.sub, function(err, user) { 54 | if (!user) { 55 | users.push(profile); 56 | return done(null, profile); 57 | } 58 | return done(null, user); 59 | }); 60 | } 61 | ); 62 | 63 | passport.use(strategy); 64 | passport.serializeUser(function(user, done) { done(null, user.sub); }); 65 | passport.deserializeUser(function(sub, done) { 66 | findBySub(sub, function (err, user) { 67 | done(err, user); 68 | }); 69 | }); 70 | 71 | var app = express(); 72 | 73 | app.set('views', __dirname + '/views'); 74 | app.set('view engine', 'ejs'); 75 | app.use(express.logger()); 76 | app.use(methodOverride()); 77 | app.use(cookieParser()); 78 | app.use(expressSession({ secret: 'keyboard cat', resave: true, saveUninitialized: false })); 79 | app.use(bodyParser.urlencoded({ extended : true })); 80 | app.use(passport.initialize()); 81 | app.use(passport.session()); 82 | app.use(app.router); 83 | 84 | app.get('/', function(req, res) { 85 | res.render('index', { user: null }); 86 | }); 87 | 88 | var testList = { 89 | 's1': { 'JWE_alg': 'RSA1_5', 'JWE_alg_key_kid': 'rsa_key', 'JWE_enc': 'A128CBC-HS256'}, 90 | 's2': { 'JWE_alg': 'RSA-OAEP', 'JWE_alg_key_kid': 'rsa_key', 'JWE_enc': 'A128CBC-HS256'}, 91 | 's3': { 'JWE_alg': 'A128KW', 'JWE_alg_key_kid': 'sym_key_128', 'JWE_enc': 'A128CBC-HS256'}, 92 | 's4': { 'JWE_alg': 'A256KW', 'JWE_alg_key_kid': 'sym_key_256', 'JWE_enc': 'A128CBC-HS256'}, 93 | 's5': { 'JWE_alg': 'dir', 'JWE_alg_key_kid': 'sym_key_256', 'JWE_enc': 'A128CBC-HS256'}, 94 | 95 | 's6': { 'JWE_alg': 'RSA1_5', 'JWE_alg_key_kid': 'rsa_key', 'JWE_enc': 'A256CBC-HS512'}, 96 | 's7': { 'JWE_alg': 'RSA-OAEP', 'JWE_alg_key_kid': 'rsa_key', 'JWE_enc': 'A256CBC-HS512'}, 97 | 's8': { 'JWE_alg': 'A128KW', 'JWE_alg_key_kid': 'sym_key_128', 'JWE_enc': 'A256CBC-HS512'}, 98 | 's9': { 'JWE_alg': 'A256KW', 'JWE_alg_key_kid': 'sym_key_256', 'JWE_enc': 'A256CBC-HS512'}, 99 | 's10': { 'JWE_alg': 'dir', 'JWE_alg_key_kid': 'sym_key_512', 'JWE_enc': 'A256CBC-HS512'}, 100 | 101 | 's11': { 'JWE_alg': 'RSA1_5', 'JWE_alg_key_kid': 'rsa_key', 'JWE_enc': 'A192CBC-HS384'}, 102 | 's12': { 'JWE_alg': 'RSA-OAEP', 'JWE_alg_key_kid': 'rsa_key', 'JWE_enc': 'A192CBC-HS384'}, 103 | 's13': { 'JWE_alg': 'A128KW', 'JWE_alg_key_kid': 'sym_key_128', 'JWE_enc': 'A192CBC-HS384'}, 104 | 's14': { 'JWE_alg': 'A256KW', 'JWE_alg_key_kid': 'sym_key_256', 'JWE_enc': 'A192CBC-HS384'}, 105 | 's15': { 'JWE_alg': 'dir', 'JWE_alg_key_kid': 'sym_key_384', 'JWE_enc': 'A192CBC-HS384'}, 106 | 107 | 'f1': { 'JWE_alg': 'RSA1_5', 'JWE_alg_key_kid': 'rsa_key', 'JWE_enc': 'A128CBC-HS256', 'id_token_JWE_invalid_authTag': true }, 108 | 'f2': { 'JWE_alg': 'RSA1_5', 'JWE_alg_key_kid': 'rsa_key', 'JWE_enc': 'A256CBC-HS512', 'id_token_JWE_invalid_authTag': true }, 109 | }; 110 | 111 | app.get('/t/:id', (req, res, next) => { 112 | req.logout(); 113 | var id = req.params['id']; 114 | var tParams = base64url.encode(JSON.stringify(testList[id])); 115 | passport.authenticate('azuread-openidconnect', { extraAuthReqQueryParams: { 'tParams': tParams }, failureRedirect: '/result' })(req, res, next); 116 | }, (req, res) => { 117 | res.render('apiResult', { result: 'succeeded' }); 118 | }); 119 | 120 | app.get('/t_no_kid/:id', (req, res, next) => { 121 | req.logout(); 122 | var id = req.params['id']; 123 | var json = JSON.parse(JSON.stringify(testList[id])); // make a copy 124 | json['id_token_JWE_header_no_kid'] = true; 125 | var tParams = base64url.encode(JSON.stringify(json)); 126 | passport.authenticate('azuread-openidconnect', { extraAuthReqQueryParams: { 'tParams': tParams }, failureRedirect: '/result' })(req, res, next); 127 | }, (req, res) => { 128 | res.render('apiResult', { result: 'succeeded' }); 129 | }); 130 | 131 | app.post('/auth/openid/return', 132 | passport.authenticate('azuread-openidconnect', { failureRedirect: '/result'}), 133 | (req, res) => { 134 | res.render('apiResult', { result: 'succeeded' }); 135 | } 136 | ); 137 | 138 | app.get('/result', function(req, res) { 139 | res.render('apiResult', { result: 'failed' }); 140 | }); 141 | 142 | var server = app.listen(3000); 143 | enableGracefulShutdown(server); 144 | return server; 145 | }; 146 | 147 | 148 | -------------------------------------------------------------------------------- /test/End_to_end_test/app/client_for_api.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation 3 | * All Rights Reserved 4 | * MIT License 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this 7 | * software and associated documentation files (the 'Software'), to deal in the Software 8 | * without restriction, including without limitation the rights to use, copy, modify, 9 | * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 10 | * permit persons to whom the Software is furnished to do so, subject to the following 11 | * conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be 14 | * included in all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 17 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS 19 | * OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT 21 | * OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | */ 23 | 24 | 'use strict'; 25 | 26 | var users = []; 27 | var access_token = null; 28 | 29 | module.exports = function(strategyOptions, authenicateOptions) { 30 | var express = require('express'); 31 | var cookieParser = require('cookie-parser'); 32 | var expressSession = require('express-session'); 33 | var bodyParser = require('body-parser'); 34 | var methodOverride = require('method-override'); 35 | var passport = require('passport'); 36 | var request = require('request'); 37 | var enableGracefulShutdown = require('server-graceful-shutdown'); 38 | var OIDCStrategy = require('../../../lib/index').OIDCStrategy; 39 | 40 | users = []; 41 | access_token = null; 42 | 43 | var findByOid = function(oid, fn) { 44 | for (var i = 0, len = users.length; i < len; i++) { 45 | var user = users[i]; 46 | if (user.oid === oid) 47 | return fn(null, user); 48 | } 49 | return fn(null, null); 50 | }; 51 | 52 | var strategy = new OIDCStrategy(strategyOptions, 53 | function(iss, sub, profile, accessToken, refreshToken, done) { 54 | access_token = accessToken; 55 | findByOid(profile.oid, function(err, user) { 56 | if (!user) { 57 | users.push(profile); 58 | return done(null, profile); 59 | } 60 | return done(null, user); 61 | }); 62 | } 63 | ); 64 | 65 | passport.use(strategy); 66 | passport.serializeUser(function(user, done) { done(null, user.oid); }); 67 | passport.deserializeUser(function(oid, done) { 68 | findByOid(oid, function (err, user) { 69 | done(err, user); 70 | }); 71 | }); 72 | 73 | var app = express(); 74 | 75 | app.set('views', __dirname + '/views'); 76 | app.set('view engine', 'ejs'); 77 | app.use(express.logger()); 78 | app.use(methodOverride()); 79 | app.use(cookieParser()); 80 | app.use(expressSession({ secret: 'keyboard cat', resave: true, saveUninitialized: false })); 81 | app.use(bodyParser.urlencoded({ extended : true })); 82 | app.use(passport.initialize()); 83 | app.use(passport.session()); 84 | app.use(app.router); 85 | 86 | app.get('/', function(req, res) { 87 | if (strategyOptions.isB2C) 88 | res.render('index_b2c', { user: null }); 89 | else 90 | res.render('index', { user: null }); 91 | }); 92 | 93 | app.get('/login', passport.authenticate('azuread-openidconnect', authenicateOptions)); 94 | 95 | app.post('/auth/openid/return', passport.authenticate('azuread-openidconnect'), 96 | function(req, res) { 97 | res.send(200); 98 | } 99 | ); 100 | 101 | app.get('/callApi', function(req, res) { 102 | request({ 103 | url: 'http://localhost:4000/api', 104 | auth: { 105 | 'bearer': access_token 106 | } 107 | }, function(err, resp) { 108 | res.render('apiResult', { result: resp.body }); 109 | }) 110 | }); 111 | 112 | var server = app.listen(3000); 113 | enableGracefulShutdown(server); 114 | return server; 115 | }; 116 | 117 | 118 | -------------------------------------------------------------------------------- /test/End_to_end_test/app/views/apiResult.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | result 5 | 6 | 7 |

<%= result %>

8 | 9 | 10 | -------------------------------------------------------------------------------- /test/End_to_end_test/app/views/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Example 5 | 6 | 7 | <% if (!user) { %> 8 |

Log In

9 | <% } else { %> 10 |

11 | Home | 12 | Account | 13 | Log Out 14 |

Hello, <%= user.displayName %>.

15 |

16 | <% } %> 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /test/End_to_end_test/app/views/index_b2c.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Example 5 | 6 | 7 | <% if (!user) { %> 8 |

Welcome! Please log in.

9 | Sign In 10 | Sign Up 11 | Update Profile 12 | Reset Password 13 | <% } else { %> 14 |

Hello, <%= user.emails[0] %>.

15 | Log Out 16 | <% } %> 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /test/End_to_end_test/app/views/result.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | result 5 | 6 | 7 | <% if (user) { %> 8 |

succeeded

9 |

<%= user.sub %>

10 |

<%= user.access_token %>

11 |

<%= user.refresh_token %>

12 |

<%= user.profile.upn %>

13 |

<%= user.profile.oid %>

14 |

<%= user.profile.emails %>

15 | <% } else { %> 16 |

failed

17 | <% } %> 18 | 19 | 20 | -------------------------------------------------------------------------------- /test/End_to_end_test/driver.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation 3 | * All Rights Reserved 4 | * MIT License 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this 7 | * software and associated documentation files (the 'Software'), to deal in the Software 8 | * without restriction, including without limitation the rights to use, copy, modify, 9 | * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 10 | * permit persons to whom the Software is furnished to do so, subject to the following 11 | * conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be 14 | * included in all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 17 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS 19 | * OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT 21 | * OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | */ 23 | 24 | 'use strict'; 25 | 26 | var webdriver = require('selenium-webdriver'); 27 | var chai = require('chai'); 28 | var chrome = require('selenium-webdriver/chrome'); 29 | var path = require('chromedriver').path; 30 | var expect = chai.expect; 31 | 32 | var chromeCapabilities = webdriver.Capabilities.chrome(); 33 | var chromeOptions = { 34 | 'args': ['--no-sandbox', 'disable-infobars'] 35 | }; 36 | chromeCapabilities.set('chromeOptions', chromeOptions); 37 | 38 | exports = module.exports = { 39 | error_handler: (ex, server, done) => { 40 | server.shutdown(() => { 41 | expect(ex).to.equal(null); 42 | done(); 43 | }); 44 | }, 45 | get_service: () => { 46 | var service = new chrome.ServiceBuilder(path).build(); 47 | chrome.setDefaultService(service); 48 | return service; 49 | }, 50 | get_driver: () => { return new webdriver.Builder().withCapabilities(chromeCapabilities).build(); }, 51 | webdriver: webdriver 52 | }; 53 | -------------------------------------------------------------------------------- /test/End_to_end_test/oidc_cookie_test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation 3 | * All Rights Reserved 4 | * MIT License 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this 7 | * software and associated documentation files (the 'Software'), to deal in the Software 8 | * without restriction, including without limitation the rights to use, copy, modify, 9 | * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 10 | * permit persons to whom the Software is furnished to do so, subject to the following 11 | * conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be 14 | * included in all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 17 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS 19 | * OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT 21 | * OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | */ 23 | 24 | 'use strict'; 25 | 26 | /****************************************************************************** 27 | * Testing tools setup 28 | *****************************************************************************/ 29 | 30 | var chromedriver = require('./driver'); 31 | var service = chromedriver.get_service(); 32 | var webdriver = chromedriver.webdriver; 33 | var By = webdriver.By; 34 | var until = webdriver.until; 35 | var base64url = require('base64url'); 36 | 37 | var chai = require('chai'); 38 | var expect = chai.expect; 39 | 40 | const TEST_TIMEOUT = 500000; // 500 seconds 41 | const LOGIN_WAITING_TIME = 1000; // 1 second 42 | 43 | /****************************************************************************** 44 | * configurations needed 45 | *****************************************************************************/ 46 | 47 | var driver; 48 | var server; 49 | 50 | var code_config = { 51 | identityMetadata: 'https://testingsts.azurewebsites.net/.well-known/openid-configuration', 52 | //identityMetadata: 'http://localhost:8081/.well-known/openid-configuration', 53 | clientID: 'client-001', 54 | responseType: 'code', 55 | responseMode: 'form_post', 56 | redirectUrl: 'http://localhost:3000/auth/openid/return', 57 | allowHttpForRedirectUrl: true, 58 | allowMultiAudiencesInToken: true, 59 | clientSecret: 'secret-001', 60 | validateIssuer: true, 61 | passReqToCallback: false, 62 | scope: null, 63 | loggingLevel: null, 64 | useCookieInsteadOfSession: true, 65 | nonceLifetime: null, 66 | nonceMaxAmount: 5, 67 | cookieEncryptionKeys: [ 68 | { key: '3zTvzr3p67VC61jmV54rIYu1545x4TlY', iv: '60iP0h6vJoEa' }, 69 | { key: '12345678901234567890123456789012', iv: '123456789012' }] 70 | }; 71 | 72 | var implicit_config = JSON.parse(JSON.stringify(code_config)); 73 | implicit_config.responseType = 'id_token'; 74 | 75 | var hybrid_config = JSON.parse(JSON.stringify(code_config)); 76 | hybrid_config.responseType = 'code id_token'; 77 | 78 | var runAuthTest = (done) => { 79 | driver.get('http://localhost:3000/login').then(() => { 80 | driver.wait(until.titleIs('result'), 10000); 81 | driver.findElement(By.id('status')).getText().then((text) => { 82 | expect(text).to.equal('succeeded'); 83 | done(); 84 | }); 85 | }); 86 | }; 87 | 88 | /****************************************************************************** 89 | * The test cases 90 | *****************************************************************************/ 91 | 92 | describe('authorization code flow test', function() { 93 | this.timeout(TEST_TIMEOUT); 94 | 95 | it('start app for authorization code flow', function(done) { 96 | if (!driver) 97 | driver = chromedriver.get_driver(); 98 | 99 | server = require('./app/app_for_cookie')(code_config); 100 | done(); 101 | }); 102 | 103 | it('should succeeded', function(done) { 104 | runAuthTest(done); 105 | }); 106 | 107 | it('shut down app', function(done) { 108 | server.shutdown(done); 109 | }); 110 | }); 111 | 112 | describe('implicit flow test', function() { 113 | this.timeout(TEST_TIMEOUT); 114 | 115 | it('start app for implicit flow', function(done) { 116 | if (!driver) 117 | driver = chromedriver.get_driver(); 118 | 119 | server = require('./app/app_for_cookie')(implicit_config); 120 | done(); 121 | }); 122 | 123 | it('should succeeded', function(done) { 124 | runAuthTest(done); 125 | }); 126 | 127 | it('shut down app', function(done) { 128 | server.shutdown(done); 129 | }); 130 | }); 131 | 132 | describe('hybrid flow test', function() { 133 | this.timeout(TEST_TIMEOUT); 134 | 135 | it('start app for hybrid flow', function(done) { 136 | if (!driver) 137 | driver = chromedriver.get_driver(); 138 | 139 | server = require('./app/app_for_cookie')(hybrid_config); 140 | done(); 141 | }); 142 | 143 | it('should succeeded', function(done) { 144 | runAuthTest(done); 145 | }); 146 | 147 | it('shut down app', function(done) { 148 | driver.quit(); 149 | service.stop(); 150 | server.shutdown(done); 151 | }); 152 | }); 153 | 154 | 155 | -------------------------------------------------------------------------------- /test/End_to_end_test/oidc_testing_sts_test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation 3 | * All Rights Reserved 4 | * MIT License 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this 7 | * software and associated documentation files (the 'Software'), to deal in the Software 8 | * without restriction, including without limitation the rights to use, copy, modify, 9 | * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 10 | * permit persons to whom the Software is furnished to do so, subject to the following 11 | * conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be 14 | * included in all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 17 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS 19 | * OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT 21 | * OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | */ 23 | 24 | 'use strict'; 25 | 26 | /****************************************************************************** 27 | * Testing tools setup 28 | *****************************************************************************/ 29 | 30 | var chromedriver = require('./driver'); 31 | var service = chromedriver.get_service(); 32 | var webdriver = chromedriver.webdriver; 33 | var By = webdriver.By; 34 | var until = webdriver.until; 35 | var base64url = require('base64url'); 36 | 37 | var chai = require('chai'); 38 | var expect = chai.expect; 39 | 40 | const TEST_TIMEOUT = 500000; // 30 seconds 41 | const LOGIN_WAITING_TIME = 1000; // 1 second 42 | 43 | /****************************************************************************** 44 | * configurations needed 45 | *****************************************************************************/ 46 | 47 | var driver; 48 | var server; 49 | 50 | var code_config = { 51 | identityMetadata: 'https://testingsts.azurewebsites.net/.well-known/openid-configuration', 52 | //identityMetadata: 'http://localhost:8081/.well-known/openid-configuration', 53 | clientID: 'client-001', 54 | responseType: 'code', 55 | responseMode: 'form_post', 56 | redirectUrl: 'http://localhost:3000/auth/openid/return', 57 | allowHttpForRedirectUrl: true, 58 | allowMultiAudiencesInToken: true, 59 | clientSecret: 'secret-001', 60 | validateIssuer: true, 61 | passReqToCallback: false, 62 | scope: null, 63 | loggingLevel: null, 64 | nonceLifetime: null, 65 | }; 66 | 67 | var implicit_config = JSON.parse(JSON.stringify(code_config)); 68 | implicit_config.responseType = 'id_token'; 69 | 70 | var hybrid_config = JSON.parse(JSON.stringify(code_config)); 71 | hybrid_config.responseType = 'code id_token'; 72 | 73 | var runAuthTest = (id, result, done) => { 74 | driver.get('http://localhost:3000/auth/' + id).then(() => { 75 | driver.wait(until.titleIs('result'), 10000); 76 | driver.findElement(By.id('status')).getText().then((text) => { 77 | expect(text).to.equal(result); 78 | done(); 79 | }); 80 | }); 81 | }; 82 | 83 | var runTokenTest = (id, result, done) => { 84 | driver.get('http://localhost:3000/token/' + id).then(() => { 85 | driver.wait(until.titleIs('result'), 10000); 86 | driver.findElement(By.id('status')).getText().then((text) => { 87 | expect(text).to.equal(result); 88 | done(); 89 | }); 90 | }); 91 | }; 92 | 93 | var id_token_list = [ "alg1", "iss1", "iss2", "sub1", "aud1", "aud2", "exp1", "exp2", 94 | "nonce1", "nonce2", "azp1", "azp2", "nbf1", "sig1", "sig2"]; 95 | 96 | var c_hash_list = ["c_hash1", "c_hash2"]; // for "code id_token" 97 | 98 | var auth_resp_list = ["state1", "state2", "denied"]; 99 | 100 | var token_resp_list = ["id_token_tokenResp", "access_token_tokenResp", "access_token_expired"]; 101 | 102 | var invalid_code = "code2"; // for "code", "code id_token" 103 | 104 | var invalid_sub = "sub2"; // for the id_token from token endpoint 105 | 106 | /****************************************************************************** 107 | * The test cases 108 | *****************************************************************************/ 109 | 110 | describe('authorization code flow test', function() { 111 | this.timeout(TEST_TIMEOUT); 112 | 113 | it('start app for authorization code flow', function(done) { 114 | if (!driver) 115 | driver = chromedriver.get_driver(); 116 | 117 | server = require('./app/app_for_testing_sts')(code_config); 118 | done(); 119 | }); 120 | 121 | // tests with tparams in authentication request 122 | 123 | for(let i = 0; i < id_token_list.length; i++) { 124 | it('add_tparam_in_auth_req_test: ' + id_token_list[i], function(done) { 125 | runAuthTest(id_token_list[i], 'failed', done); 126 | }); 127 | } 128 | 129 | for(let i = 0; i < auth_resp_list.length; i++) { 130 | it('add_tparam_in_auth_req_test: ' + auth_resp_list[i], function(done) { 131 | runAuthTest(auth_resp_list[i], 'failed', done); 132 | }); 133 | } 134 | 135 | for(let i = 0; i < token_resp_list.length; i++) { 136 | it('add_tparam_in_auth_req_test: ' + token_resp_list[i], function(done) { 137 | runAuthTest(token_resp_list[i], 'failed', done); 138 | }); 139 | } 140 | 141 | it('add_tparam_in_auth_req_test: code2' , function(done) { 142 | runAuthTest(invalid_code, 'failed', done); 143 | }); 144 | 145 | // tests with tparams in token request 146 | 147 | for(let i = 0; i < id_token_list.length; i++) { 148 | it('add_tparam_in_token_req_test: ' + id_token_list[i], function(done) { 149 | runTokenTest(id_token_list[i], 'failed', done); 150 | }); 151 | } 152 | 153 | for(let i = 0; i < token_resp_list.length; i++) { 154 | it('add_tparam_in_token_req_test: ' + token_resp_list[i], function(done) { 155 | runTokenTest(token_resp_list[i], 'failed', done); 156 | }); 157 | } 158 | 159 | it('add_tparam_in_token_req_test: sub2' , function(done) { 160 | runAuthTest(invalid_sub, 'failed', done); 161 | }); 162 | 163 | it('shut down app', function(done) { 164 | server.shutdown(done); 165 | }) 166 | }); 167 | 168 | describe('implicit flow test', function() { 169 | this.timeout(TEST_TIMEOUT); 170 | 171 | it('start app for implicit flow', function(done) { 172 | if (!driver) 173 | driver = chromedriver.get_driver(); 174 | 175 | server = require('./app/app_for_testing_sts')(implicit_config); 176 | done(); 177 | }); 178 | 179 | // tests with tparams in authentication request 180 | 181 | for(let i = 0; i < id_token_list.length; i++) { 182 | it('add_tparam_in_auth_req_test: ' + id_token_list[i], function(done) { 183 | runAuthTest(id_token_list[i], 'failed', done); 184 | }); 185 | } 186 | 187 | for(let i = 0; i < auth_resp_list.length; i++) { 188 | it('add_tparam_in_auth_req_test: ' + auth_resp_list[i], function(done) { 189 | runAuthTest(auth_resp_list[i], 'failed', done); 190 | }); 191 | } 192 | 193 | it('shut down app', function(done) { 194 | server.shutdown(done); 195 | }) 196 | }); 197 | 198 | describe('hybrid flow test', function() { 199 | this.timeout(TEST_TIMEOUT); 200 | 201 | it('start app for hybrid flow', function(done) { 202 | if (!driver) 203 | driver = chromedriver.get_driver(); 204 | 205 | server = require('./app/app_for_testing_sts')(hybrid_config); 206 | done(); 207 | }); 208 | 209 | // tests with tparams in authentication request 210 | 211 | for(let i = 0; i < id_token_list.length; i++) { 212 | it('add_tparam_in_auth_req_test: ' + id_token_list[i], function(done) { 213 | runAuthTest(id_token_list[i], 'failed', done); 214 | }); 215 | } 216 | 217 | for(let i = 0; i < c_hash_list.length; i++) { 218 | it('add_tparam_in_auth_req_test: ' + c_hash_list[i], function(done) { 219 | runAuthTest(c_hash_list[i], 'failed', done); 220 | }); 221 | } 222 | 223 | for(let i = 0; i < auth_resp_list.length; i++) { 224 | it('add_tparam_in_auth_req_test: ' + auth_resp_list[i], function(done) { 225 | runAuthTest(auth_resp_list[i], 'failed', done); 226 | }); 227 | } 228 | 229 | for(let i = 0; i < token_resp_list.length; i++) { 230 | it('add_tparam_in_auth_req_test: ' + token_resp_list[i], function(done) { 231 | runAuthTest(token_resp_list[i], 'failed', done); 232 | }); 233 | } 234 | 235 | it('add_tparam_in_auth_req_test: code2' , function(done) { 236 | runAuthTest(invalid_code, 'failed', done); 237 | }); 238 | 239 | // tests with tparams in token request 240 | 241 | for(let i = 0; i < id_token_list.length; i++) { 242 | it('add_tparam_in_token_req_test: ' + id_token_list[i], function(done) { 243 | runTokenTest(id_token_list[i], 'failed', done); 244 | }); 245 | } 246 | 247 | for(let i = 0; i < token_resp_list.length; i++) { 248 | it('add_tparam_in_token_req_test: ' + token_resp_list[i], function(done) { 249 | runTokenTest(token_resp_list[i], 'failed', done); 250 | }); 251 | } 252 | 253 | it('add_tparam_in_token_req_test: sub2' , function(done) { 254 | runAuthTest(invalid_sub, 'failed', done); 255 | }); 256 | 257 | it('shut down app', function(done) { 258 | driver.quit(); 259 | service.stop(); 260 | server.shutdown(done); 261 | }) 262 | }); 263 | 264 | 265 | -------------------------------------------------------------------------------- /test/End_to_end_test/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "passport-azure-ad-end-to-end-test", 3 | "version": "0.0.1", 4 | "dependencies": { 5 | "adal-node": "^0.1.22", 6 | "azure-keyvault": "^0.11.0", 7 | "chromedriver": "^2.24.1", 8 | "selenium-webdriver": "^3.0.0", 9 | "server-graceful-shutdown": "^0.1.2", 10 | "body-parser": "^1.15.2", 11 | "cookie-parser": "^1.4.3", 12 | "ejs": ">= 0.0.0", 13 | "ejs-locals": ">= 0.0.0", 14 | "express": "^3.21.2", 15 | "express-session": "^1.14.2", 16 | "method-override": "^2.3.6", 17 | "passport": "*", 18 | "request": "^2.78.0" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /test/End_to_end_test/script.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation 3 | * All Rights Reserved 4 | * MIT License 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this 7 | * software and associated documentation files (the 'Software'), to deal in the Software 8 | * without restriction, including without limitation the rights to use, copy, modify, 9 | * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 10 | * permit persons to whom the Software is furnished to do so, subject to the following 11 | * conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be 14 | * included in all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 17 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS 19 | * OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT 21 | * OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | */ 23 | 24 | 'use strict'; 25 | 26 | var adalNode = require('adal-node'); 27 | var azureKeyVault = require('azure-keyvault'); 28 | var async = require('async'); 29 | 30 | // Get the clientID and clientSecret from environment variables. 31 | // Those parameters are given by Jenkins. 32 | var clientId = process.env.KEY_VAULT_CLIENT_ID; 33 | var clientSecret = process.env.KEY_VAULT_CLIENT_SECRET; 34 | 35 | 36 | var authenticator = (challenge, callback) => { 37 | // Create a new authentication context. 38 | var context = new adalNode.AuthenticationContext(challenge.authorization); 39 | // Use the context to acquire an authentication token. 40 | return context.acquireTokenWithClientCredentials(challenge.resource, clientId, clientSecret, function(err, tokenResponse) { 41 | if (err) throw err; 42 | // Calculate the value to be set in the request's Authorization header and resume the call. 43 | var authorizationValue = tokenResponse.tokenType + ' ' + tokenResponse.accessToken; 44 | return callback(null, authorizationValue); 45 | }); 46 | }; 47 | 48 | var credentials = new azureKeyVault.KeyVaultCredentials(authenticator); 49 | var client = new azureKeyVault.KeyVaultClient(credentials); 50 | 51 | // Key vault uri for ADAL testing 52 | var vaultUri = 'https://jse2ekeyvault.vault.azure.net'; 53 | 54 | // The secrets we want to get from the key vault 55 | var v1_v2_user_password_kv_name = 'v1v2UserPassword'; 56 | var v1_v2_user_password; 57 | var b2c_user_password_kv_name = 'b2cUserPassword'; 58 | var b2c_user_password; 59 | var v1_client_secret_kv_name = 'v1ClientSecret'; 60 | var v1_client_secret; 61 | var v2_client_secret_kv_name = 'v2ClientSecret'; 62 | var v2_client_secret; 63 | var b2c_client_secret_kv_name = 'b2cClientSecret'; 64 | var b2c_client_secret; 65 | 66 | exports.set_test_parameters = (callback) => { 67 | var test_parameters = {}; 68 | 69 | async.waterfall([ 70 | (next) => { 71 | client.getSecret(vaultUri + '/secrets/' + v1_v2_user_password_kv_name, function (err, result) { 72 | if (err) throw err; 73 | v1_v2_user_password = result.value; 74 | return next(); 75 | }); 76 | }, 77 | 78 | (next) => { 79 | client.getSecret(vaultUri + '/secrets/' + b2c_user_password_kv_name, function (err, result) { 80 | if (err) throw err; 81 | b2c_user_password = result.value; 82 | return next(); 83 | }); 84 | }, 85 | 86 | (next) => { 87 | client.getSecret(vaultUri + '/secrets/' + v1_client_secret_kv_name, function (err, result) { 88 | if (err) throw err; 89 | v1_client_secret = result.value; 90 | return next(); 91 | }); 92 | }, 93 | 94 | (next) => { 95 | client.getSecret(vaultUri + '/secrets/' + v2_client_secret_kv_name, function (err, result) { 96 | if (err) throw err; 97 | v2_client_secret = result.value; 98 | return next(); 99 | }); 100 | }, 101 | 102 | (next) => { 103 | client.getSecret(vaultUri + '/secrets/' + b2c_client_secret_kv_name, function (err, result) { 104 | if (err) throw err; 105 | b2c_client_secret = result.value; 106 | return next(); 107 | }); 108 | }, 109 | 110 | (next) => { 111 | test_parameters.v1_params = { 112 | tenantID: '268da1a1-9db4-48b9-b1fe-683250ba90cc', 113 | clientID: 'a8600356-09b7-4ca5-aee1-04aa7841a05b', 114 | clientSecret: v1_client_secret, 115 | username: 'robot@sijun.onmicrosoft.com', 116 | password: v1_v2_user_password 117 | }; 118 | 119 | test_parameters.v2_params = { 120 | tenantID: '268da1a1-9db4-48b9-b1fe-683250ba90cc', 121 | clientID: '186104ca-b7a3-4462-ac54-43b60161243f', 122 | clientSecret: v2_client_secret, 123 | username: 'robot@sijun.onmicrosoft.com', 124 | password: v1_v2_user_password 125 | }; 126 | 127 | test_parameters.b2c_params = { 128 | tenantID: '22bf40c6-1186-4ea5-b49b-3dc4ec0f54eb', 129 | clientID: 'f0b6e4eb-2d8c-40b6-b9c6-e26d1074846d', 130 | clientSecret: b2c_client_secret, 131 | username: 'lsj31415926@gmail.com', 132 | password: b2c_user_password, 133 | scopeForBearer: ['read', 'write'], 134 | scopeForOIDC: ['https://sijun1b2c.onmicrosoft.com/oidc-b2c/read', 'https://sijun1b2c.onmicrosoft.com/oidc-b2c/write'] 135 | }; 136 | 137 | return callback(test_parameters); 138 | }], 139 | 140 | (waterfallError) => { 141 | if (waterfallError) 142 | console.log(waterfallError.message); 143 | return true; 144 | } 145 | ); 146 | }; 147 | 148 | -------------------------------------------------------------------------------- /test/End_to_end_test/test_parameters.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation 3 | * All Rights Reserved 4 | * MIT License 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this 7 | * software and associated documentation files (the 'Software'), to deal in the Software 8 | * without restriction, including without limitation the rights to use, copy, modify, 9 | * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 10 | * permit persons to whom the Software is furnished to do so, subject to the following 11 | * conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be 14 | * included in all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 17 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS 19 | * OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT 21 | * OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | */ 23 | 24 | 'use strict'; 25 | 26 | var test_parameters = { 27 | v1_params: { 28 | tenantID: '', 29 | clientID: '', 30 | clientSecret: '=', 31 | username: '', 32 | password: '' 33 | }, 34 | v2_params: { 35 | tenantID: '', 36 | clientID: '', 37 | clientSecret: '', 38 | username: '', 39 | password: '' 40 | }, 41 | b2c_params: { 42 | tenantID: '', 43 | clientID: '', 44 | clientSecret: '', 45 | username: '', 46 | password: '', 47 | scopeForBearer: [''], 48 | scopeForOIDC: [''] 49 | } 50 | }; 51 | 52 | exports.is_test_parameters_completed = false; 53 | 54 | exports.test_parameters = test_parameters; 55 | -------------------------------------------------------------------------------- /test/Nodeunit_test/bearer_test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation 3 | * All Rights Reserved 4 | * MIT License 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this 7 | * software and associated documentation files (the "Software"), to deal in the Software 8 | * without restriction, including without limitation the rights to use, copy, modify, 9 | * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 10 | * permit persons to whom the Software is furnished to do so, subject to the following 11 | * conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be 14 | * included in all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS 19 | * OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT 21 | * OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | */ 23 | 24 | /* eslint-disable no-new */ 25 | 26 | 'use strict'; 27 | 28 | const BearerStrategy = require('../../lib/index').BearerStrategy; 29 | 30 | /* 31 | ======== A Handy Little Nodeunit Reference ======== 32 | https://github.com/caolan/nodeunit 33 | 34 | Test methods: 35 | test.expect(numAssertions) 36 | test.done() 37 | Test assertions: 38 | test.ok(value, [message]) 39 | test.equal(actual, expected, [message]) 40 | test.notEqual(actual, expected, [message]) 41 | test.deepEqual(actual, expected, [message]) 42 | test.notDeepEqual(actual, expected, [message]) 43 | test.strictEqual(actual, expected, [message]) 44 | test.notStrictEqual(actual, expected, [message]) 45 | test.throws(block, [error], [message]) 46 | test.doesNotThrow(block, [error], [message]) 47 | test.ifError(value) 48 | */ 49 | 50 | function noop() {} 51 | 52 | var commonMetadataURL = 'https://login.microsoftonline.com/common/.well-known/openid-configuration'; 53 | var nonCommonMetadataURL = 'https://login.microsoftonline.com/xxx.onmicrosoft.com/.well-known/openid-configuration'; 54 | 55 | function setConfig(metadataURL, validateIssuer, callback) { 56 | var config = { 57 | clientID: 'spn:6514a8ca-d9e4-4155-b292-65258398f3aa', 58 | identityMetadata: metadataURL, 59 | validateIssuer: validateIssuer 60 | }; 61 | 62 | callback(config); 63 | }; 64 | 65 | exports.bearer = { 66 | 'validateIssuer tests': (test) => { 67 | test.expect(15); 68 | 69 | setConfig(commonMetadataURL, true, (bearerConfig) => { 70 | test.doesNotThrow(() => { 71 | new BearerStrategy(bearerConfig, noop); 72 | }, 73 | Error, 74 | 'Should not throw with validateIssuer set true on common endpoint' 75 | ); 76 | }); 77 | 78 | setConfig(commonMetadataURL, undefined, (bearerConfig) => { 79 | test.doesNotThrow(() => { 80 | new BearerStrategy(bearerConfig, noop); 81 | }, 82 | Error, 83 | 'Should throw with default validateIssuer value on common endpoint' 84 | ); 85 | }); 86 | 87 | setConfig(commonMetadataURL, null, (bearerConfig) => { 88 | test.doesNotThrow(() => { 89 | new BearerStrategy(bearerConfig, noop); 90 | }, 91 | Error, 92 | 'Should throw with default validateIssuer value on common endpoint' 93 | ); 94 | }); 95 | 96 | setConfig(commonMetadataURL, null, (bearerConfig) => { 97 | test.doesNotThrow(() => { 98 | bearerConfig.issuer = 'some issuer'; 99 | new BearerStrategy(bearerConfig, noop); 100 | }, 101 | Error, 102 | 'Should not throw if validateIssuer is true and issuer is provided on common endpoint' 103 | ); 104 | }); 105 | 106 | setConfig(commonMetadataURL, null, (bearerConfig) => { 107 | test.doesNotThrow(() => { 108 | bearerConfig.isB2C = true; 109 | bearerConfig.policyName = 'B2C_1_signin'; 110 | bearerConfig.validateIssuer = false; 111 | new BearerStrategy(bearerConfig, noop); 112 | }, 113 | Error, 114 | 'Should throw for using B2C on common endpoint' 115 | ); 116 | }); 117 | 118 | setConfig(nonCommonMetadataURL, null, (bearerConfig) => { 119 | test.throws(() => { 120 | bearerConfig.isB2C = true; 121 | bearerConfig.policyName = 'signin'; 122 | bearerConfig.validateIssuer = false; 123 | new BearerStrategy(bearerConfig, noop); 124 | }, 125 | Error, 126 | 'Should throw for using B2C with wrong policy name' 127 | ); 128 | }); 129 | 130 | setConfig(nonCommonMetadataURL, null, (bearerConfig) => { 131 | test.throws(() => { 132 | bearerConfig.isB2C = true; 133 | bearerConfig.policyName = 'b2c_1_signin'; 134 | bearerConfig.validateIssuer = false; 135 | bearerConfig.scope = 'scope'; 136 | new BearerStrategy(bearerConfig, noop); 137 | }, 138 | Error, 139 | 'Should throw for using B2C if scope is not an array' 140 | ); 141 | }); 142 | 143 | setConfig(nonCommonMetadataURL, null, (bearerConfig) => { 144 | test.throws(() => { 145 | bearerConfig.isB2C = true; 146 | bearerConfig.policyName = 'b2c_1_signin'; 147 | bearerConfig.validateIssuer = false; 148 | bearerConfig.scope = []; 149 | new BearerStrategy(bearerConfig, noop); 150 | }, 151 | Error, 152 | 'Should throw for using B2C if scope is an empty array' 153 | ); 154 | }); 155 | 156 | setConfig(nonCommonMetadataURL, null, (bearerConfig) => { 157 | test.doesNotThrow(() => { 158 | bearerConfig.isB2C = true; 159 | bearerConfig.policyName = 'b2c_1_signin'; 160 | bearerConfig.validateIssuer = false; 161 | new BearerStrategy(bearerConfig, noop); 162 | }, 163 | Error, 164 | 'Should not throw with lower case prefix for B2C' 165 | ); 166 | }); 167 | 168 | setConfig(commonMetadataURL, false, (bearerConfig) => { 169 | var strategy; 170 | test.doesNotThrow(() => { 171 | strategy = new BearerStrategy(bearerConfig, noop); 172 | }, 173 | Error, 174 | 'Should not throw with validateIssuer value set false on common endpoint' 175 | ); 176 | test.ok(strategy._options.validateIssuer === false, 'validateIssuer should be consistent with the user input'); 177 | }); 178 | 179 | setConfig(nonCommonMetadataURL, true, (bearerConfig) => { 180 | var strategy = new BearerStrategy(bearerConfig, noop); 181 | test.ok(strategy._options.validateIssuer === true, 'validateIssuer should be consistent with the user input'); 182 | }); 183 | 184 | setConfig(nonCommonMetadataURL, false, (bearerConfig) => { 185 | var strategy = new BearerStrategy(bearerConfig, noop); 186 | test.ok(strategy._options.validateIssuer === false, 'validateIssuer should be consistent with the user input'); 187 | }); 188 | 189 | setConfig(nonCommonMetadataURL, null, (bearerConfig) => { 190 | var strategy = new BearerStrategy(bearerConfig, noop); 191 | test.ok(strategy._options.validateIssuer === true, 'validateIssuer should use the default validateIssuer value if it is set null'); 192 | }); 193 | 194 | setConfig(nonCommonMetadataURL, undefined, (bearerConfig) => { 195 | var strategy = new BearerStrategy(bearerConfig, noop); 196 | test.ok(strategy._options.validateIssuer === true, 'validateIssuer should use the default validateIssuer value if it is not set'); 197 | }); 198 | 199 | test.done(); 200 | }, 201 | }; 202 | 203 | -------------------------------------------------------------------------------- /test/Nodeunit_test/hash_test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation 3 | * All Rights Reserved 4 | * MIT License 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this 7 | * software and associated documentation files (the "Software"), to deal in the Software 8 | * without restriction, including without limitation the rights to use, copy, modify, 9 | * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 10 | * permit persons to whom the Software is furnished to do so, subject to the following 11 | * conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be 14 | * included in all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS 19 | * OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT 21 | * OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | */ 23 | 24 | /* eslint-disable no-new */ 25 | 26 | 'use strict'; 27 | 28 | const utils = require('../../lib/aadutils'); 29 | 30 | /* 31 | ======== A Handy Little Nodeunit Reference ======== 32 | https://github.com/caolan/nodeunit 33 | 34 | Test methods: 35 | test.expect(numAssertions) 36 | test.done() 37 | Test assertions: 38 | test.ok(value, [message]) 39 | test.equal(actual, expected, [message]) 40 | test.notEqual(actual, expected, [message]) 41 | test.deepEqual(actual, expected, [message]) 42 | test.notDeepEqual(actual, expected, [message]) 43 | test.strictEqual(actual, expected, [message]) 44 | test.notStrictEqual(actual, expected, [message]) 45 | test.throws(block, [error], [message]) 46 | test.doesNotThrow(block, [error], [message]) 47 | test.ifError(value) 48 | */ 49 | 50 | exports.hashTest = { 51 | 52 | 'should recognize the code and c_hash pair is valid': (test) => { 53 | test.expect(1); 54 | var code = 'OAAABAAAA0TWEUN3YUUq5vuCvmnaQiV2K0FwRHqI7u-VXhnGiX0U5u__oid-BUXdlqsWGfHTWV9cIBzYj_S5OoR06m_-b4CbNA-QMMNTCt6VUiynMIRHJvrJMgzuVzkrwnsyfbtMvvpnUoHLH1_qbdkM3dGQj0YgiN_-CcIIzzqtw5KtGmusuZQK8OYQG-KcDqxw1q56mEan2wWrS2U70gWkB0pylkJrOS09BgSmYKZrPCwO7VAco_e9RP8M1fMVP1k5bXCkBwVTCuWm23IXt1CxxJmtQGGEKxH5lETAFqRpFq_P57QDtzjhAPOy6uwM6IXbk2ZU4s3O81M_CTtm3dUlFsYKaPntCgSELZvL0X-6uv5DNXmymJY5hoxcPWlMOOofU7X6fe3U1fBlUsa4ifgaZQsaqQeQO3LR8rYRu3wBKRpGStIvsanGfF9Sdan66EwOmlsdkDhWNgxzM3v0fAvPEg6nyiD7jyfqXBuJCvlGxXdewj82M9xK32xxqB965b9ubR_Ncjki7T4vF0LiO4r85P9yuWktNc_tbnQ0kqFenzozAVQX4t33i-pCk94Me4FUrirRwLvkfwsn0Zmc_aEPa98YHes3cSvA2JZG71SqciA33dV0sTaFOjecjZgk_3_hFO2iTooI27tEBnnkhZNxDIsGpgE4dM0q1wldP-s4UT9QkRmd_LJke7WLyXsdMC9K4x2P6b8P7cngVEzc6yXwbhsq_p3tY5YFDDUecUclgTgeYy1MgAA'; 55 | var c_hash = '4_VbjhfR5g6MSxOYZcvQdw'; 56 | var valid = utils.checkHashValueRS256(code, c_hash); 57 | 58 | test.ok(valid, 'should pass with correct code and c_hash pair'); 59 | test.done(); 60 | }, 61 | 62 | 'should recognize the code and c_hash pair is invalid': (test) => { 63 | test.expect(1); 64 | var code = 'PAAABAAAA0TWEUN3YUUq5vuCvmnaQiV2K0FwRHqI7u-VXhnGiX0U5u__oid-BUXdlqsWGfHTWV9cIBzYj_S5OoR06m_-b4CbNA-QMMNTCt6VUiynMIRHJvrJMgzuVzkrwnsyfbtMvvpnUoHLH1_qbdkM3dGQj0YgiN_-CcIIzzqtw5KtGmusuZQK8OYQG-KcDqxw1q56mEan2wWrS2U70gWkB0pylkJrOS09BgSmYKZrPCwO7VAco_e9RP8M1fMVP1k5bXCkBwVTCuWm23IXt1CxxJmtQGGEKxH5lETAFqRpFq_P57QDtzjhAPOy6uwM6IXbk2ZU4s3O81M_CTtm3dUlFsYKaPntCgSELZvL0X-6uv5DNXmymJY5hoxcPWlMOOofU7X6fe3U1fBlUsa4ifgaZQsaqQeQO3LR8rYRu3wBKRpGStIvsanGfF9Sdan66EwOmlsdkDhWNgxzM3v0fAvPEg6nyiD7jyfqXBuJCvlGxXdewj82M9xK32xxqB965b9ubR_Ncjki7T4vF0LiO4r85P9yuWktNc_tbnQ0kqFenzozAVQX4t33i-pCk94Me4FUrirRwLvkfwsn0Zmc_aEPa98YHes3cSvA2JZG71SqciA33dV0sTaFOjecjZgk_3_hFO2iTooI27tEBnnkhZNxDIsGpgE4dM0q1wldP-s4UT9QkRmd_LJke7WLyXsdMC9K4x2P6b8P7cngVEzc6yXwbhsq_p3tY5YFDDUecUclgTgeYy1MgAA'; 65 | var c_hash = '4_VbjhfR5g6MSxOYZcvQdw'; 66 | var valid = utils.checkHashValueRS256(code, c_hash); 67 | 68 | test.ok(!valid, 'should pass with correct code and c_hash pair'); 69 | test.done(); 70 | }, 71 | }; 72 | -------------------------------------------------------------------------------- /test/Nodeunit_test/metadata_test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation 3 | * All Rights Reserved 4 | * MIT License 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this 7 | * software and associated documentation files (the "Software"), to deal in the Software 8 | * without restriction, including without limitation the rights to use, copy, modify, 9 | * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 10 | * permit persons to whom the Software is furnished to do so, subject to the following 11 | * conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be 14 | * included in all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS 19 | * OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT 21 | * OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | */ 23 | 24 | /* eslint-disable no-new */ 25 | 26 | 'use strict'; 27 | 28 | const Metadata = require('../../lib/metadata').Metadata; 29 | 30 | /* 31 | ======== A Handy Little Nodeunit Reference ======== 32 | https://github.com/caolan/nodeunit 33 | 34 | Test methods: 35 | test.expect(numAssertions) 36 | test.done() 37 | Test assertions: 38 | test.ok(value, [message]) 39 | test.equal(actual, expected, [message]) 40 | test.notEqual(actual, expected, [message]) 41 | test.deepEqual(actual, expected, [message]) 42 | test.notDeepEqual(actual, expected, [message]) 43 | test.strictEqual(actual, expected, [message]) 44 | test.notStrictEqual(actual, expected, [message]) 45 | test.throws(block, [error], [message]) 46 | test.doesNotThrow(block, [error], [message]) 47 | test.ifError(value) 48 | */ 49 | 50 | const metadataUrl = 'https://login.windows.net/GraphDir1.OnMicrosoft.com/federationmetadata/2007-06/federationmetadata.xml'; 51 | const oidcMetadataUrl = 'https://login.microsoftonline.com/common/.well-known/openid-configuration'; 52 | const oidcMetadataUrl2 = 'https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration'; 53 | const options = {}; 54 | 55 | exports.metadata = { 56 | 57 | 'has option': (test) => { 58 | test.expect(1); 59 | // tests here 60 | 61 | test.doesNotThrow( 62 | () => { 63 | new Metadata(oidcMetadataUrl, 'oidc', options); 64 | }, 65 | Error, 66 | 'Should not fail with url present' 67 | ); 68 | 69 | test.done(); 70 | }, 71 | 'missing option url': (test) => { 72 | test.expect(1); 73 | // tests here 74 | 75 | test.throws( 76 | () => { 77 | new Metadata(); 78 | }, 79 | Error, 80 | 'Should fail with url missing' 81 | ); 82 | 83 | test.done(); 84 | }, 85 | 'missing option auth': (test) => { 86 | test.expect(1); 87 | // tests here 88 | 89 | test.throws( 90 | () => { 91 | new Metadata(oidcMetadataUrl, options); 92 | }, 93 | Error, 94 | 'Should fail with auth type missing' 95 | ); 96 | 97 | test.done(); 98 | }, 99 | 'missing option options': (test) => { 100 | test.expect(1); 101 | // tests here 102 | 103 | test.throws( 104 | () => { 105 | new Metadata(oidcMetadataUrl, 'oidc'); 106 | }, 107 | Error, 108 | 'Should fail with options missing' 109 | ); 110 | 111 | test.done(); 112 | }, 113 | 'fetch metadata saml': (test) => { 114 | test.expect(1); 115 | // tests here 116 | 117 | test.throws( 118 | () => { 119 | new Metadata(metadataUrl, 'saml', options); 120 | }, 121 | Error, 122 | 'Should fail with unsupported auth type' 123 | ); 124 | 125 | test.done(); 126 | }, 127 | 'fetch metadata oidc': (test) => { 128 | test.expect(4); 129 | // tests here 130 | 131 | test.doesNotThrow( 132 | () => { 133 | const m = new Metadata(oidcMetadataUrl, 'oidc', options); 134 | m.fetch((err) => { 135 | test.ifError(err); 136 | test.ok(m.oidc.algorithms, 'fetch algorithms'); 137 | test.ok(m.oidc.issuer, 'fetch issuer'); 138 | test.done(); 139 | }); 140 | }, 141 | Error, 142 | 'Should not fail with url present and auth type oidc' 143 | ); 144 | }, 145 | 'fetch metadata oidc v2': (test) => { 146 | test.expect(4); 147 | // tests here 148 | 149 | test.doesNotThrow( 150 | () => { 151 | const m = new Metadata(oidcMetadataUrl2, 'oidc', options); 152 | m.fetch((err) => { 153 | test.ifError(err); 154 | test.ok(m.oidc.algorithms, 'fetch algorithms'); 155 | test.ok(m.oidc.issuer, 'fetch issuer'); 156 | test.done(); 157 | }); 158 | }, 159 | Error, 160 | 'Should not fail with url present and auth type oidc' 161 | ); 162 | }, 163 | 'check keys and pem generation': (test) => { 164 | test.expect(5); 165 | 166 | const m = new Metadata('www.example.com', 'oidc', {}); 167 | 168 | // example public keys from AAD 169 | 170 | var key1 = { 171 | e: "AQAB", 172 | kid: "MnC_VZcATfM5pOYiJHMba9goEKY", 173 | n: "vIqz-4-ER_vNWLON9yv8hIYV737JQ6rCl6XfzOC628seYUPf0TaGk91CFxefhzh23V9Tkq-RtwN1Vs_z57hO82kkzL-cQHZX3bMJD-GEGOKXCEXURN7VMyZWMAuzQoW9vFb1k3cR1RW_EW_P-C8bb2dCGXhBYqPfHyimvz2WarXhntPSbM5XyS5v5yCw5T_Vuwqqsio3V8wooWGMpp61y12NhN8bNVDQAkDPNu2DT9DXB1g0CeFINp_KAS_qQ2Kq6TSvRHJqxRR68RezYtje9KAqwqx4jxlmVAQy0T3-T-IAbsk1wRtWDndhO6s1Os-dck5TzyZ_dNOhfXgelixLUQ" 174 | }; 175 | 176 | var key2 = { 177 | e: "AQAB", 178 | kid: "YbRAQRYcE_motWVJKHrwLBbd_9s", 179 | n: "vbcFrj193Gm6zeo5e2_y54Jx49sIgScv-2JO-n6NxNqQaKVnMkHcz-S1j2FfpFngotwGMzZIKVCY1SK8SKZMFfRTU3wvToZITwf3W1Qq6n-h-abqpyJTaqIcfhA0d6kEAM5NsQAKhfvw7fre1QicmU9LWVWUYAayLmiRX6o3tktJq6H58pUzTtx_D0Dprnx6z5sW-uiMipLXbrgYmOez7htokJVgDg8w-yDFCxZNo7KVueUkLkxhNjYGkGfnt18s7ZW036WoTmdaQmW4CChf_o4TLE5VyGpYWm7I_-nV95BBvwlzokVVKzveKf3l5UU3c6PkGy-BB3E_ChqFm6sPWw" 180 | }; 181 | 182 | var correctPemKey2 = "-----BEGIN RSA PUBLIC KEY-----\n" + 183 | "MIIBCgKCAQEAvbcFrj193Gm6zeo5e2/y54Jx49sIgScv+2JO+n6NxNqQaKVnMkHc\n" + 184 | "z+S1j2FfpFngotwGMzZIKVCY1SK8SKZMFfRTU3wvToZITwf3W1Qq6n+h+abqpyJT\n" + 185 | "aqIcfhA0d6kEAM5NsQAKhfvw7fre1QicmU9LWVWUYAayLmiRX6o3tktJq6H58pUz\n" + 186 | "Ttx/D0Dprnx6z5sW+uiMipLXbrgYmOez7htokJVgDg8w+yDFCxZNo7KVueUkLkxh\n" + 187 | "NjYGkGfnt18s7ZW036WoTmdaQmW4CChf/o4TLE5VyGpYWm7I/+nV95BBvwlzokVV\n" + 188 | "KzveKf3l5UU3c6PkGy+BB3E/ChqFm6sPWwIDAQAB\n" + 189 | "-----END RSA PUBLIC KEY-----\n"; 190 | 191 | m.oidc = {keys: [key1, key2]}; 192 | 193 | test.doesNotThrow( 194 | () => { 195 | var pem = m.generateOidcPEM(key2.kid); 196 | test.ok(pem === correctPemKey2, 'get the correct pem'); 197 | }, 198 | Error, 199 | 'should not fail with the correct keys, kid and pem' 200 | ); 201 | 202 | test.throws( 203 | () => { 204 | // wrong kid 205 | var pem = m.generateOidcPEM('wrong_kid'); 206 | }, 207 | Error, 208 | "Should fail with 'a key with kid my_kid cannot be found'" 209 | ); 210 | 211 | test.throws( 212 | () => { 213 | // missing kid 214 | var pem = m.generateOidcPEM(null); 215 | }, 216 | Error, 217 | "Should fail with 'kid is missing'" 218 | ); 219 | 220 | test.throws( 221 | () => { 222 | // missing keys 223 | m.oidc = {}; 224 | var pem = m.generateOidcPEM(key2.kid); 225 | }, 226 | Error, 227 | "Should fail with 'keys is missing'" 228 | ); 229 | 230 | test.done(); 231 | } 232 | }; 233 | -------------------------------------------------------------------------------- /test/Nodeunit_test/oidc_b2c_test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation 3 | * All Rights Reserved 4 | * MIT License 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this 7 | * software and associated documentation files (the "Software"), to deal in the Software 8 | * without restriction, including without limitation the rights to use, copy, modify, 9 | * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 10 | * permit persons to whom the Software is furnished to do so, subject to the following 11 | * conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be 14 | * included in all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS 19 | * OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT 21 | * OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | */ 23 | 24 | /* eslint-disable no-new */ 25 | 26 | 'use strict'; 27 | 28 | const OIDCStrategy = require('../../lib/index').OIDCStrategy; 29 | 30 | /* 31 | ======== A Handy Little Nodeunit Reference ======== 32 | https://github.com/caolan/nodeunit 33 | 34 | Test methods: 35 | test.expect(numAssertions) 36 | test.done() 37 | Test assertions: 38 | test.ok(value, [message]) 39 | test.equal(actual, expected, [message]) 40 | test.notEqual(actual, expected, [message]) 41 | test.deepEqual(actual, expected, [message]) 42 | test.notDeepEqual(actual, expected, [message]) 43 | test.strictEqual(actual, expected, [message]) 44 | test.notStrictEqual(actual, expected, [message]) 45 | test.throws(block, [error], [message]) 46 | test.doesNotThrow(block, [error], [message]) 47 | test.ifError(value) 48 | */ 49 | 50 | function noop() {} 51 | 52 | exports.oidc = { 53 | 54 | 'with options': (test) => { 55 | test.expect(1); 56 | // tests here 57 | 58 | const oidcConfig = { 59 | // required options 60 | identityMetadata: 'https://login.microsoftonline.com/test.onmicrosoft.com/.well-known/openid-configuration', 61 | forceB2C: true, 62 | clientID: '123', 63 | redirectUrl: 'https://www.example.com', 64 | responseType: 'id_token', 65 | responseMode: 'form_post', 66 | isB2C: true, 67 | validateIssuer: false 68 | }; 69 | 70 | test.doesNotThrow( 71 | () => { 72 | new OIDCStrategy(oidcConfig, noop); 73 | }, 74 | Error, 75 | 'Should not fail with proper OIDC config options' 76 | ); 77 | 78 | test.done(); 79 | }, 80 | }; 81 | -------------------------------------------------------------------------------- /test/Nodeunit_test/oidc_v2_test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation 3 | * All Rights Reserved 4 | * MIT License 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this 7 | * software and associated documentation files (the "Software"), to deal in the Software 8 | * without restriction, including without limitation the rights to use, copy, modify, 9 | * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 10 | * permit persons to whom the Software is furnished to do so, subject to the following 11 | * conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be 14 | * included in all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS 19 | * OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT 21 | * OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | */ 23 | 24 | /* eslint-disable no-new */ 25 | 26 | 'use strict'; 27 | 28 | const OIDCStrategy = require('../../lib/index').OIDCStrategy; 29 | 30 | /* 31 | ======== A Handy Little Nodeunit Reference ======== 32 | https://github.com/caolan/nodeunit 33 | 34 | Test methods: 35 | test.expect(numAssertions) 36 | test.done() 37 | Test assertions: 38 | test.ok(value, [message]) 39 | test.equal(actual, expected, [message]) 40 | test.notEqual(actual, expected, [message]) 41 | test.deepEqual(actual, expected, [message]) 42 | test.notDeepEqual(actual, expected, [message]) 43 | test.strictEqual(actual, expected, [message]) 44 | test.notStrictEqual(actual, expected, [message]) 45 | test.throws(block, [error], [message]) 46 | test.doesNotThrow(block, [error], [message]) 47 | test.ifError(value) 48 | */ 49 | 50 | function noop() {} 51 | 52 | exports.oidc = { 53 | 54 | 'no args': (test) => { 55 | test.expect(1); 56 | // tests here 57 | 58 | test.throws( 59 | () => { 60 | new OIDCStrategy(); 61 | }, 62 | Error, 63 | 'Should fail with no arguments' 64 | ); 65 | 66 | test.done(); 67 | }, 68 | 69 | 'with options': (test) => { 70 | test.expect(1); 71 | // tests here 72 | 73 | const oidcConfig = { 74 | // required options 75 | identityMetadata: 'https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration', 76 | clientID: '123', 77 | redirectUrl: 'https://www.example.com', 78 | responseType: 'id_token', // for login only flows use id_token. For accessing resources use `id_token code` 79 | responseMode: 'form_post', // For login only flows we should have token passed back to us in a POST 80 | validateIssuer: false 81 | }; 82 | 83 | test.doesNotThrow( 84 | () => { 85 | new OIDCStrategy(oidcConfig, noop); 86 | }, 87 | Error, 88 | 'Should not fail with proper v2 OIDC config options' 89 | ); 90 | 91 | test.done(); 92 | }, 93 | }; 94 | -------------------------------------------------------------------------------- /test/Nodeunit_test/pem_test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation 3 | * All Rights Reserved 4 | * MIT License 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this 7 | * software and associated documentation files (the "Software"), to deal in the Software 8 | * without restriction, including without limitation the rights to use, copy, modify, 9 | * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 10 | * permit persons to whom the Software is furnished to do so, subject to the following 11 | * conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be 14 | * included in all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS 19 | * OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT 21 | * OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | */ 23 | 24 | /* eslint-disable no-new */ 25 | 26 | 'use strict'; 27 | 28 | const fs = require('fs'); 29 | const pem = require('../../lib/pem'); 30 | const correctKeyOrCert = require('../resource/correctKeyOrCert'); 31 | 32 | /* 33 | ======== A Handy Little Nodeunit Reference ======== 34 | https://github.com/caolan/nodeunit 35 | 36 | Test methods: 37 | test.expect(numAssertions) 38 | test.done() 39 | Test assertions: 40 | test.ok(value, [message]) 41 | test.equal(actual, expected, [message]) 42 | test.notEqual(actual, expected, [message]) 43 | test.deepEqual(actual, expected, [message]) 44 | test.notDeepEqual(actual, expected, [message]) 45 | test.strictEqual(actual, expected, [message]) 46 | test.notStrictEqual(actual, expected, [message]) 47 | test.throws(block, [error], [message]) 48 | test.doesNotThrow(block, [error], [message]) 49 | test.ifError(value) 50 | */ 51 | 52 | var extractAndCompare = function(extractFunc, pemFileName, correctValue) { 53 | var pem = null; 54 | var pemKeyOrCert = null; 55 | 56 | try { 57 | pem = fs.readFileSync(__dirname + '/../resource/' + pemFileName, 'utf8'); 58 | } catch (e) { 59 | return { pass: false, message: e.message}; 60 | } 61 | 62 | try { 63 | pemKeyOrCert = extractFunc(pem); 64 | } catch (e) { 65 | return { pass: false, message: e.message}; 66 | } 67 | 68 | return {pass: pemKeyOrCert === correctValue, message: 'should be the same key/certificate'}; 69 | }; 70 | 71 | exports.pemTest = { 72 | 73 | 'get private key': (test) => { 74 | test.expect(1); 75 | var result = extractAndCompare(pem.getPrivateKey, 'private.pem', correctKeyOrCert.privateKey); 76 | test.ok(result.pass, result.message); 77 | test.done(); 78 | }, 79 | 80 | 'get certificate': (test) => { 81 | test.expect(1); 82 | var result = extractAndCompare(pem.getCertificate, 'public.pem', correctKeyOrCert.certificate); 83 | test.ok(result.pass, result.message); 84 | test.done(); 85 | }, 86 | 87 | 'test certToPem': (test) => { 88 | test.expect(1); 89 | var pemContent = pem.certToPEM('myCertificate'); 90 | 91 | var result = (function() { 92 | var certificate = null; 93 | try { 94 | certificate = pem.getCertificate(pemContent); 95 | } catch (e) { 96 | return {pass: false, message: e.message}; 97 | } 98 | return {pass: certificate === 'myCertificate', message: 'should get the same certificate'}; 99 | }()); 100 | 101 | test.ok(result.pass, result.message); 102 | test.done(); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /test/Nodeunit_test/validator_test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation 3 | * All Rights Reserved 4 | * MIT License 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this 7 | * software and associated documentation files (the "Software"), to deal in the Software 8 | * without restriction, including without limitation the rights to use, copy, modify, 9 | * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 10 | * permit persons to whom the Software is furnished to do so, subject to the following 11 | * conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be 14 | * included in all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS 19 | * OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT 21 | * OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | */ 23 | 'use strict'; 24 | 25 | const Validator = require('../../lib/validator').Validator; 26 | 27 | /* 28 | ======== A Handy Little Nodeunit Reference ======== 29 | https://github.com/caolan/nodeunit 30 | 31 | Test methods: 32 | test.expect(numAssertions) 33 | test.done() 34 | Test assertions: 35 | test.ok(value, [message]) 36 | test.equal(actual, expected, [message]) 37 | test.notEqual(actual, expected, [message]) 38 | test.deepEqual(actual, expected, [message]) 39 | test.notDeepEqual(actual, expected, [message]) 40 | test.strictEqual(actual, expected, [message]) 41 | test.notStrictEqual(actual, expected, [message]) 42 | test.throws(block, [error], [message]) 43 | test.doesNotThrow(block, [error], [message]) 44 | test.ifError(value) 45 | */ 46 | 47 | const checker = new Validator({ something: Validator.isNonEmpty }); 48 | 49 | exports.validator = { 50 | 51 | 'has option': (test) => { 52 | test.expect(1); 53 | // tests here 54 | 55 | test.doesNotThrow( 56 | () => { 57 | checker.validate({ something: 'test' }); 58 | }, 59 | Error, 60 | 'Should not fail with option present' 61 | ); 62 | 63 | test.done(); 64 | }, 65 | 'missing option': (test) => { 66 | test.expect(1); 67 | // tests here 68 | 69 | test.throws( 70 | () => { 71 | checker.validate({ bar: 'test' }); 72 | }, 73 | Error, 74 | 'Should fail with option missing' 75 | ); 76 | 77 | test.done(); 78 | }, 79 | 'no options': (test) => { 80 | test.expect(1); 81 | // tests here 82 | 83 | test.doesNotThrow( 84 | () => { 85 | const myChecker = new Validator({}); 86 | myChecker.validate({}); 87 | }, 88 | Error, 89 | 'Should not fail with no options or config' 90 | ); 91 | 92 | test.done(); 93 | }, 94 | }; 95 | -------------------------------------------------------------------------------- /test/resource/correctKeyOrCert.js: -------------------------------------------------------------------------------- 1 | exports.privateKey = 2 | 'MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDXHOqphcoT2V8C\ 3 | HHgmlrN4LtE7WNZKvNa7TXdoWNL7O22oWaAjBSttAJkaL9jKBpkhGvg9zD4nRhus\ 4 | htqBbqD9N41+TUs+Ao3RbULEdtczfqrM5bJyWTz+cxen/hZwr/3EJJQvL/M0yqEy\ 5 | ECm3BNK/gFZS9wT3W3Qh8mWJNj7hx772+/vdrMb0sqsBRNeSev4M5TWq4OTmR+br\ 6 | z3O6rA7Qw6RqHHazV4p75u4YQVko9bbSdmvhO5e2lDJ0NX2zSBN8h8HWVofqrsXn\ 7 | IU9E3g24X5+FfnVmpr7i76JFqsWBkuH+5jAyxf8e4QbjcwPIDLpjqpNfZn/KIEwu\ 8 | PfvTUgTRAgMBAAECggEAfPHM015ke5b8ekty9mb+I0Z+PUxiW9LHns9mFfPRyB9C\ 9 | mFBGlVDBYnTAeNdlwiKG+xWhLrPdPg6YsGM7s4ZAeBO2WbAnjgMJwKUH7PliEgT2\ 10 | zEB2n7h5lwneqbMJ8bYnXbApaicdyoIPkvaUZDum9Zhji5jzB4HVOIqEs2gQqi68\ 11 | ciSsFXyd0TFV76cNpP7xn/y38p8jD8BtyQ9rB/63a4IMvSeIkp2G0lL77OuyRrF+\ 12 | pj/c6n6c7bwxOCZXkzAeSC1CJk7qlyBqthgPgBWpZm+74RQG+oxkpDHXSeNYCMMj\ 13 | o0ftAfJJsHicOvwRphOyo1y7A0gxLyM+uCie46EigQKBgQDvMQxLiz6qPcPyGckw\ 14 | FhDMNWM9nV8UqfkJipqHchAsLpRxZs3bfES8wLOQ9iXxyik4WVghm1VdWLmMa7QO\ 15 | Ialeu4EsglXSxE3ED5Kbgz2my8aZwUT9V4/sl1fr1/cAWgPIwCBpbzS7PgFYNOB7\ 16 | txmi9egVsZFqTyJf2HX6t1U1mQKBgQDmOrRYKuGntiUG77U9Y7dQjlzOX+jwbf86\ 17 | f+7AhS1r9bbl3gHqFjoi2/5r8VNEtMnLgT0mmbKws0GQY6zC2zxSDbWHi2JPgWra\ 18 | mp9NdzYpUjGgtXF2WM0XnUJ+rcMJagk5kEHvNb0P1YGXtiSVlTKSffi7yuPxgV1M\ 19 | 5iJzYa/b+QKBgQDpdxZpJiCkSmVXiZO2O9NpfzK+kTydDsrlZUQvsEXY2h5KEe4c\ 20 | rc7p7a1XcOrYdlqFha9gHh3UXyW8yeOVZ4XJ0Xrj1tOuRa0iEZEn+ZHTnFLdAKg2\ 21 | H6F7bL2EleehkDrhV6ZLQyBVItBtKC90keOUDDHb+tXUzijfJIkDjq69AQKBgGyr\ 22 | jPCI3RZifTu27Z9ab+6QI/Ithy/lW7FrLXCl6/indHsvvUuWzj4iBQdCU4F8rDto\ 23 | d0q/BX8uPWZABg27mc9JNUQTYIYSmjMxBLx15pS+fTpUKogoBjZ9PiO5NPQ+nrgT\ 24 | BWw5u1G7/31ytfX9BG/tEfjiHZGi9D0V63D2zSFRAoGBAMPtk04KyHnUTSNMbYqJ\ 25 | ZLUMBMTwyFhezAiwpsat46k67kc5B94q8O5i9CCOod1cE+VSTcGK2Cm0PDKuFImw\ 26 | Ec4l/ueQzHWv0resBiHuLcZ0i/io90L2WujzjtDBaOCLRj5iV/8MUacwSyTF9NRa\ 27 | gACBOPJoGGPoH3Vfq8OIjzHA'; 28 | 29 | exports.certificate = 30 | 'MIIDyTCCArGgAwIBAgIJALdM2LOYxZ+7MA0GCSqGSIb3DQEBBQUAMHsxCzAJBgNV\ 31 | BAYTAlVTMQswCQYDVQQIDAJXQTEQMA4GA1UEBwwHUmVkbW9uZDESMBAGA1UECgwJ\ 32 | TWljcm9zb2Z0MRAwDgYDVQQDDAdteV9uYW1lMScwJQYJKoZIhvcNAQkBFhhteV9h\ 33 | ZGRyZXNzQG1pY3Jvc29mdC5jb20wHhcNMTYwNjIyMjAzMjEwWhcNMTcwNjIyMjAz\ 34 | MjEwWjB7MQswCQYDVQQGEwJVUzELMAkGA1UECAwCV0ExEDAOBgNVBAcMB1JlZG1v\ 35 | bmQxEjAQBgNVBAoMCU1pY3Jvc29mdDEQMA4GA1UEAwwHbXlfbmFtZTEnMCUGCSqG\ 36 | SIb3DQEJARYYbXlfYWRkcmVzc0BtaWNyb3NvZnQuY29tMIIBIjANBgkqhkiG9w0B\ 37 | AQEFAAOCAQ8AMIIBCgKCAQEA1xzqqYXKE9lfAhx4JpazeC7RO1jWSrzWu013aFjS\ 38 | +zttqFmgIwUrbQCZGi/YygaZIRr4Pcw+J0YbrIbagW6g/TeNfk1LPgKN0W1CxHbX\ 39 | M36qzOWyclk8/nMXp/4WcK/9xCSULy/zNMqhMhAptwTSv4BWUvcE91t0IfJliTY+\ 40 | 4ce+9vv73azG9LKrAUTXknr+DOU1quDk5kfm689zuqwO0MOkahx2s1eKe+buGEFZ\ 41 | KPW20nZr4TuXtpQydDV9s0gTfIfB1laH6q7F5yFPRN4NuF+fhX51Zqa+4u+iRarF\ 42 | gZLh/uYwMsX/HuEG43MDyAy6Y6qTX2Z/yiBMLj3701IE0QIDAQABo1AwTjAdBgNV\ 43 | HQ4EFgQUUWi/gYGWByiSlqi6TdzIdT8a/DEwHwYDVR0jBBgwFoAUUWi/gYGWByiS\ 44 | lqi6TdzIdT8a/DEwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEAWRdI\ 45 | tLikzV4zHVsOJtYyEzCNe+Qa07egrM5WginRLJ2b0d9UaUGDHFUswMoqYoBI4LKX\ 46 | 894vjUEXY9PWbSC9G34wRm+ECxQwyfPfz0R4/6gX/BQmneJ7AEhGws/7yIWc7yDr\ 47 | Nf1OdBOENW/8pJuaUZE+oA9xT08cBfx+bXPQGDTQ0FrdvjwLtC0C3fP9zPC3CT/P\ 48 | WEt3r1jKPWlbH7otOCbcWk+sV+xd5Rpb8QyGK2UoXm4RbqQPDl0OnY6eG4j/UH+u\ 49 | g7ovMfx6rN4LObItlGAHCl6YBdJXw0axxLtao4NHr5WzWjVYWJLI0ERxPjLL3qM7\ 50 | XscPLkVca2Q6eX1L+A=='; 51 | -------------------------------------------------------------------------------- /test/resource/private.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDXHOqphcoT2V8C 3 | HHgmlrN4LtE7WNZKvNa7TXdoWNL7O22oWaAjBSttAJkaL9jKBpkhGvg9zD4nRhus 4 | htqBbqD9N41+TUs+Ao3RbULEdtczfqrM5bJyWTz+cxen/hZwr/3EJJQvL/M0yqEy 5 | ECm3BNK/gFZS9wT3W3Qh8mWJNj7hx772+/vdrMb0sqsBRNeSev4M5TWq4OTmR+br 6 | z3O6rA7Qw6RqHHazV4p75u4YQVko9bbSdmvhO5e2lDJ0NX2zSBN8h8HWVofqrsXn 7 | IU9E3g24X5+FfnVmpr7i76JFqsWBkuH+5jAyxf8e4QbjcwPIDLpjqpNfZn/KIEwu 8 | PfvTUgTRAgMBAAECggEAfPHM015ke5b8ekty9mb+I0Z+PUxiW9LHns9mFfPRyB9C 9 | mFBGlVDBYnTAeNdlwiKG+xWhLrPdPg6YsGM7s4ZAeBO2WbAnjgMJwKUH7PliEgT2 10 | zEB2n7h5lwneqbMJ8bYnXbApaicdyoIPkvaUZDum9Zhji5jzB4HVOIqEs2gQqi68 11 | ciSsFXyd0TFV76cNpP7xn/y38p8jD8BtyQ9rB/63a4IMvSeIkp2G0lL77OuyRrF+ 12 | pj/c6n6c7bwxOCZXkzAeSC1CJk7qlyBqthgPgBWpZm+74RQG+oxkpDHXSeNYCMMj 13 | o0ftAfJJsHicOvwRphOyo1y7A0gxLyM+uCie46EigQKBgQDvMQxLiz6qPcPyGckw 14 | FhDMNWM9nV8UqfkJipqHchAsLpRxZs3bfES8wLOQ9iXxyik4WVghm1VdWLmMa7QO 15 | Ialeu4EsglXSxE3ED5Kbgz2my8aZwUT9V4/sl1fr1/cAWgPIwCBpbzS7PgFYNOB7 16 | txmi9egVsZFqTyJf2HX6t1U1mQKBgQDmOrRYKuGntiUG77U9Y7dQjlzOX+jwbf86 17 | f+7AhS1r9bbl3gHqFjoi2/5r8VNEtMnLgT0mmbKws0GQY6zC2zxSDbWHi2JPgWra 18 | mp9NdzYpUjGgtXF2WM0XnUJ+rcMJagk5kEHvNb0P1YGXtiSVlTKSffi7yuPxgV1M 19 | 5iJzYa/b+QKBgQDpdxZpJiCkSmVXiZO2O9NpfzK+kTydDsrlZUQvsEXY2h5KEe4c 20 | rc7p7a1XcOrYdlqFha9gHh3UXyW8yeOVZ4XJ0Xrj1tOuRa0iEZEn+ZHTnFLdAKg2 21 | H6F7bL2EleehkDrhV6ZLQyBVItBtKC90keOUDDHb+tXUzijfJIkDjq69AQKBgGyr 22 | jPCI3RZifTu27Z9ab+6QI/Ithy/lW7FrLXCl6/indHsvvUuWzj4iBQdCU4F8rDto 23 | d0q/BX8uPWZABg27mc9JNUQTYIYSmjMxBLx15pS+fTpUKogoBjZ9PiO5NPQ+nrgT 24 | BWw5u1G7/31ytfX9BG/tEfjiHZGi9D0V63D2zSFRAoGBAMPtk04KyHnUTSNMbYqJ 25 | ZLUMBMTwyFhezAiwpsat46k67kc5B94q8O5i9CCOod1cE+VSTcGK2Cm0PDKuFImw 26 | Ec4l/ueQzHWv0resBiHuLcZ0i/io90L2WujzjtDBaOCLRj5iV/8MUacwSyTF9NRa 27 | gACBOPJoGGPoH3Vfq8OIjzHA 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /test/resource/public.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDyTCCArGgAwIBAgIJALdM2LOYxZ+7MA0GCSqGSIb3DQEBBQUAMHsxCzAJBgNV 3 | BAYTAlVTMQswCQYDVQQIDAJXQTEQMA4GA1UEBwwHUmVkbW9uZDESMBAGA1UECgwJ 4 | TWljcm9zb2Z0MRAwDgYDVQQDDAdteV9uYW1lMScwJQYJKoZIhvcNAQkBFhhteV9h 5 | ZGRyZXNzQG1pY3Jvc29mdC5jb20wHhcNMTYwNjIyMjAzMjEwWhcNMTcwNjIyMjAz 6 | MjEwWjB7MQswCQYDVQQGEwJVUzELMAkGA1UECAwCV0ExEDAOBgNVBAcMB1JlZG1v 7 | bmQxEjAQBgNVBAoMCU1pY3Jvc29mdDEQMA4GA1UEAwwHbXlfbmFtZTEnMCUGCSqG 8 | SIb3DQEJARYYbXlfYWRkcmVzc0BtaWNyb3NvZnQuY29tMIIBIjANBgkqhkiG9w0B 9 | AQEFAAOCAQ8AMIIBCgKCAQEA1xzqqYXKE9lfAhx4JpazeC7RO1jWSrzWu013aFjS 10 | +zttqFmgIwUrbQCZGi/YygaZIRr4Pcw+J0YbrIbagW6g/TeNfk1LPgKN0W1CxHbX 11 | M36qzOWyclk8/nMXp/4WcK/9xCSULy/zNMqhMhAptwTSv4BWUvcE91t0IfJliTY+ 12 | 4ce+9vv73azG9LKrAUTXknr+DOU1quDk5kfm689zuqwO0MOkahx2s1eKe+buGEFZ 13 | KPW20nZr4TuXtpQydDV9s0gTfIfB1laH6q7F5yFPRN4NuF+fhX51Zqa+4u+iRarF 14 | gZLh/uYwMsX/HuEG43MDyAy6Y6qTX2Z/yiBMLj3701IE0QIDAQABo1AwTjAdBgNV 15 | HQ4EFgQUUWi/gYGWByiSlqi6TdzIdT8a/DEwHwYDVR0jBBgwFoAUUWi/gYGWByiS 16 | lqi6TdzIdT8a/DEwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEAWRdI 17 | tLikzV4zHVsOJtYyEzCNe+Qa07egrM5WginRLJ2b0d9UaUGDHFUswMoqYoBI4LKX 18 | 894vjUEXY9PWbSC9G34wRm+ECxQwyfPfz0R4/6gX/BQmneJ7AEhGws/7yIWc7yDr 19 | Nf1OdBOENW/8pJuaUZE+oA9xT08cBfx+bXPQGDTQ0FrdvjwLtC0C3fP9zPC3CT/P 20 | WEt3r1jKPWlbH7otOCbcWk+sV+xd5Rpb8QyGK2UoXm4RbqQPDl0OnY6eG4j/UH+u 21 | g7ovMfx6rN4LObItlGAHCl6YBdJXw0axxLtao4NHr5WzWjVYWJLI0ERxPjLL3qM7 22 | XscPLkVca2Q6eX1L+A== 23 | -----END CERTIFICATE----- 24 | -------------------------------------------------------------------------------- /typings/browser.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | /// 4 | /// 5 | -------------------------------------------------------------------------------- /typings/browser/ambient/express/index.d.ts: -------------------------------------------------------------------------------- 1 | // Generated by typings 2 | // Source: https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/7de6c3dd94feaeb21f20054b9f30d5dabc5efabd/express/express.d.ts 3 | // Type definitions for Express 4.x 4 | // Project: http://expressjs.com 5 | // Definitions by: Boris Yankov 6 | // Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped 7 | 8 | /* =================== USAGE =================== 9 | 10 | import * as express from "express"; 11 | var app = express(); 12 | 13 | =============================================== */ 14 | 15 | 16 | declare module "express" { 17 | import * as serveStatic from "serve-static"; 18 | import * as core from "express-serve-static-core"; 19 | 20 | /** 21 | * Creates an Express application. The express() function is a top-level function exported by the express module. 22 | */ 23 | function e(): core.Express; 24 | 25 | namespace e { 26 | 27 | /** 28 | * This is the only built-in middleware function in Express. It serves static files and is based on serve-static. 29 | */ 30 | var static: typeof serveStatic; 31 | 32 | export function Router(options?: any): core.Router; 33 | 34 | interface Application extends core.Application { } 35 | interface CookieOptions extends core.CookieOptions { } 36 | interface Errback extends core.Errback { } 37 | interface ErrorRequestHandler extends core.ErrorRequestHandler { } 38 | interface Express extends core.Express { } 39 | interface Handler extends core.Handler { } 40 | interface IRoute extends core.IRoute { } 41 | interface IRouter extends core.IRouter { } 42 | interface IRouterMatcher extends core.IRouterMatcher { } 43 | interface MediaType extends core.MediaType { } 44 | interface NextFunction extends core.NextFunction { } 45 | interface Request extends core.Request { } 46 | interface RequestHandler extends core.RequestHandler { } 47 | interface RequestParamHandler extends core.RequestParamHandler { } 48 | export interface Response extends core.Response { } 49 | interface Router extends core.Router { } 50 | interface Send extends core.Send { } 51 | } 52 | 53 | export = e; 54 | } -------------------------------------------------------------------------------- /typings/browser/ambient/serve-static/index.d.ts: -------------------------------------------------------------------------------- 1 | // Generated by typings 2 | // Source: https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/7de6c3dd94feaeb21f20054b9f30d5dabc5efabd/serve-static/serve-static.d.ts 3 | // Type definitions for serve-static 1.7.1 4 | // Project: https://github.com/expressjs/serve-static 5 | // Definitions by: Uros Smolnik 6 | // Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped 7 | 8 | /* =================== USAGE =================== 9 | 10 | import * as serveStatic from "serve-static"; 11 | app.use(serveStatic("public/ftp", {"index": ["default.html", "default.htm"]})) 12 | 13 | =============================================== */ 14 | 15 | 16 | declare module "serve-static" { 17 | import * as express from "express-serve-static-core"; 18 | 19 | /** 20 | * Create a new middleware function to serve files from within a given root directory. 21 | * The file to serve will be determined by combining req.url with the provided root directory. 22 | * When a file is not found, instead of sending a 404 response, this module will instead call next() to move on to the next middleware, allowing for stacking and fall-backs. 23 | */ 24 | function serveStatic(root: string, options?: { 25 | /** 26 | * Set how "dotfiles" are treated when encountered. A dotfile is a file or directory that begins with a dot ("."). 27 | * Note this check is done on the path itself without checking if the path actually exists on the disk. 28 | * If root is specified, only the dotfiles above the root are checked (i.e. the root itself can be within a dotfile when when set to "deny"). 29 | * The default value is 'ignore'. 30 | * 'allow' No special treatment for dotfiles 31 | * 'deny' Send a 403 for any request for a dotfile 32 | * 'ignore' Pretend like the dotfile does not exist and call next() 33 | */ 34 | dotfiles?: string; 35 | 36 | /** 37 | * Enable or disable etag generation, defaults to true. 38 | */ 39 | etag?: boolean; 40 | 41 | /** 42 | * Set file extension fallbacks. When set, if a file is not found, the given extensions will be added to the file name and search for. 43 | * The first that exists will be served. Example: ['html', 'htm']. 44 | * The default value is false. 45 | */ 46 | extensions?: string[]; 47 | 48 | /** 49 | * By default this module will send "index.html" files in response to a request on a directory. 50 | * To disable this set false or to supply a new index pass a string or an array in preferred order. 51 | */ 52 | index?: boolean|string|string[]; 53 | 54 | /** 55 | * Enable or disable Last-Modified header, defaults to true. Uses the file system's last modified value. 56 | */ 57 | lastModified?: boolean; 58 | 59 | /** 60 | * Provide a max-age in milliseconds for http caching, defaults to 0. This can also be a string accepted by the ms module. 61 | */ 62 | maxAge?: number|string; 63 | 64 | /** 65 | * Redirect to trailing "/" when the pathname is a dir. Defaults to true. 66 | */ 67 | redirect?: boolean; 68 | 69 | /** 70 | * Function to set custom headers on response. Alterations to the headers need to occur synchronously. 71 | * The function is called as fn(res, path, stat), where the arguments are: 72 | * res the response object 73 | * path the file path that is being sent 74 | * stat the stat object of the file that is being sent 75 | */ 76 | setHeaders?: (res: express.Response, path: string, stat: any) => any; 77 | }): express.Handler; 78 | 79 | import * as m from "mime"; 80 | 81 | namespace serveStatic { 82 | var mime: typeof m; 83 | } 84 | 85 | export = serveStatic; 86 | } -------------------------------------------------------------------------------- /typings/main.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | /// 4 | /// 5 | -------------------------------------------------------------------------------- /typings/main/ambient/express/index.d.ts: -------------------------------------------------------------------------------- 1 | // Generated by typings 2 | // Source: https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/7de6c3dd94feaeb21f20054b9f30d5dabc5efabd/express/express.d.ts 3 | // Type definitions for Express 4.x 4 | // Project: http://expressjs.com 5 | // Definitions by: Boris Yankov 6 | // Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped 7 | 8 | /* =================== USAGE =================== 9 | 10 | import * as express from "express"; 11 | var app = express(); 12 | 13 | =============================================== */ 14 | 15 | 16 | declare module "express" { 17 | import * as serveStatic from "serve-static"; 18 | import * as core from "express-serve-static-core"; 19 | 20 | /** 21 | * Creates an Express application. The express() function is a top-level function exported by the express module. 22 | */ 23 | function e(): core.Express; 24 | 25 | namespace e { 26 | 27 | /** 28 | * This is the only built-in middleware function in Express. It serves static files and is based on serve-static. 29 | */ 30 | var static: typeof serveStatic; 31 | 32 | export function Router(options?: any): core.Router; 33 | 34 | interface Application extends core.Application { } 35 | interface CookieOptions extends core.CookieOptions { } 36 | interface Errback extends core.Errback { } 37 | interface ErrorRequestHandler extends core.ErrorRequestHandler { } 38 | interface Express extends core.Express { } 39 | interface Handler extends core.Handler { } 40 | interface IRoute extends core.IRoute { } 41 | interface IRouter extends core.IRouter { } 42 | interface IRouterMatcher extends core.IRouterMatcher { } 43 | interface MediaType extends core.MediaType { } 44 | interface NextFunction extends core.NextFunction { } 45 | interface Request extends core.Request { } 46 | interface RequestHandler extends core.RequestHandler { } 47 | interface RequestParamHandler extends core.RequestParamHandler { } 48 | export interface Response extends core.Response { } 49 | interface Router extends core.Router { } 50 | interface Send extends core.Send { } 51 | } 52 | 53 | export = e; 54 | } -------------------------------------------------------------------------------- /typings/main/ambient/serve-static/index.d.ts: -------------------------------------------------------------------------------- 1 | // Generated by typings 2 | // Source: https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/7de6c3dd94feaeb21f20054b9f30d5dabc5efabd/serve-static/serve-static.d.ts 3 | // Type definitions for serve-static 1.7.1 4 | // Project: https://github.com/expressjs/serve-static 5 | // Definitions by: Uros Smolnik 6 | // Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped 7 | 8 | /* =================== USAGE =================== 9 | 10 | import * as serveStatic from "serve-static"; 11 | app.use(serveStatic("public/ftp", {"index": ["default.html", "default.htm"]})) 12 | 13 | =============================================== */ 14 | 15 | 16 | declare module "serve-static" { 17 | import * as express from "express-serve-static-core"; 18 | 19 | /** 20 | * Create a new middleware function to serve files from within a given root directory. 21 | * The file to serve will be determined by combining req.url with the provided root directory. 22 | * When a file is not found, instead of sending a 404 response, this module will instead call next() to move on to the next middleware, allowing for stacking and fall-backs. 23 | */ 24 | function serveStatic(root: string, options?: { 25 | /** 26 | * Set how "dotfiles" are treated when encountered. A dotfile is a file or directory that begins with a dot ("."). 27 | * Note this check is done on the path itself without checking if the path actually exists on the disk. 28 | * If root is specified, only the dotfiles above the root are checked (i.e. the root itself can be within a dotfile when when set to "deny"). 29 | * The default value is 'ignore'. 30 | * 'allow' No special treatment for dotfiles 31 | * 'deny' Send a 403 for any request for a dotfile 32 | * 'ignore' Pretend like the dotfile does not exist and call next() 33 | */ 34 | dotfiles?: string; 35 | 36 | /** 37 | * Enable or disable etag generation, defaults to true. 38 | */ 39 | etag?: boolean; 40 | 41 | /** 42 | * Set file extension fallbacks. When set, if a file is not found, the given extensions will be added to the file name and search for. 43 | * The first that exists will be served. Example: ['html', 'htm']. 44 | * The default value is false. 45 | */ 46 | extensions?: string[]; 47 | 48 | /** 49 | * By default this module will send "index.html" files in response to a request on a directory. 50 | * To disable this set false or to supply a new index pass a string or an array in preferred order. 51 | */ 52 | index?: boolean|string|string[]; 53 | 54 | /** 55 | * Enable or disable Last-Modified header, defaults to true. Uses the file system's last modified value. 56 | */ 57 | lastModified?: boolean; 58 | 59 | /** 60 | * Provide a max-age in milliseconds for http caching, defaults to 0. This can also be a string accepted by the ms module. 61 | */ 62 | maxAge?: number|string; 63 | 64 | /** 65 | * Redirect to trailing "/" when the pathname is a dir. Defaults to true. 66 | */ 67 | redirect?: boolean; 68 | 69 | /** 70 | * Function to set custom headers on response. Alterations to the headers need to occur synchronously. 71 | * The function is called as fn(res, path, stat), where the arguments are: 72 | * res the response object 73 | * path the file path that is being sent 74 | * stat the stat object of the file that is being sent 75 | */ 76 | setHeaders?: (res: express.Response, path: string, stat: any) => any; 77 | }): express.Handler; 78 | 79 | import * as m from "mime"; 80 | 81 | namespace serveStatic { 82 | var mime: typeof m; 83 | } 84 | 85 | export = serveStatic; 86 | } --------------------------------------------------------------------------------