Compare commits
	
		
			No commits in common. "master" and "v1.x" have entirely different histories.
		
	
	
		
	
		
							
								
								
									
										15
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										15
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @ -1,8 +1,6 @@ | ||||
| # Logs | ||||
| logs | ||||
| *.log | ||||
| npm-debug.log* | ||||
| .vscode | ||||
| 
 | ||||
| # Runtime data | ||||
| pids | ||||
| @ -15,9 +13,6 @@ lib-cov | ||||
| # Coverage directory used by tools like istanbul | ||||
| coverage | ||||
| 
 | ||||
| # nyc test coverage | ||||
| .nyc_output | ||||
| 
 | ||||
| # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) | ||||
| .grunt | ||||
| 
 | ||||
| @ -27,12 +22,6 @@ coverage | ||||
| # Compiled binary addons (http://nodejs.org/api/addons.html) | ||||
| build/Release | ||||
| 
 | ||||
| # Dependency directories | ||||
| # Dependency directory | ||||
| # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git | ||||
| node_modules | ||||
| jspm_packages | ||||
| 
 | ||||
| # Optional npm cache directory | ||||
| .npm | ||||
| 
 | ||||
| # Optional REPL history | ||||
| .node_repl_history | ||||
|  | ||||
| @ -1,7 +0,0 @@ | ||||
| { | ||||
|   "bracketSpacing": true, | ||||
|   "printWidth": 120, | ||||
|   "tabWidth": 2, | ||||
|   "trailingComma": "none", | ||||
|   "useTabs": true | ||||
| } | ||||
							
								
								
									
										575
									
								
								LICENSE
									
									
									
									
									
								
							
							
						
						
									
										575
									
								
								LICENSE
									
									
									
									
									
								
							| @ -1,375 +1,202 @@ | ||||
| Copyright 2015-2019 AJ ONeal | ||||
|                                  Apache License | ||||
|                            Version 2.0, January 2004 | ||||
|                         http://www.apache.org/licenses/ | ||||
| 
 | ||||
|    TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION | ||||
| 
 | ||||
|    1. Definitions. | ||||
| 
 | ||||
|       "License" shall mean the terms and conditions for use, reproduction, | ||||
|       and distribution as defined by Sections 1 through 9 of this document. | ||||
| 
 | ||||
|       "Licensor" shall mean the copyright owner or entity authorized by | ||||
|       the copyright owner that is granting the License. | ||||
| 
 | ||||
|       "Legal Entity" shall mean the union of the acting entity and all | ||||
|       other entities that control, are controlled by, or are under common | ||||
|       control with that entity. For the purposes of this definition, | ||||
|       "control" means (i) the power, direct or indirect, to cause the | ||||
|       direction or management of such entity, whether by contract or | ||||
|       otherwise, or (ii) ownership of fifty percent (50%) or more of the | ||||
|       outstanding shares, or (iii) beneficial ownership of such entity. | ||||
| 
 | ||||
|       "You" (or "Your") shall mean an individual or Legal Entity | ||||
|       exercising permissions granted by this License. | ||||
| 
 | ||||
|       "Source" form shall mean the preferred form for making modifications, | ||||
|       including but not limited to software source code, documentation | ||||
|       source, and configuration files. | ||||
| 
 | ||||
|       "Object" form shall mean any form resulting from mechanical | ||||
|       transformation or translation of a Source form, including but | ||||
|       not limited to compiled object code, generated documentation, | ||||
|       and conversions to other media types. | ||||
| 
 | ||||
|       "Work" shall mean the work of authorship, whether in Source or | ||||
|       Object form, made available under the License, as indicated by a | ||||
|       copyright notice that is included in or attached to the work | ||||
|       (an example is provided in the Appendix below). | ||||
| 
 | ||||
|       "Derivative Works" shall mean any work, whether in Source or Object | ||||
|       form, that is based on (or derived from) the Work and for which the | ||||
|       editorial revisions, annotations, elaborations, or other modifications | ||||
|       represent, as a whole, an original work of authorship. For the purposes | ||||
|       of this License, Derivative Works shall not include works that remain | ||||
|       separable from, or merely link (or bind by name) to the interfaces of, | ||||
|       the Work and Derivative Works thereof. | ||||
| 
 | ||||
|       "Contribution" shall mean any work of authorship, including | ||||
|       the original version of the Work and any modifications or additions | ||||
|       to that Work or Derivative Works thereof, that is intentionally | ||||
|       submitted to Licensor for inclusion in the Work by the copyright owner | ||||
|       or by an individual or Legal Entity authorized to submit on behalf of | ||||
|       the copyright owner. For the purposes of this definition, "submitted" | ||||
|       means any form of electronic, verbal, or written communication sent | ||||
|       to the Licensor or its representatives, including but not limited to | ||||
|       communication on electronic mailing lists, source code control systems, | ||||
|       and issue tracking systems that are managed by, or on behalf of, the | ||||
|       Licensor for the purpose of discussing and improving the Work, but | ||||
|       excluding communication that is conspicuously marked or otherwise | ||||
|       designated in writing by the copyright owner as "Not a Contribution." | ||||
| 
 | ||||
|       "Contributor" shall mean Licensor and any individual or Legal Entity | ||||
|       on behalf of whom a Contribution has been received by Licensor and | ||||
|       subsequently incorporated within the Work. | ||||
| 
 | ||||
|    2. Grant of Copyright License. Subject to the terms and conditions of | ||||
|       this License, each Contributor hereby grants to You a perpetual, | ||||
|       worldwide, non-exclusive, no-charge, royalty-free, irrevocable | ||||
|       copyright license to reproduce, prepare Derivative Works of, | ||||
|       publicly display, publicly perform, sublicense, and distribute the | ||||
|       Work and such Derivative Works in Source or Object form. | ||||
| 
 | ||||
|    3. Grant of Patent License. Subject to the terms and conditions of | ||||
|       this License, each Contributor hereby grants to You a perpetual, | ||||
|       worldwide, non-exclusive, no-charge, royalty-free, irrevocable | ||||
|       (except as stated in this section) patent license to make, have made, | ||||
|       use, offer to sell, sell, import, and otherwise transfer the Work, | ||||
|       where such license applies only to those patent claims licensable | ||||
|       by such Contributor that are necessarily infringed by their | ||||
|       Contribution(s) alone or by combination of their Contribution(s) | ||||
|       with the Work to which such Contribution(s) was submitted. If You | ||||
|       institute patent litigation against any entity (including a | ||||
|       cross-claim or counterclaim in a lawsuit) alleging that the Work | ||||
|       or a Contribution incorporated within the Work constitutes direct | ||||
|       or contributory patent infringement, then any patent licenses | ||||
|       granted to You under this License for that Work shall terminate | ||||
|       as of the date such litigation is filed. | ||||
| 
 | ||||
|    4. Redistribution. You may reproduce and distribute copies of the | ||||
|       Work or Derivative Works thereof in any medium, with or without | ||||
|       modifications, and in Source or Object form, provided that You | ||||
|       meet the following conditions: | ||||
| 
 | ||||
|       (a) You must give any other recipients of the Work or | ||||
|           Derivative Works a copy of this License; and | ||||
| 
 | ||||
|       (b) You must cause any modified files to carry prominent notices | ||||
|           stating that You changed the files; and | ||||
| 
 | ||||
|       (c) You must retain, in the Source form of any Derivative Works | ||||
|           that You distribute, all copyright, patent, trademark, and | ||||
|           attribution notices from the Source form of the Work, | ||||
|           excluding those notices that do not pertain to any part of | ||||
|           the Derivative Works; and | ||||
| 
 | ||||
|       (d) If the Work includes a "NOTICE" text file as part of its | ||||
|           distribution, then any Derivative Works that You distribute must | ||||
|           include a readable copy of the attribution notices contained | ||||
|           within such NOTICE file, excluding those notices that do not | ||||
|           pertain to any part of the Derivative Works, in at least one | ||||
|           of the following places: within a NOTICE text file distributed | ||||
|           as part of the Derivative Works; within the Source form or | ||||
|           documentation, if provided along with the Derivative Works; or, | ||||
|           within a display generated by the Derivative Works, if and | ||||
|           wherever such third-party notices normally appear. The contents | ||||
|           of the NOTICE file are for informational purposes only and | ||||
|           do not modify the License. You may add Your own attribution | ||||
|           notices within Derivative Works that You distribute, alongside | ||||
|           or as an addendum to the NOTICE text from the Work, provided | ||||
|           that such additional attribution notices cannot be construed | ||||
|           as modifying the License. | ||||
| 
 | ||||
|       You may add Your own copyright statement to Your modifications and | ||||
|       may provide additional or different license terms and conditions | ||||
|       for use, reproduction, or distribution of Your modifications, or | ||||
|       for any such Derivative Works as a whole, provided Your use, | ||||
|       reproduction, and distribution of the Work otherwise complies with | ||||
|       the conditions stated in this License. | ||||
| 
 | ||||
|    5. Submission of Contributions. Unless You explicitly state otherwise, | ||||
|       any Contribution intentionally submitted for inclusion in the Work | ||||
|       by You to the Licensor shall be under the terms and conditions of | ||||
|       this License, without any additional terms or conditions. | ||||
|       Notwithstanding the above, nothing herein shall supersede or modify | ||||
|       the terms of any separate license agreement you may have executed | ||||
|       with Licensor regarding such Contributions. | ||||
| 
 | ||||
|    6. Trademarks. This License does not grant permission to use the trade | ||||
|       names, trademarks, service marks, or product names of the Licensor, | ||||
|       except as required for reasonable and customary use in describing the | ||||
|       origin of the Work and reproducing the content of the NOTICE file. | ||||
| 
 | ||||
|    7. Disclaimer of Warranty. Unless required by applicable law or | ||||
|       agreed to in writing, Licensor provides the Work (and each | ||||
|       Contributor provides its Contributions) on an "AS IS" BASIS, | ||||
|       WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or | ||||
|       implied, including, without limitation, any warranties or conditions | ||||
|       of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A | ||||
|       PARTICULAR PURPOSE. You are solely responsible for determining the | ||||
|       appropriateness of using or redistributing the Work and assume any | ||||
|       risks associated with Your exercise of permissions under this License. | ||||
| 
 | ||||
|    8. Limitation of Liability. In no event and under no legal theory, | ||||
|       whether in tort (including negligence), contract, or otherwise, | ||||
|       unless required by applicable law (such as deliberate and grossly | ||||
|       negligent acts) or agreed to in writing, shall any Contributor be | ||||
|       liable to You for damages, including any direct, indirect, special, | ||||
|       incidental, or consequential damages of any character arising as a | ||||
|       result of this License or out of the use or inability to use the | ||||
|       Work (including but not limited to damages for loss of goodwill, | ||||
|       work stoppage, computer failure or malfunction, or any and all | ||||
|       other commercial damages or losses), even if such Contributor | ||||
|       has been advised of the possibility of such damages. | ||||
| 
 | ||||
|    9. Accepting Warranty or Additional Liability. While redistributing | ||||
|       the Work or Derivative Works thereof, You may choose to offer, | ||||
|       and charge a fee for, acceptance of support, warranty, indemnity, | ||||
|       or other liability obligations and/or rights consistent with this | ||||
|       License. However, in accepting such obligations, You may act only | ||||
|       on Your own behalf and on Your sole responsibility, not on behalf | ||||
|       of any other Contributor, and only if You agree to indemnify, | ||||
|       defend, and hold each Contributor harmless for any liability | ||||
|       incurred by, or claims asserted against, such Contributor by reason | ||||
|       of your accepting any such warranty or additional liability. | ||||
| 
 | ||||
|    END OF TERMS AND CONDITIONS | ||||
| 
 | ||||
|    APPENDIX: How to apply the Apache License to your work. | ||||
| 
 | ||||
|       To apply the Apache License to your work, attach the following | ||||
|       boilerplate notice, with the fields enclosed by brackets "{}" | ||||
|       replaced with your own identifying information. (Don't include | ||||
|       the brackets!)  The text should be enclosed in the appropriate | ||||
|       comment syntax for the file format. We also recommend that a | ||||
|       file or class name and description of purpose be included on the | ||||
|       same "printed page" as the copyright notice for easier | ||||
|       identification within third-party archives. | ||||
| 
 | ||||
|    Copyright {yyyy} {name of copyright owner} | ||||
| 
 | ||||
|    Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|    you may not use this file except in compliance with the License. | ||||
|    You may obtain a copy of the License at | ||||
| 
 | ||||
|        http://www.apache.org/licenses/LICENSE-2.0 | ||||
| 
 | ||||
|    Unless required by applicable law or agreed to in writing, software | ||||
|    distributed under the License is distributed on an "AS IS" BASIS, | ||||
|    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|    See the License for the specific language governing permissions and | ||||
|    limitations under the License. | ||||
| 
 | ||||
| Mozilla Public License Version 2.0 | ||||
| ================================== | ||||
| 
 | ||||
| 1. Definitions | ||||
| -------------- | ||||
| 
 | ||||
| 1.1. "Contributor" | ||||
|     means each individual or legal entity that creates, contributes to | ||||
|     the creation of, or owns Covered Software. | ||||
| 
 | ||||
| 1.2. "Contributor Version" | ||||
|     means the combination of the Contributions of others (if any) used | ||||
|     by a Contributor and that particular Contributor's Contribution. | ||||
| 
 | ||||
| 1.3. "Contribution" | ||||
|     means Covered Software of a particular Contributor. | ||||
| 
 | ||||
| 1.4. "Covered Software" | ||||
|     means Source Code Form to which the initial Contributor has attached | ||||
|     the notice in Exhibit A, the Executable Form of such Source Code | ||||
|     Form, and Modifications of such Source Code Form, in each case | ||||
|     including portions thereof. | ||||
| 
 | ||||
| 1.5. "Incompatible With Secondary Licenses" | ||||
|     means | ||||
| 
 | ||||
|     (a) that the initial Contributor has attached the notice described | ||||
|         in Exhibit B to the Covered Software; or | ||||
| 
 | ||||
|     (b) that the Covered Software was made available under the terms of | ||||
|         version 1.1 or earlier of the License, but not also under the | ||||
|         terms of a Secondary License. | ||||
| 
 | ||||
| 1.6. "Executable Form" | ||||
|     means any form of the work other than Source Code Form. | ||||
| 
 | ||||
| 1.7. "Larger Work" | ||||
|     means a work that combines Covered Software with other material, in | ||||
|     a separate file or files, that is not Covered Software. | ||||
| 
 | ||||
| 1.8. "License" | ||||
|     means this document. | ||||
| 
 | ||||
| 1.9. "Licensable" | ||||
|     means having the right to grant, to the maximum extent possible, | ||||
|     whether at the time of the initial grant or subsequently, any and | ||||
|     all of the rights conveyed by this License. | ||||
| 
 | ||||
| 1.10. "Modifications" | ||||
|     means any of the following: | ||||
| 
 | ||||
|     (a) any file in Source Code Form that results from an addition to, | ||||
|         deletion from, or modification of the contents of Covered | ||||
|         Software; or | ||||
| 
 | ||||
|     (b) any new file in Source Code Form that contains any Covered | ||||
|         Software. | ||||
| 
 | ||||
| 1.11. "Patent Claims" of a Contributor | ||||
|     means any patent claim(s), including without limitation, method, | ||||
|     process, and apparatus claims, in any patent Licensable by such | ||||
|     Contributor that would be infringed, but for the grant of the | ||||
|     License, by the making, using, selling, offering for sale, having | ||||
|     made, import, or transfer of either its Contributions or its | ||||
|     Contributor Version. | ||||
| 
 | ||||
| 1.12. "Secondary License" | ||||
|     means either the GNU General Public License, Version 2.0, the GNU | ||||
|     Lesser General Public License, Version 2.1, the GNU Affero General | ||||
|     Public License, Version 3.0, or any later versions of those | ||||
|     licenses. | ||||
| 
 | ||||
| 1.13. "Source Code Form" | ||||
|     means the form of the work preferred for making modifications. | ||||
| 
 | ||||
| 1.14. "You" (or "Your") | ||||
|     means an individual or a legal entity exercising rights under this | ||||
|     License. For legal entities, "You" includes any entity that | ||||
|     controls, is controlled by, or is under common control with You. For | ||||
|     purposes of this definition, "control" means (a) the power, direct | ||||
|     or indirect, to cause the direction or management of such entity, | ||||
|     whether by contract or otherwise, or (b) ownership of more than | ||||
|     fifty percent (50%) of the outstanding shares or beneficial | ||||
|     ownership of such entity. | ||||
| 
 | ||||
| 2. License Grants and Conditions | ||||
| -------------------------------- | ||||
| 
 | ||||
| 2.1. Grants | ||||
| 
 | ||||
| Each Contributor hereby grants You a world-wide, royalty-free, | ||||
| non-exclusive license: | ||||
| 
 | ||||
| (a) under intellectual property rights (other than patent or trademark) | ||||
|     Licensable by such Contributor to use, reproduce, make available, | ||||
|     modify, display, perform, distribute, and otherwise exploit its | ||||
|     Contributions, either on an unmodified basis, with Modifications, or | ||||
|     as part of a Larger Work; and | ||||
| 
 | ||||
| (b) under Patent Claims of such Contributor to make, use, sell, offer | ||||
|     for sale, have made, import, and otherwise transfer either its | ||||
|     Contributions or its Contributor Version. | ||||
| 
 | ||||
| 2.2. Effective Date | ||||
| 
 | ||||
| The licenses granted in Section 2.1 with respect to any Contribution | ||||
| become effective for each Contribution on the date the Contributor first | ||||
| distributes such Contribution. | ||||
| 
 | ||||
| 2.3. Limitations on Grant Scope | ||||
| 
 | ||||
| The licenses granted in this Section 2 are the only rights granted under | ||||
| this License. No additional rights or licenses will be implied from the | ||||
| distribution or licensing of Covered Software under this License. | ||||
| Notwithstanding Section 2.1(b) above, no patent license is granted by a | ||||
| Contributor: | ||||
| 
 | ||||
| (a) for any code that a Contributor has removed from Covered Software; | ||||
|     or | ||||
| 
 | ||||
| (b) for infringements caused by: (i) Your and any other third party's | ||||
|     modifications of Covered Software, or (ii) the combination of its | ||||
|     Contributions with other software (except as part of its Contributor | ||||
|     Version); or | ||||
| 
 | ||||
| (c) under Patent Claims infringed by Covered Software in the absence of | ||||
|     its Contributions. | ||||
| 
 | ||||
| This License does not grant any rights in the trademarks, service marks, | ||||
| or logos of any Contributor (except as may be necessary to comply with | ||||
| the notice requirements in Section 3.4). | ||||
| 
 | ||||
| 2.4. Subsequent Licenses | ||||
| 
 | ||||
| No Contributor makes additional grants as a result of Your choice to | ||||
| distribute the Covered Software under a subsequent version of this | ||||
| License (see Section 10.2) or under the terms of a Secondary License (if | ||||
| permitted under the terms of Section 3.3). | ||||
| 
 | ||||
| 2.5. Representation | ||||
| 
 | ||||
| Each Contributor represents that the Contributor believes its | ||||
| Contributions are its original creation(s) or it has sufficient rights | ||||
| to grant the rights to its Contributions conveyed by this License. | ||||
| 
 | ||||
| 2.6. Fair Use | ||||
| 
 | ||||
| This License is not intended to limit any rights You have under | ||||
| applicable copyright doctrines of fair use, fair dealing, or other | ||||
| equivalents. | ||||
| 
 | ||||
| 2.7. Conditions | ||||
| 
 | ||||
| Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted | ||||
| in Section 2.1. | ||||
| 
 | ||||
| 3. Responsibilities | ||||
| ------------------- | ||||
| 
 | ||||
| 3.1. Distribution of Source Form | ||||
| 
 | ||||
| All distribution of Covered Software in Source Code Form, including any | ||||
| Modifications that You create or to which You contribute, must be under | ||||
| the terms of this License. You must inform recipients that the Source | ||||
| Code Form of the Covered Software is governed by the terms of this | ||||
| License, and how they can obtain a copy of this License. You may not | ||||
| attempt to alter or restrict the recipients' rights in the Source Code | ||||
| Form. | ||||
| 
 | ||||
| 3.2. Distribution of Executable Form | ||||
| 
 | ||||
| If You distribute Covered Software in Executable Form then: | ||||
| 
 | ||||
| (a) such Covered Software must also be made available in Source Code | ||||
|     Form, as described in Section 3.1, and You must inform recipients of | ||||
|     the Executable Form how they can obtain a copy of such Source Code | ||||
|     Form by reasonable means in a timely manner, at a charge no more | ||||
|     than the cost of distribution to the recipient; and | ||||
| 
 | ||||
| (b) You may distribute such Executable Form under the terms of this | ||||
|     License, or sublicense it under different terms, provided that the | ||||
|     license for the Executable Form does not attempt to limit or alter | ||||
|     the recipients' rights in the Source Code Form under this License. | ||||
| 
 | ||||
| 3.3. Distribution of a Larger Work | ||||
| 
 | ||||
| You may create and distribute a Larger Work under terms of Your choice, | ||||
| provided that You also comply with the requirements of this License for | ||||
| the Covered Software. If the Larger Work is a combination of Covered | ||||
| Software with a work governed by one or more Secondary Licenses, and the | ||||
| Covered Software is not Incompatible With Secondary Licenses, this | ||||
| License permits You to additionally distribute such Covered Software | ||||
| under the terms of such Secondary License(s), so that the recipient of | ||||
| the Larger Work may, at their option, further distribute the Covered | ||||
| Software under the terms of either this License or such Secondary | ||||
| License(s). | ||||
| 
 | ||||
| 3.4. Notices | ||||
| 
 | ||||
| You may not remove or alter the substance of any license notices | ||||
| (including copyright notices, patent notices, disclaimers of warranty, | ||||
| or limitations of liability) contained within the Source Code Form of | ||||
| the Covered Software, except that You may alter any license notices to | ||||
| the extent required to remedy known factual inaccuracies. | ||||
| 
 | ||||
| 3.5. Application of Additional Terms | ||||
| 
 | ||||
| You may choose to offer, and to charge a fee for, warranty, support, | ||||
| indemnity or liability obligations to one or more recipients of Covered | ||||
| Software. However, You may do so only on Your own behalf, and not on | ||||
| behalf of any Contributor. You must make it absolutely clear that any | ||||
| such warranty, support, indemnity, or liability obligation is offered by | ||||
| You alone, and You hereby agree to indemnify every Contributor for any | ||||
| liability incurred by such Contributor as a result of warranty, support, | ||||
| indemnity or liability terms You offer. You may include additional | ||||
| disclaimers of warranty and limitations of liability specific to any | ||||
| jurisdiction. | ||||
| 
 | ||||
| 4. Inability to Comply Due to Statute or Regulation | ||||
| --------------------------------------------------- | ||||
| 
 | ||||
| If it is impossible for You to comply with any of the terms of this | ||||
| License with respect to some or all of the Covered Software due to | ||||
| statute, judicial order, or regulation then You must: (a) comply with | ||||
| the terms of this License to the maximum extent possible; and (b) | ||||
| describe the limitations and the code they affect. Such description must | ||||
| be placed in a text file included with all distributions of the Covered | ||||
| Software under this License. Except to the extent prohibited by statute | ||||
| or regulation, such description must be sufficiently detailed for a | ||||
| recipient of ordinary skill to be able to understand it. | ||||
| 
 | ||||
| 5. Termination | ||||
| -------------- | ||||
| 
 | ||||
| 5.1. The rights granted under this License will terminate automatically | ||||
| if You fail to comply with any of its terms. However, if You become | ||||
| compliant, then the rights granted under this License from a particular | ||||
| Contributor are reinstated (a) provisionally, unless and until such | ||||
| Contributor explicitly and finally terminates Your grants, and (b) on an | ||||
| ongoing basis, if such Contributor fails to notify You of the | ||||
| non-compliance by some reasonable means prior to 60 days after You have | ||||
| come back into compliance. Moreover, Your grants from a particular | ||||
| Contributor are reinstated on an ongoing basis if such Contributor | ||||
| notifies You of the non-compliance by some reasonable means, this is the | ||||
| first time You have received notice of non-compliance with this License | ||||
| from such Contributor, and You become compliant prior to 30 days after | ||||
| Your receipt of the notice. | ||||
| 
 | ||||
| 5.2. If You initiate litigation against any entity by asserting a patent | ||||
| infringement claim (excluding declaratory judgment actions, | ||||
| counter-claims, and cross-claims) alleging that a Contributor Version | ||||
| directly or indirectly infringes any patent, then the rights granted to | ||||
| You by any and all Contributors for the Covered Software under Section | ||||
| 2.1 of this License shall terminate. | ||||
| 
 | ||||
| 5.3. In the event of termination under Sections 5.1 or 5.2 above, all | ||||
| end user license agreements (excluding distributors and resellers) which | ||||
| have been validly granted by You or Your distributors under this License | ||||
| prior to termination shall survive termination. | ||||
| 
 | ||||
| ************************************************************************ | ||||
| *                                                                      * | ||||
| *  6. Disclaimer of Warranty                                           * | ||||
| *  -------------------------                                           * | ||||
| *                                                                      * | ||||
| *  Covered Software is provided under this License on an "as is"       * | ||||
| *  basis, without warranty of any kind, either expressed, implied, or  * | ||||
| *  statutory, including, without limitation, warranties that the       * | ||||
| *  Covered Software is free of defects, merchantable, fit for a        * | ||||
| *  particular purpose or non-infringing. The entire risk as to the     * | ||||
| *  quality and performance of the Covered Software is with You.        * | ||||
| *  Should any Covered Software prove defective in any respect, You     * | ||||
| *  (not any Contributor) assume the cost of any necessary servicing,   * | ||||
| *  repair, or correction. This disclaimer of warranty constitutes an   * | ||||
| *  essential part of this License. No use of any Covered Software is   * | ||||
| *  authorized under this License except under this disclaimer.         * | ||||
| *                                                                      * | ||||
| ************************************************************************ | ||||
| 
 | ||||
| ************************************************************************ | ||||
| *                                                                      * | ||||
| *  7. Limitation of Liability                                          * | ||||
| *  --------------------------                                          * | ||||
| *                                                                      * | ||||
| *  Under no circumstances and under no legal theory, whether tort      * | ||||
| *  (including negligence), contract, or otherwise, shall any           * | ||||
| *  Contributor, or anyone who distributes Covered Software as          * | ||||
| *  permitted above, be liable to You for any direct, indirect,         * | ||||
| *  special, incidental, or consequential damages of any character      * | ||||
| *  including, without limitation, damages for lost profits, loss of    * | ||||
| *  goodwill, work stoppage, computer failure or malfunction, or any    * | ||||
| *  and all other commercial damages or losses, even if such party      * | ||||
| *  shall have been informed of the possibility of such damages. This   * | ||||
| *  limitation of liability shall not apply to liability for death or   * | ||||
| *  personal injury resulting from such party's negligence to the       * | ||||
| *  extent applicable law prohibits such limitation. Some               * | ||||
| *  jurisdictions do not allow the exclusion or limitation of           * | ||||
| *  incidental or consequential damages, so this exclusion and          * | ||||
| *  limitation may not apply to You.                                    * | ||||
| *                                                                      * | ||||
| ************************************************************************ | ||||
| 
 | ||||
| 8. Litigation | ||||
| ------------- | ||||
| 
 | ||||
| Any litigation relating to this License may be brought only in the | ||||
| courts of a jurisdiction where the defendant maintains its principal | ||||
| place of business and such litigation shall be governed by laws of that | ||||
| jurisdiction, without reference to its conflict-of-law provisions. | ||||
| Nothing in this Section shall prevent a party's ability to bring | ||||
| cross-claims or counter-claims. | ||||
| 
 | ||||
| 9. Miscellaneous | ||||
| ---------------- | ||||
| 
 | ||||
| This License represents the complete agreement concerning the subject | ||||
| matter hereof. If any provision of this License is held to be | ||||
| unenforceable, such provision shall be reformed only to the extent | ||||
| necessary to make it enforceable. Any law or regulation which provides | ||||
| that the language of a contract shall be construed against the drafter | ||||
| shall not be used to construe this License against a Contributor. | ||||
| 
 | ||||
| 10. Versions of the License | ||||
| --------------------------- | ||||
| 
 | ||||
| 10.1. New Versions | ||||
| 
 | ||||
| Mozilla Foundation is the license steward. Except as provided in Section | ||||
| 10.3, no one other than the license steward has the right to modify or | ||||
| publish new versions of this License. Each version will be given a | ||||
| distinguishing version number. | ||||
| 
 | ||||
| 10.2. Effect of New Versions | ||||
| 
 | ||||
| You may distribute the Covered Software under the terms of the version | ||||
| of the License under which You originally received the Covered Software, | ||||
| or under the terms of any subsequent version published by the license | ||||
| steward. | ||||
| 
 | ||||
| 10.3. Modified Versions | ||||
| 
 | ||||
| If you create software not governed by this License, and you want to | ||||
| create a new license for such software, you may create and use a | ||||
| modified version of this License if you rename the license and remove | ||||
| any references to the name of the license steward (except to note that | ||||
| such modified license differs from this License). | ||||
| 
 | ||||
| 10.4. Distributing Source Code Form that is Incompatible With Secondary | ||||
| Licenses | ||||
| 
 | ||||
| If You choose to distribute Source Code Form that is Incompatible With | ||||
| Secondary Licenses under the terms of this version of the License, the | ||||
| notice described in Exhibit B of this License must be attached. | ||||
| 
 | ||||
| Exhibit A - Source Code Form License Notice | ||||
| ------------------------------------------- | ||||
| 
 | ||||
|   This Source Code Form is subject to the terms of the Mozilla Public | ||||
|   License, v. 2.0. If a copy of the MPL was not distributed with this | ||||
|   file, You can obtain one at http://mozilla.org/MPL/2.0/. | ||||
| 
 | ||||
| If it is not possible or desirable to put the notice in a particular | ||||
| file, then You may include the notice in a location (such as a LICENSE | ||||
| file in a relevant directory) where a recipient would be likely to look | ||||
| for such a notice. | ||||
| 
 | ||||
| You may add additional accurate notices of copyright ownership. | ||||
| 
 | ||||
| Exhibit B - "Incompatible With Secondary Licenses" Notice | ||||
| --------------------------------------------------------- | ||||
| 
 | ||||
|   This Source Code Form is "Incompatible With Secondary Licenses", as | ||||
|   defined by the Mozilla Public License, v. 2.0. | ||||
|  | ||||
							
								
								
									
										718
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										718
									
								
								README.md
									
									
									
									
									
								
							| @ -1,376 +1,478 @@ | ||||
| # New Documentation & [v2/v3 Migration Guide](https://git.rootprojects.org/root/greenlock.js/src/branch/v3/MIGRATION_GUIDE_V2_V3.md) | ||||
| [](https://gitter.im/Daplie/letsencrypt-express?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) | ||||
| 
 | ||||
| Greenlock v3 just came out of private beta **today** (Nov 1st, 2019). | ||||
| | [letsencrypt (library)](https://github.com/Daplie/node-letsencrypt) | ||||
| | [letsencrypt-cli](https://github.com/Daplie/letsencrypt-cli)  | ||||
| | **letsencrypt-express** | ||||
| | [letsencrypt-koa](https://github.com/Daplie/letsencrypt-koa) | ||||
| | [letsencrypt-hapi](https://github.com/Daplie/letsencrypt-hapi) | ||||
| | | ||||
| 
 | ||||
| The code is complete and we're working on great documentation. | ||||
| # HELP WANTED | ||||
| 
 | ||||
| Many **examples** and **full API** documentation are still coming. | ||||
| There are a number of easy to fix bugs (the most important of which is basically requires tracing some functions, doing some console.log-ing and returning the right type of object). | ||||
| 
 | ||||
| # [Greenlock Express](https://git.rootprojects.org/root/greenlock-express.js) is Let's Encrypt for Node | ||||
| If you've got some free cycles to help, I can guide you through the process, I'm just still too busy to fix them right now and the workarounds work mentioned in the comments work. | ||||
| 
 | ||||
|  | ||||
| Email me coolaj86@gmail.com if you want to help. | ||||
| 
 | ||||
| | Built by [Root](https://therootcompany.com) for [Hub](https://rootprojects.org/hub/) | ||||
| # LetsEncrypt Express | ||||
| 
 | ||||
| Free SSL, Automated HTTPS / HTTP2, served with Node via Express, Koa, hapi, etc. | ||||
| [](https://gitter.im/Daplie/letsencrypt-express?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) | ||||
| 
 | ||||
| ### Let's Encrypt for Node, Express, etc | ||||
| Free SSL and managed or automatic HTTPS for node.js with Express, Koa, Connect, Hapi, and all other middleware systems. | ||||
| 
 | ||||
| Greenlock Express is a **Web Server** with **Fully Automated HTTPS** and renewals. | ||||
| * Automatic Registration via SNI (`httpsOptions.SNICallback`) | ||||
|   * **registrations** require an **approval callback** in *production* | ||||
| * Automatic Renewal (around 80 days) | ||||
|   * **renewals** are *fully automatic* and happen in the *background*, with **no downtime** | ||||
| * Automatic vhost / virtual hosting | ||||
| 
 | ||||
| ```js | ||||
| "use strict"; | ||||
| All you have to do is start the webserver and then visit it at it's domain name. | ||||
| 
 | ||||
| function httpsWorker(glx) { | ||||
| 	// Serves on 80 and 443 | ||||
| 	// Get's SSL certificates magically! | ||||
| ## Install | ||||
| 
 | ||||
| 	glx.serveApp(function(req, res) { | ||||
| 		res.end("Hello, Encrypted World!"); | ||||
| 	}); | ||||
| } | ||||
| 
 | ||||
| var pkg = require("./package.json"); | ||||
| require("greenlock-express") | ||||
| 	.init(function getConfig() { | ||||
| 		// Greenlock Config | ||||
| 
 | ||||
| 		return { | ||||
| 			package: { name: pkg.name, version: pkg.version }, | ||||
| 			maintainerEmail: pkg.author, | ||||
| 			cluster: false | ||||
| 		}; | ||||
| 	}) | ||||
| 	.serve(httpsWorker); | ||||
| ``` | ||||
| npm install --save letsencrypt-express | ||||
| ``` | ||||
| 
 | ||||
| Manage via API or the config file: | ||||
| ## Usage | ||||
| 
 | ||||
| `~/.config/greenlock/manage.json`: (default filesystem config) | ||||
| * standalone | ||||
| * express | ||||
| * http / https | ||||
| * http / http2 / spdy | ||||
| * koa | ||||
| 
 | ||||
| ```json | ||||
| { | ||||
| 	"subscriberEmail": "letsencrypt-test@therootcompany.com", | ||||
| 	"agreeToTerms": true, | ||||
| 	"sites": { | ||||
| 		"example.com": { | ||||
| 			"subject": "example.com", | ||||
| 			"altnames": ["example.com", "www.example.com"] | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| ### Setup (same for all examples)  | ||||
| 
 | ||||
| ```javascript | ||||
| 'use strict'; | ||||
| 
 | ||||
| /* Note: using staging server url, remove .testing() for production | ||||
| Using .testing() will overwrite the debug flag with true */  | ||||
| var LEX = require('letsencrypt-express').testing(); | ||||
| 
 | ||||
| // Change these two lines! | ||||
| var DOMAIN = 'myservice.example.com'; | ||||
| var EMAIL = 'user@example.com'; | ||||
| 
 | ||||
| var lex = LEX.create({ | ||||
|   configDir: require('os').homedir() + '/letsencrypt/etc' | ||||
| , approveRegistration: function (hostname, approve) { // leave `null` to disable automatic registration | ||||
|     if (hostname === DOMAIN) { // Or check a database or list of allowed domains | ||||
|       approve(null, { | ||||
|         domains: [DOMAIN] | ||||
|       , email: EMAIL | ||||
|       , agreeTos: true | ||||
|       }); | ||||
|     } | ||||
|   } | ||||
| }); | ||||
| ``` | ||||
| 
 | ||||
| # Let's Encrypt for... | ||||
| 
 | ||||
| - IoT | ||||
| - Enterprise On-Prem | ||||
| - Local Development | ||||
| - Home Servers | ||||
| - Quitting Heroku | ||||
| 
 | ||||
| # Features | ||||
| 
 | ||||
| - [x] Let's Encrypt v2 (November 2019) | ||||
|   - [x] ACME Protocol (RFC 8555) | ||||
|   - [x] HTTP Validation (HTTP-01) | ||||
|   - [x] DNS Validation (DNS-01) | ||||
|   - [ ] ALPN Validation (TLS-ALPN-01) | ||||
|     - Need ALPN validation? [contact us](mailto:greenlock-support@therootcompany.com) | ||||
| - [x] Automated HTTPS | ||||
|   - [x] Fully Automatic Renewals every 45 days | ||||
|   - [x] Free SSL | ||||
|   - [x] **Wildcard** SSL | ||||
|   - [x] **Localhost** certificates | ||||
|   - [x] HTTPS-enabled Secure **WebSockets** (`wss://`) | ||||
| - [x] Fully customizable | ||||
|   - [x] **Reasonable defaults** | ||||
|   - [x] Domain Management | ||||
|   - [x] Key and Certificate Management | ||||
|   - [x] ACME Challenge Plugins | ||||
| 
 | ||||
| # QuickStart Guide | ||||
| 
 | ||||
| Easy as 1, 2, 3... 4 | ||||
| 
 | ||||
| <details> | ||||
| <summary>1. Create a node project</summary> | ||||
| 
 | ||||
| ## 1. Create a node project | ||||
| 
 | ||||
| Create an empty node project. | ||||
| 
 | ||||
| Be sure to fill out the package name, version, and an author email. | ||||
| WARNING: If you don't do any checks and simply complete `approveRegistration` callback, an attacker will spoof SNI packets with bad hostnames and that will cause you to be rate-limited and or blocked from the ACME server. Alternatively, You can run registration *manually*: | ||||
| 
 | ||||
| ```bash | ||||
| mkdir ~/my-project | ||||
| pushd ~/my-project | ||||
| npm init | ||||
| npm install -g letsencrypt-cli | ||||
| 
 | ||||
| letsencrypt certonly --standalone \ | ||||
|   --config-dir ~/letsencrypt/etc \ | ||||
|   --agree-tos --domains example.com --email user@example.com | ||||
|    | ||||
| # Note: the '--webrootPath' option is also available if you don't want to shut down your webserver to get the cert. | ||||
| ``` | ||||
| 
 | ||||
| </details> | ||||
| ### Standalone | ||||
| 
 | ||||
| <details> | ||||
| <summary>2. Create an http app (i.e. express)</summary> | ||||
| ```javascript | ||||
| lex.onRequest = function (req, res) { | ||||
|   res.end('Hello, World!'); | ||||
| }; | ||||
| 
 | ||||
| ## 2. Create an http app (i.e. express) | ||||
| lex.listen([80], [443, 5001], function () { | ||||
|   console.log("ENCRYPT __ALL__ THE DOMAINS!"); | ||||
| }); | ||||
| 
 | ||||
| This example is shown with Express, but any node app will do. Greenlock | ||||
| works with everything. | ||||
| (or any node-style http app) | ||||
| // NOTE: | ||||
| // `~/letsencrypt/etc` is the default `configDir` | ||||
| // ports 80, 443, and 5001 are the default ports to listen on. | ||||
| ``` | ||||
| 
 | ||||
| `my-express-app.js`: | ||||
| ## Express | ||||
| 
 | ||||
| ```js | ||||
| "use strict"; | ||||
| ```bash | ||||
| npm install --save spdy | ||||
| ``` | ||||
| 
 | ||||
| // A plain, node-style app | ||||
| 
 | ||||
| function myPlainNodeHttpApp(req, res) { | ||||
| 	res.end("Hello, Encrypted World!"); | ||||
| } | ||||
| 
 | ||||
| // Wrap that plain app in express, | ||||
| // because that's what you're used to | ||||
| 
 | ||||
| var express = require("express"); | ||||
| ```javascript | ||||
| // A happy little express app | ||||
| var express = require('express'); | ||||
| var app = express(); | ||||
| app.get("/", myPlainNodeHttpApp); | ||||
| 
 | ||||
| // export the app normally | ||||
| // do not .listen() | ||||
| app.use(function (req, res) { | ||||
|   res.send({ success: true }); | ||||
| }); | ||||
| 
 | ||||
| module.exports = app; | ||||
| lex.onRequest = app; | ||||
| 
 | ||||
| lex.listen([80], [443, 5001], function () { | ||||
|   var protocol = ('requestCert' in this) ? 'https': 'http'; | ||||
|   console.log("Listening at " + protocol + '://localhost:' + this.address().port); | ||||
| }); | ||||
| ``` | ||||
| 
 | ||||
| </details> | ||||
| ### Use with raw http / https modules | ||||
| 
 | ||||
| <details> | ||||
| <summary>3. Serve with Greenlock Express</summary> | ||||
| Let's say you want to redirect all http to https. | ||||
| 
 | ||||
| ## 3. Serve with Greenlock Express | ||||
| ```javascript | ||||
| var http = require('http'); | ||||
| var https = require('spdy'); | ||||
| // NOTE: you could use the old https module if for some reason you don't want to support modern browsers | ||||
| 
 | ||||
| Greenlock Express is designed with these goals in mind: | ||||
| 
 | ||||
| - Simplicity and ease-of-use | ||||
| - Performance and scalability | ||||
| - Configurability and control | ||||
| 
 | ||||
| You can start with **near-zero configuration** and | ||||
| slowly add options for greater performance and customization | ||||
| later, if you need them. | ||||
| 
 | ||||
| `server.js`: | ||||
| 
 | ||||
| ```js | ||||
| require("greenlock-express") | ||||
| 	.init(getConfig) | ||||
| 	.serve(worker); | ||||
| 
 | ||||
| function getConfig() { | ||||
| 	return { | ||||
| 		// uses name and version as part of the ACME client user-agent | ||||
| 		// uses author as the contact for support notices | ||||
| 		package: require("./package.json") | ||||
| 	}; | ||||
| function redirectHttp() { | ||||
|   http.createServer(LEX.createAcmeResponder(lex, function redirectHttps(req, res) { | ||||
|     res.setHeader('Location', 'https://' + req.headers.host + req.url); | ||||
|     res.statusCode = 302; // use 307 if you want to redirect requests with POST, DELETE or PUT action. | ||||
|     res.end('<!-- Hello Developer Person! Please use HTTPS instead -->'); | ||||
|   })).listen(80); | ||||
| } | ||||
| 
 | ||||
| function worker(server) { | ||||
| 	// Works with any Node app (Express, etc) | ||||
| 	var app = require("my-express-app.js"); | ||||
| 	server.serveApp(app); | ||||
| function serveHttps() { | ||||
|   var app = require('express')(); | ||||
|    | ||||
|   app.use('/', function (req, res) { | ||||
|     res.end('Hello!'); | ||||
|   }); | ||||
|    | ||||
|   https.createServer(lex.httpsOptions, LEX.createAcmeResponder(lex, app)).listen(443); | ||||
| } | ||||
| 
 | ||||
| redirectHttp(); | ||||
| serveHttps(); | ||||
| ``` | ||||
| 
 | ||||
| ### Let's Encrypt with Koa | ||||
| 
 | ||||
| ```javascript | ||||
| var http = require('http'); | ||||
| var https = require('spdy');       // Note: some have reported trouble with `http2` and success with `spdy` | ||||
| var koa = require('koa'); | ||||
| var app = koa(); | ||||
| var redirectHttps = koa().use(require('koa-sslify')()).callback(); | ||||
| 
 | ||||
| app.use(function *() { | ||||
|   this.body = 'Hello World'; | ||||
| }); | ||||
| 
 | ||||
| var server = https.createServer(lex.httpsOptions, LEX.createAcmeResponder(lex, app.callback())); | ||||
| var redirectServer = http.createServer(LEX.createAcmeResponder(lex, redirectHttps))); | ||||
| 
 | ||||
| server.listen(443, function () { | ||||
|  console.log('Listening at https://localhost:' + this.address().port); | ||||
| }); | ||||
| 
 | ||||
| redirectServer.listen(80, function () { | ||||
|   console.log('Redirecting insecure traffic from http://localhost:' + this.address().port + ' to https'); | ||||
| }); | ||||
| ``` | ||||
| 
 | ||||
| ### WebSockets with Let's Encrypt | ||||
| 
 | ||||
| Note: you don't need to create websockets for the plain ports. | ||||
| 
 | ||||
| ```javascript | ||||
| var WebSocketServer = require('ws').Server; | ||||
| var https = require('spdy'); | ||||
| var server = https.createServer(lex.httpsOptions, LEX.createAcmeResponder(lex, app)); | ||||
| var wss = new WebSocketServer({ server: server }); | ||||
| 
 | ||||
| wss.on('connection', onConnection); | ||||
| server.listen(443); | ||||
| 
 | ||||
| function onConnection(ws) { | ||||
|   var location = url.parse(ws.upgradeReq.url, true); | ||||
|   // you might use location.query.access_token to authenticate or share sessions | ||||
|   // or ws.upgradeReq.headers.cookie (see http://stackoverflow.com/a/16395220/151312) | ||||
| 
 | ||||
|   ws.on('message', function incoming(message) { | ||||
|     console.log('received: %s', message); | ||||
|   }); | ||||
| 
 | ||||
|   ws.send('something'); | ||||
| } | ||||
| ``` | ||||
| 
 | ||||
| And start your server: | ||||
| ## API | ||||
| 
 | ||||
| ```bash | ||||
| # Allow non-root node to use ports 80 (HTTP) and 443 (HTTPS) | ||||
| sudo setcap 'cap_net_bind_service=+ep' $(which node) | ||||
| ``` | ||||
|                                 // checks options and sets up defaults. returns object with `listen` | ||||
| LEX.create(options)             // (it was really just done this way to appeal to what people are used to seeing) | ||||
| 
 | ||||
|   lex.listen(plain, tls, fn)    // actually creates the servers and causes them to listen | ||||
| 
 | ||||
| 
 | ||||
|                                 // receives an instance of letsencrypt, returns an SNICallback handler for https.createServer() | ||||
| LEX.createSniCallback(opts)     // this will call letsencrypt.renew and letsencrypt.register as appropriate | ||||
|                                 // it will randomly stagger renewals such that they don't all happen at once on boot | ||||
|                                 // or at any other time. registrations will be handled as per `handleRegistration` | ||||
|   opts = { | ||||
|     letsencrypt: <obj>          // letsencrypt instance | ||||
|   , memorizeFor: <1 day>        // how long to wait before checking the disk for updated certificates | ||||
|   , renewWithin: <3 days>       // the first possible moment the certificate staggering should begin | ||||
|   , failedWait:  <5 minutes>    // how long to wait before trying again if the certificate registration failed | ||||
| 
 | ||||
| 
 | ||||
|                                 // registrations are NOT approved automatically by default due to security concerns | ||||
|   , approveRegistration: func   // (someone can spoof servername indication to your server and cause you to be rate-limited) | ||||
|                                 // but you can implement handling of them if you wish | ||||
|                                 // (note that you should probably call the callback immediately with a tlsContext) | ||||
|                                 // | ||||
|                                 // default    function (hostname, cb) { cb(null, null); } | ||||
|                                 // | ||||
|                                 // example    function (hostname, cb) { | ||||
|                                 //              cb(null, { domains: [hostname], agreeTos: true, email: 'user@example.com' }); | ||||
|                                 //            } | ||||
| 
 | ||||
| 
 | ||||
|   , handleRenewFailure: func    // renewals are automatic, but sometimes they may fail. If that happens, you should handle it | ||||
|                                 // (note that renewals happen in the background) | ||||
|                                 // | ||||
|                                 // default    function (err, letsencrypt, hostname, certInfo) {} | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|                                 // uses `opts.webrootPath` to read from the filesystem | ||||
| LEX.getChallenge(opts, hostname, key cb) | ||||
| 
 | ||||
| LEX.createAcmeResponder(opts, fn)  // this will return the necessary request handler for /.well-known/acme-challenges | ||||
|                                    // which then calls `fn` (such as express app) to complete the request | ||||
|                                    // | ||||
|                                    // opts     lex instance created with LEX.create(opts) | ||||
|                                    //         more generally, any object with a compatible `getChallenge` will work: | ||||
|                                    //         `lex.getChallenge(opts, domain, key, function (err, val) {})` | ||||
|                                    // | ||||
|                                    // fn       function (req, res) { | ||||
|                                    //            console.log(req.method, req.url); | ||||
|                                    // | ||||
|                                    //            res.end('Hello!'); | ||||
|                                    //          } | ||||
| ``` | ||||
| 
 | ||||
| ```bash | ||||
| # `npm start` will call `node ./server.js` by default | ||||
| npm start | ||||
| ## Options | ||||
| 
 | ||||
| If any of these values are `undefined` or `null` the will assume use reasonable defaults. | ||||
| 
 | ||||
| Partially defined values will be merged with the defaults. | ||||
| 
 | ||||
| Setting the value to `false` will, in many cases (as documented), disable the defaults. | ||||
| 
 | ||||
| ``` | ||||
| configDir: string               // string     the letsencrypt configuration path (de facto /etc/letsencrypt) | ||||
|                                 // | ||||
|                                 // default    os.homedir() + '/letsencrypt/etc' | ||||
| 
 | ||||
| 
 | ||||
| webrootPath: string             // string     a path to a folder where temporary challenge files will be stored and read | ||||
|                                 // | ||||
|                                 // default    os.tmpdir() + '/acme-challenge' | ||||
| 
 | ||||
| 
 | ||||
| getChallenge: func | false      // false      do not handle getChallenge | ||||
|                                 // | ||||
|                                 // func       Example: | ||||
|                                 // | ||||
|                                 // default    function (defaults, hostname, key, cb) { | ||||
|                                 //              var filename = path.join(defaults.webrootPath.replace(':hostname', hostname), key); | ||||
|                                 //              fs.readFile(filename, 'ascii', function (cb, text) { | ||||
|                                 //                cb(null, text); | ||||
|                                 //              }) | ||||
|                                 //            } | ||||
| 
 | ||||
| 
 | ||||
| httpsOptions: object            // object     will be merged with internal defaults and passed to https.createServer() | ||||
|                                 //            { pfx, key, cert, passphrase, ca, ciphers, rejectUnauthorized, secureProtocol } | ||||
|                                 //            See https://nodejs.org/api/https.html | ||||
|                                 //            Note: if SNICallback is specified, it will be run *before* | ||||
|                                 //            the internal SNICallback that manages automated certificates | ||||
|                                 // | ||||
|                                 // default    uses a localhost cert and key to prevent https.createServer() from throwing an error | ||||
|                                 //            and also uses our SNICallback, which manages certificates | ||||
| 
 | ||||
| 
 | ||||
| sniCallback: func               // func       replace the default sniCallback handler (which manages certificates) with your own | ||||
| 
 | ||||
| 
 | ||||
| letsencrypt: object             // object     configure the letsencrypt object yourself and pass it in directly | ||||
|                                 // | ||||
|                                 // default    we create the letsencrypt object using parameters you specify | ||||
| 
 | ||||
| server: url                     // url        use letsencrypt.productionServerUrl (i.e. https://acme-v01.api.letsencrypt.org/directory) | ||||
|                                 //            or letsencrypt.stagingServerUrl     (i.e. https://acme-staging.api.letsencrypt.org/directory) | ||||
|                                 // | ||||
|                                 // default    production | ||||
| ``` | ||||
| 
 | ||||
| ```txt | ||||
| Greenlock v3.0.0 | ||||
| Greenlock Manager Config File: ~/.config/greenlock/manager.json | ||||
| Greenlock Storage Directory: ~/.config/greenlock/ | ||||
| ## More Examples | ||||
| 
 | ||||
| Listening on 0.0.0.0:80 for ACME challenges and HTTPS redirects | ||||
| Listening on 0.0.0.0:443 for secure traffic | ||||
| ### < 140 Characters | ||||
| 
 | ||||
| Let's Encrypt in 128 characters, with spaces! | ||||
| 
 | ||||
| ``` | ||||
| node -e 'require("letsencrypt-express").testing().create( require("express")().use(function (_, r) { r.end("Hi!") }) ).listen()' | ||||
| ``` | ||||
| 
 | ||||
| </details> | ||||
| ### More realistic | ||||
| 
 | ||||
| <details> | ||||
| <summary>4. Manage SSL Certificates and Domains</summary> | ||||
| ```javascript | ||||
| 'use strict'; | ||||
| 
 | ||||
| ## 4. Manage domains | ||||
| // Note: using staging server url, remove .testing() for production | ||||
| var LEX = require('letsencrypt-express').testing(); | ||||
| var express = require('express'); | ||||
| var app = express(); | ||||
| 
 | ||||
| The management API is built to work with Databases, S3, etc. | ||||
| app.use('/', function (req, res) { | ||||
|   res.send({ success: true }); | ||||
| }); | ||||
| 
 | ||||
| HOWEVER, by default it starts with a simple config file. | ||||
| LEX.create({ | ||||
|   configDir: './letsencrypt.config'                 // ~/letsencrypt, /etc/letsencrypt, whatever you want | ||||
| 
 | ||||
| <!-- | ||||
| This will update the config file (assuming the default fs-based management plugin): | ||||
| --> | ||||
| , onRequest: app                                    // your express app (or plain node http app) | ||||
| 
 | ||||
| `~/.config/greenlock/manager.json`: | ||||
| , letsencrypt: null                                 // you can provide you own instance of letsencrypt | ||||
|                                                     // if you need to configure it (with an agreeToTerms | ||||
|                                                     // callback, for example) | ||||
| 
 | ||||
| ```json | ||||
| { | ||||
| 	"subscriberEmail": "letsencrypt-test@therootcompany.com", | ||||
| 	"agreeToTerms": true, | ||||
| 	"sites": { | ||||
| 		"example.com": { | ||||
| 			"subject": "example.com", | ||||
| 			"altnames": ["example.com", "www.example.com"] | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| , approveRegistration: function (hostname, cb) {    // PRODUCTION MODE needs this function, but only if you want | ||||
|                                                     // automatic registration (usually not necessary) | ||||
|                                                     // renewals for registered domains will still be automatic | ||||
|     cb(null, { | ||||
|       domains: [hostname] | ||||
|     , email: 'user@example.com' | ||||
|     , agreeTos: true              // you | ||||
|     }); | ||||
|   } | ||||
| }).listen([80], [443, 5001], function () { | ||||
|   console.log("ENCRYPT __ALL__ THE DOMAINS!"); | ||||
| }); | ||||
| ``` | ||||
| 
 | ||||
| COMING SOON | ||||
| ### More Options Exposed | ||||
| 
 | ||||
| Management can be done via the **CLI** or the JavaScript [**API**](https://git.rootprojects.org/root/greenlock.js/). | ||||
| Since this is the QuickStart, we'll demo the **CLI**: | ||||
| ```javascript | ||||
| 'use strict'; | ||||
| 
 | ||||
| You need to create a Let's Encrypt _subscriber account_, which can be done globally, or per-site. | ||||
| All individuals, and most businesses, should set this globally: | ||||
| var lex = require('letsencrypt-express'); | ||||
| var express = require('express'); | ||||
| var app = express(); | ||||
| 
 | ||||
| ```bash | ||||
| # COMING SOON | ||||
| # (this command should be here by Nov 5th) | ||||
| # (edit the config by hand for now) | ||||
| # | ||||
| # Set a global subscriber account | ||||
| npx greenlock config --subscriber-email 'mycompany@example.com' --agree-to-terms true | ||||
| app.use('/', function (req, res) { | ||||
|   res.send({ success: true }); | ||||
| }); | ||||
| 
 | ||||
| var results = lex.create({ | ||||
|   configDir: '/etc/letsencrypt' | ||||
| , onRequest: app | ||||
| , server: require('letsencrypt').productionServerUrl | ||||
| }).listen( | ||||
| 
 | ||||
|   // you can give just the port, or expand out to the full options | ||||
|   [80, { port: 8080, address: 'localhost', onListening: function () { console.log('http://localhost'); } }] | ||||
| 
 | ||||
|   // you can give just the port, or expand out to the full options | ||||
| , [443, 5001, { port: 8443, address: 'localhost' }] | ||||
| 
 | ||||
|   // this is pretty much the default onListening handler | ||||
| , function onListening() { | ||||
|     var server = this; | ||||
|     var protocol = ('requestCert' in server) ? 'https': 'http'; | ||||
|     console.log("Listening at " + protocol + '://localhost:' + this.address().port); | ||||
|   } | ||||
| ); | ||||
| 
 | ||||
| // In case you need access to the raw servers (i.e. using websockets) | ||||
| console.log(results.plainServers); | ||||
| console.log(results.tlsServers); | ||||
| ``` | ||||
| 
 | ||||
| <!-- todo print where the key was saved --> | ||||
| 
 | ||||
| A Let's Encrypt SSL certificate has a "Subject" (Primary Domain) and up to 100 "Alternative Names" | ||||
| (of which the first _must_ be the subject). | ||||
| ### All Options Exposed | ||||
| 
 | ||||
| ```bash | ||||
| # COMING SOON | ||||
| # (this command should be here by Nov 5th) | ||||
| # (edit the config by hand for now) | ||||
| # | ||||
| # Add a certificate with specific domains | ||||
| npx greenlock add --subject example.com --altnames example.com,www.example.com | ||||
| Here's absolutely every option and function exposed | ||||
| 
 | ||||
| ```javascript | ||||
| var http = require('http'); | ||||
| var https = require('spdy'); | ||||
| var LEX = require('letsencrypt-express'); | ||||
| var LE = require('letsencrypt'); | ||||
| var lex; | ||||
| 
 | ||||
| lex = LEX.create({ | ||||
|   webrootPath: '/tmp/.well-known/acme-challenge' | ||||
| 
 | ||||
| , lifetime: 90 * 24 * 60 * 60 * 1000    // expect certificates to last 90 days | ||||
| , failedWait: 5 * 60 * 1000             // if registering fails wait 5 minutes before trying again | ||||
| , renewWithin: 3 * 24 * 60 * 60 * 1000  // renew at least 3 days before expiration | ||||
| , memorizeFor: 1 * 24 * 60 * 60 * 1000  // keep certificates in memory for 1 day | ||||
| 
 | ||||
| , approveRegistration: function (hostname, cb) { | ||||
|     cb(null, { | ||||
|       domains: [hostname] | ||||
|     , email: 'user@example.com' | ||||
|     , agreeTos: true | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
| , handleRenewFailure: function (err, hostname, certInfo) { | ||||
|     console.error("ERROR: Failed to renew domain '", hostname, "':"); | ||||
|     if (err) { | ||||
|       console.error(err.stack || err); | ||||
|     } | ||||
|     if (certInfo) { | ||||
|       console.error(certInfo); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
| , letsencrypt: LE.create( | ||||
|     // options | ||||
|     { configDir: './letsencrypt.config' | ||||
|     , manual: true | ||||
| 
 | ||||
|     , server: LE.productionServerUrl | ||||
|     , privkeyPath: LE.privkeyPath | ||||
|     , fullchainPath: LE.fullchainPath | ||||
|     , certPath: LE.certPath | ||||
|     , chainPath: LE.chainPath | ||||
|     , renewalPath: LE.renewalPath | ||||
|     , accountsDir: LE.accountsDir | ||||
| 
 | ||||
|     , debug: false | ||||
|     } | ||||
| 
 | ||||
|     // handlers | ||||
|   , { setChallenge: LEX.setChallenge | ||||
|     , removeChallenge: LEX.removeChallenge | ||||
|     } | ||||
|   ) | ||||
| 
 | ||||
| , debug: false | ||||
| }); | ||||
| 
 | ||||
| http.createServer(LEX.createAcmeResponder(lex, function (req, res) { | ||||
|   res.setHeader('Location', 'https://' + req.headers.host + req.url); | ||||
|   res.end('<!-- Hello Mr Developer! Please use HTTPS instead -->'); | ||||
| })); | ||||
| 
 | ||||
| https.createServer(lex.httpsOptions, LEX.createAcmeResponder(lex, function (req, res) { | ||||
|   res.end('Hello!'); | ||||
| })); | ||||
| ``` | ||||
| 
 | ||||
| <!-- todo print where the cert was saved --> | ||||
| ## Heroku? | ||||
| 
 | ||||
| Note: **Localhost**, **Wildcard**, and Certificates for Private Networks require | ||||
| [**DNS validation**](https://git.rootprojects.org/root/greenlock-exp). | ||||
| 
 | ||||
| - DNS Validation | ||||
|   - [**Wildcards**](https://git.rootprojects.org/root/greenlock-express.js/src/branch/master/examples/wildcards/) (coming soon) | ||||
|   - [**Localhost**](https://git.rootprojects.org/root/greenlock-express.js/src/branch/master/examples/localhost/) (coming soon) | ||||
|   - [**CI/CD**](https://git.rootprojects.org/root/greenlock-express.js/src/branch/master/examples/ci-cd/) (coming soon) | ||||
| 
 | ||||
| </details> | ||||
| 
 | ||||
| # Plenty of Examples | ||||
| 
 | ||||
| **These are in-progress** Check back tomorrow (Nov 2nd, 2019). | ||||
| 
 | ||||
| - [greenlock-express.js/examples/](https://git.rootprojects.org/root/greenlock-express.js/src/branch/master/examples) | ||||
|   - [Express](https://git.rootprojects.org/root/greenlock-express.js/src/branch/master/examples/express/) | ||||
|   - [Node's **http2**](https://git.rootprojects.org/root/greenlock-express.js/src/branch/master/examples/http2/) | ||||
|   - [Node's https](https://git.rootprojects.org/root/greenlock-express.js/src/branch/master/examples/https/) | ||||
|   - [**WebSockets**](https://git.rootprojects.org/root/greenlock-express.js/src/branch/master/examples/websockets/) | ||||
|   - [Socket.IO](https://git.rootprojects.org/root/greenlock-express.js/src/branch/master/examples/socket-io/) | ||||
|   - [Cluster](https://git.rootprojects.org/root/greenlock-express.js/src/branch/master/examples/cluster/) | ||||
|   - [**Wildcards**](https://git.rootprojects.org/root/greenlock-express.js/src/branch/master/examples/wildcards/) (coming soon) | ||||
|   - [**Localhost**](https://git.rootprojects.org/root/greenlock-express.js/src/branch/master/examples/localhost/) (coming soon) | ||||
|   - [**CI/CD**](https://git.rootprojects.org/root/greenlock-express.js/src/branch/master/examples/ci-cd/) (coming soon) | ||||
|   - [HTTP Proxy](https://git.rootprojects.org/root/greenlock-express.js/src/branch/master/examples/http-proxy/) | ||||
| 
 | ||||
| # Easy to Customize | ||||
| 
 | ||||
| <!-- greenlock-manager-test => greenlock-manager-custom --> | ||||
| 
 | ||||
| <!-- | ||||
| - [greenlock.js/examples/](https://git.rootprojects.org/root/greenlock.js/src/branch/master/examples) | ||||
| --> | ||||
| 
 | ||||
| - [Custom Domain Management](https://git.rootprojects.org/root/greenlock-manager-test.js) | ||||
| - [Custom Key & Cert Storage](https://git.rootprojects.org/root/greenlock-store-test.js) | ||||
| - [Custom ACME HTTP-01 Challenges](https://git.rootprojects.org/root/acme-http-01-test.js) | ||||
| - [Custom ACME DNS-01 Challenges](https://git.rootprojects.org/root/acme-dns-01-test.js) | ||||
| 
 | ||||
| # Ready-made Integrations | ||||
| 
 | ||||
| Greenlock Express integrates between Let's Encrypt's ACME Challenges and many popular services. | ||||
| 
 | ||||
| | Type        | Service                                                                             | Plugin                   | | ||||
| | ----------- | ----------------------------------------------------------------------------------- | ------------------------ | | ||||
| | dns-01      | CloudFlare                                                                          | acme-dns-01-cloudflare   | | ||||
| | dns-01      | [Digital Ocean](https://git.rootprojects.org/root/acme-dns-01-digitalocean.js)      | acme-dns-01-digitalocean | | ||||
| | dns-01      | [DNSimple](https://git.rootprojects.org/root/acme-dns-01-dnsimple.js)               | acme-dns-01-dnsimple     | | ||||
| | dns-01      | [DuckDNS](https://git.rootprojects.org/root/acme-dns-01-duckdns.js)                 | acme-dns-01-duckdns      | | ||||
| | http-01     | File System / [Web Root](https://git.rootprojects.org/root/acme-http-01-webroot.js) | acme-http-01-webroot     | | ||||
| | dns-01      | [GoDaddy](https://git.rootprojects.org/root/acme-dns-01-godaddy.js)                 | acme-dns-01-godaddy      | | ||||
| | dns-01      | [Gandi](https://git.rootprojects.org/root/acme-dns-01-gandi.js)                     | acme-dns-01-gandi        | | ||||
| | dns-01      | [NameCheap](https://git.rootprojects.org/root/acme-dns-01-namecheap.js)             | acme-dns-01-namecheap    | | ||||
| | dns-01      | [Name.com](https://git.rootprojects.org/root/acme-dns-01-namedotcom.js)         | acme-dns-01-namedotcom   | | ||||
| | dns-01      | Route53 (AWS)                                                                       | acme-dns-01-route53      | | ||||
| | http-01     | S3 (AWS, Digital Ocean, Scaleway)                                                   | acme-http-01-s3          | | ||||
| | dns-01      | [Vultr](https://git.rootprojects.org/root/acme-dns-01-vultr.js)                     | acme-dns-01-vultr        | | ||||
| | dns-01      | [Build your own](https://git.rootprojects.org/root/acme-dns-01-test.js)             | acme-dns-01-test         | | ||||
| | http-01     | [Build your own](https://git.rootprojects.org/root/acme-http-01-test.js)            | acme-http-01-test        | | ||||
| | tls-alpn-01 | [Contact us](mailto:support@therootcompany.com)                                     | -                        | | ||||
| 
 | ||||
| Search `acme-http-01-` or `acme-dns-01-` on npm to find more. | ||||
| 
 | ||||
| # Full Documentation | ||||
| 
 | ||||
| <!-- | ||||
| - Greenlock CLI | ||||
| - Greenlock JavaScript API | ||||
| --> | ||||
| 
 | ||||
| Most of the documentation is done by use-case examples, as shown up at the top of the README. | ||||
| 
 | ||||
| We're working on more comprehensive documentation for this newly released version. | ||||
| **Please open an issue** with questions in the meantime. | ||||
| 
 | ||||
| # Commercial Support | ||||
| 
 | ||||
| Do you need... | ||||
| 
 | ||||
| - training? | ||||
| - specific features? | ||||
| - different integrations? | ||||
| - bugfixes, on _your_ timeline? | ||||
| - custom code, built by experts? | ||||
| - commercial support and licensing? | ||||
| 
 | ||||
| You're welcome to [contact us](mailto:aj@therootcompany.com) in regards to IoT, On-Prem, | ||||
| Enterprise, and Internal installations, integrations, and deployments. | ||||
| 
 | ||||
| We have both commercial support and commercial licensing available. | ||||
| 
 | ||||
| We also offer consulting for all-things-ACME and Let's Encrypt. | ||||
| 
 | ||||
| # Legal & Rules of the Road | ||||
| 
 | ||||
| Greenlock™ is a [trademark](https://rootprojects.org/legal/#trademark) of AJ ONeal | ||||
| 
 | ||||
| The rule of thumb is "attribute, but don't confuse". For example: | ||||
| 
 | ||||
| > Built with [Greenlock Express](https://git.rootprojects.org/root/greenlock.js) (a [Root](https://rootprojects.org) project). | ||||
| 
 | ||||
| Please [contact us](mailto:aj@therootcompany.com) if you have any questions in regards to our trademark, | ||||
| attribution, and/or visible source policies. We want to build great software and a great community. | ||||
| 
 | ||||
| [Greenlock™](https://git.rootprojects.org/root/greenlock.js) | | ||||
| MPL-2.0 | | ||||
| [Terms of Use](https://therootcompany.com/legal/#terms) | | ||||
| [Privacy Policy](https://therootcompany.com/legal/#privacy) | ||||
| This doesn't work on heroku because heroku uses a proxy with built-in https | ||||
| (which is a smart thing to do) and besides, they want you to pay big bucks | ||||
| for https. (hopefully not for long?...) | ||||
|  | ||||
							
								
								
									
										226
									
								
								bin/lex.js
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										226
									
								
								bin/lex.js
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,226 @@ | ||||
| #!/usr/bin/env node
 | ||||
| 'use strict'; | ||||
| 
 | ||||
| var fs = require('fs'); | ||||
| var path = require('path'); | ||||
| var mkdirp = require('fs'); | ||||
| var cli = require('cli'); | ||||
| var mkdirp = require('mkdirp'); | ||||
| var homedir = require('os').homedir(); | ||||
| var configDir = path.join(homedir, 'letsencrypt'); | ||||
| var desktop = path.join(homedir, 'Desktop'); | ||||
| var vhostDir = path.join(fs.existsSync(desktop) ? desktop : configDir, 'www'); | ||||
| var welcomeHtml = fs.readFileSync(path.join(__dirname, '..', 'lib', 'public', 'welcome.html'), 'utf8'); | ||||
| var express = require('express'); | ||||
| 
 | ||||
| cli.parse({ | ||||
|   'agree-tos': [ false, " Agree to the Let's Encrypt Subscriber Agreement", 'boolean', false ] | ||||
| , email: [ false, " Email used for registration and recovery contact. (default: null)", 'email' ] | ||||
| , domains: [ false, " Domain names to apply. To include the www domain with your main domain your can enter both with a comma. Ex --domains example.com,www.example.com (default: [])", 'string' ] | ||||
| , debug: [ false, " show traces and logs", 'boolean', false ] | ||||
| , server: [ false, " ACME Directory Resource URI.", 'string', 'https://acme-v01.api.letsencrypt.org/directory)' ] | ||||
| }); | ||||
| 
 | ||||
| // ignore certonly and extraneous arguments
 | ||||
| cli.main(function(_, options) { | ||||
|   console.log(''); | ||||
|   var args = {}; | ||||
| 
 | ||||
|   Object.keys(options).forEach(function (key) { | ||||
|     var val = options[key]; | ||||
| 
 | ||||
|     if ('string' === typeof val) { | ||||
|       val = val.replace(/^~/, homedir); | ||||
|     } | ||||
| 
 | ||||
|     key = key.replace(/\-([a-z0-9A-Z])/g, function (c) { return c[1].toUpperCase(); }); | ||||
|     args[key] = val; | ||||
|   }); | ||||
| 
 | ||||
|   if (args.domains) { | ||||
|     args.domains = args.domains.split(','); | ||||
|   } | ||||
| 
 | ||||
|   makeDirectories(); | ||||
| 
 | ||||
|   function makeDirectories() { | ||||
|     mkdirp(configDir, function (err) { | ||||
|       if (err) { | ||||
|         console.error("Could not create config directory '" + configDir + "':", err.code); | ||||
|         console.error(err.stack); | ||||
|         return; | ||||
|       } | ||||
| 
 | ||||
|       mkdirp(vhostDir, function (err) { | ||||
|         if (err) { | ||||
|           console.error("Could not create vhost directory '" + vhostDir + "':", err.code); | ||||
|           console.error(err.stack); | ||||
|           return; | ||||
|         } | ||||
| 
 | ||||
|         startServers(); | ||||
|       }); | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   function configure(le, args, cb) { | ||||
|     var vhost; | ||||
|     var pubDir; | ||||
|     var index; | ||||
| 
 | ||||
|     if (!(args.email && args.agreeTos && args.server && args.domains)) { | ||||
|       cb({ error : { message: "missing one or more of agreeTos,domains,email,server" } }); | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     vhost = args.domains[0]; | ||||
|     pubDir = path.join(vhostDir, vhost); | ||||
|     index = path.join(pubDir, 'index.html'); | ||||
| 
 | ||||
|     makeLandingPage(); | ||||
| 
 | ||||
|     function makeLandingPage() { | ||||
|       mkdirp(pubDir, function (err) { | ||||
|         if (err) { | ||||
|           cb(err); | ||||
|           return; | ||||
|         } | ||||
| 
 | ||||
|         fs.exists(index, function (exists) { | ||||
|           if (exists) { | ||||
|             configureForHttps(); | ||||
|             return; | ||||
|           } | ||||
| 
 | ||||
|           fs.writeFile(path.join(pubDir, 'index.html'), welcomeHtml.replace(/:hostname/g, vhost), 'utf8', function (err) { | ||||
|             if (err) { | ||||
|               cb(err); | ||||
|               return; | ||||
|             } | ||||
| 
 | ||||
|             configureForHttps(); | ||||
|           }); | ||||
|         }); | ||||
|       }); | ||||
|     } | ||||
| 
 | ||||
|     function configureForHttps() { | ||||
|       if (args.debug) { | ||||
|         console.log('[LEX] configureForHttps'); | ||||
|         console.log(args); | ||||
|       } | ||||
|       le.setConfig(args, cb); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   function createConfigurator(le) { | ||||
|     var app = express(); | ||||
| 
 | ||||
|     app.use('/', express.static(path.join(__dirname, '..', 'lib', 'configurator'))); | ||||
| 
 | ||||
|     app.use(require('body-parser').json()); | ||||
| 
 | ||||
|     app.get('/api/com.daplie.lex/sites', function (req, res, next) { | ||||
|       le.getConfigs({ configDir: configDir }, function (err, configs) { | ||||
|         if (err) { | ||||
|           next(err); | ||||
|           return; | ||||
|         } | ||||
|         res.send(configs); | ||||
|       }); | ||||
|     }); | ||||
| 
 | ||||
|     app.post('/api/com.daplie.lex/sites', function (req, res, next) { | ||||
|       var data = req.body; | ||||
| 
 | ||||
|       configure(le, data, function (err, configs) { | ||||
|         if (err) { | ||||
|           console.error("[LEX/bin] configure"); | ||||
|           console.error(err.stack); | ||||
|           next(err); | ||||
|           return; | ||||
|         } | ||||
| 
 | ||||
|         res.send(configs); | ||||
|       }); | ||||
|     }); | ||||
| 
 | ||||
|     return app; | ||||
|   } | ||||
| 
 | ||||
|   function startServers() { | ||||
|     // Note: using staging server url, remove .testing() for production
 | ||||
|     var LE = require('letsencrypt'); | ||||
|     var LEX = require('../'); | ||||
|     var le = LE.create({ | ||||
|       configDir: configDir | ||||
|     , manual: true | ||||
| 
 | ||||
|     , privkeyPath: LE.privkeyPath | ||||
|     , fullchainPath: LE.fullchainPath | ||||
|     , certPath: LE.certPath | ||||
|     , chainPath: LE.chainPath | ||||
|     , renewalPath: LE.renewalPath | ||||
|     , accountsDir: LE.accountsDir | ||||
|     }, { | ||||
|       setChallenge: LEX.setChallenge | ||||
|     , removeChallenge: LEX.removeChallenge | ||||
|     }); | ||||
|     var app = express(); | ||||
|     var vhosts = {}; | ||||
| 
 | ||||
|     vhosts['localhost.daplie.com'] = createConfigurator(le, vhosts); | ||||
| 
 | ||||
|     app.use('/', function (req, res, next) { | ||||
|       var hostname = (req.hostname||req.headers.host||'').replace(/^www\./, ''); | ||||
|       var pubDir = path.join(vhostDir, hostname); | ||||
| 
 | ||||
|       if (vhosts[hostname]) { | ||||
|         vhosts[hostname](req, res, next); | ||||
|         return; | ||||
|       } | ||||
| 
 | ||||
|       fs.exists(pubDir, function (exists) { | ||||
|         if (exists) { | ||||
|           vhosts[hostname] = express().use('/', express.static(pubDir)); | ||||
|           vhosts[hostname](req, res, next); | ||||
|         } else { | ||||
|           vhosts['localhost.daplie.com'](req, res, next); | ||||
|         } | ||||
|       }); | ||||
|     }); | ||||
|     app.use('/', express.static(path.join(__dirname, '..', 'lib', 'public'))); | ||||
| 
 | ||||
|     LEX.create({ | ||||
|       onRequest: app | ||||
|     , configDir: configDir | ||||
|     , letsencrypt: le | ||||
|     , approveRegistration: function (domain, cb) { | ||||
|         le.getConfig({ domains: [domain] }, function (err, config) { | ||||
|           if (!(config && config.checkpoints >= 0)) { | ||||
|             cb(null, null); | ||||
|             return; | ||||
|           } | ||||
| 
 | ||||
|           cb(null, { | ||||
|             email: config.email | ||||
|                 // can't remember which it is, but the pyconf is different that the regular variable
 | ||||
|           , agreeTos: config.tos || config.agree || config.agreeTos | ||||
|           , server: config.server || LE.productionServerUrl | ||||
|           , domains: config.domains || [domain] | ||||
|           }); | ||||
|         }); | ||||
|       } | ||||
|     }).listen([80], [443, 5001]); | ||||
|   } | ||||
| 
 | ||||
|   /* | ||||
|       // should get back account, path to certs, pems, etc?
 | ||||
|       console.log('\nCertificates installed at:'); | ||||
|       console.log(Object.keys(results).filter(function (key) { | ||||
|         return /Path/.test(key); | ||||
|       }).map(function (key) { | ||||
|         return results[key]; | ||||
|       }).join('\n')); | ||||
|   */ | ||||
| }); | ||||
							
								
								
									
										20
									
								
								config.js
									
									
									
									
									
								
							
							
						
						
									
										20
									
								
								config.js
									
									
									
									
									
								
							| @ -1,20 +0,0 @@ | ||||
| "use strict"; | ||||
| 
 | ||||
| var path = require("path"); | ||||
| module.exports = { | ||||
| 	email: "jon.doe@example.com", | ||||
| 	configDir: path.join(__dirname, "acme"), | ||||
| 	srv: "/srv/www/", | ||||
| 	api: "/srv/api/", | ||||
| 	proxy: { | ||||
| 		"example.com": "http://localhost:4080", | ||||
| 		"*.example.com": "http://localhost:4080" | ||||
| 	}, | ||||
| 
 | ||||
| 	// DNS-01 challenges only
 | ||||
| 	challenges: { | ||||
| 		"*.example.com": require("acme-dns-01-YOUR_DNS_HOST").create({ | ||||
| 			token: "xxxx" | ||||
| 		}) | ||||
| 	} | ||||
| }; | ||||
							
								
								
									
										35
									
								
								demo.js
									
									
									
									
									
								
							
							
						
						
									
										35
									
								
								demo.js
									
									
									
									
									
								
							| @ -1,35 +0,0 @@ | ||||
| "use strict"; | ||||
| 
 | ||||
| require("./") | ||||
| 	.init(initialize) | ||||
| 	.serve(worker) | ||||
| 	.master(function() { | ||||
| 		console.log("Hello from master"); | ||||
| 	}); | ||||
| 
 | ||||
| function initialize() { | ||||
| 	var pkg = require("./package.json"); | ||||
| 	var config = { | ||||
| 		package: { | ||||
| 			name: "Greenlock_Express_Demo", | ||||
| 			version: pkg.version, | ||||
| 			author: pkg.author | ||||
| 		}, | ||||
| 		staging: true, | ||||
| 		cluster: true, | ||||
| 
 | ||||
| 		notify: function(ev, params) { | ||||
| 			console.info(ev, params); | ||||
| 		} | ||||
| 	}; | ||||
| 	return config; | ||||
| } | ||||
| 
 | ||||
| function worker(glx) { | ||||
| 	console.info(); | ||||
| 	console.info("Hello from worker #" + glx.id()); | ||||
| 
 | ||||
| 	glx.serveApp(function(req, res) { | ||||
| 		res.end("Hello, Encrypted World!"); | ||||
| 	}); | ||||
| } | ||||
| @ -1,53 +0,0 @@ | ||||
| # sudo systemctl daemon-reload | ||||
| # sudo systemctl restart greenlock-express | ||||
| # sudo journalctl -xefu greenlock-express | ||||
| [Unit] | ||||
| Description=Greenlock Static Server | ||||
| Documentation=https://git.coolaj86.com/coolaj86/greenlock-express.js/ | ||||
| After=network.target | ||||
| Wants=network.target systemd-networkd-wait-online.service | ||||
| 
 | ||||
| [Service] | ||||
| # Restart on crash (bad signal), 'clean' failure (error exit code), everything | ||||
| # Allow up to 3 restarts within 10 seconds | ||||
| # (it's unlikely that a user or properly-running script will do this) | ||||
| Restart=always | ||||
| StartLimitInterval=10 | ||||
| StartLimitBurst=3 | ||||
| 
 | ||||
| # User and group the process will run as | ||||
| # (git is the de facto standard on most systems) | ||||
| User=ubuntu | ||||
| Group=ubuntu | ||||
| 
 | ||||
| WorkingDirectory=/srv/www | ||||
| # custom directory cannot be set and will be the place where gitea exists, not the working directory | ||||
| ExecStart=/opt/node/bin/node /opt/greenlock-express.js/server.js /opt/greenlock-express.js/config.js | ||||
| 
 | ||||
| # Limit the number of file descriptors and processes; see `man systemd.exec` for more limit settings. | ||||
| # greenlock is not expected to use more than this. | ||||
| LimitNOFILE=1048576 | ||||
| LimitNPROC=64 | ||||
| 
 | ||||
| # Use private /tmp and /var/tmp, which are discarded after gitea stops. | ||||
| PrivateTmp=true | ||||
| # Use a minimal /dev | ||||
| PrivateDevices=true | ||||
| # Hide /home, /root, and /run/user. Nobody will steal your SSH-keys. | ||||
| ProtectHome=true | ||||
| # Make /usr, /boot, /etc and possibly some more folders read-only. | ||||
| ProtectSystem=full | ||||
| # ... except /opt/greenlock-express.js/acme because we want a place for the database | ||||
| # and /opt/greenlock-express.js/var because we want a place where logs can go. | ||||
| # This merely retains r/w access rights, it does not add any new. | ||||
| # Must still be writable on the host! | ||||
| ReadWriteDirectories=/srv/www /opt/greenlock-express.js | ||||
| 
 | ||||
| # Note: in v231 and above ReadWritePaths has been renamed to ReadWriteDirectories | ||||
| ; ReadWritePaths=/opt/gitea /var/log/gitea | ||||
| 
 | ||||
| # The following additional security directives only work with systemd v229 or later. | ||||
| # They further retrict privileges that can be gained by gitea. | ||||
| # Note that you may have to add capabilities required by any plugins in use. | ||||
| CapabilityBoundingSet=CAP_NET_BIND_SERVICE | ||||
| AmbientCapabilities=CAP_NET_BIND_SERVICE | ||||
							
								
								
									
										9
									
								
								examples/116.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								examples/116.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,9 @@ | ||||
| 'use strict'; | ||||
| 
 | ||||
| // Don't try this at home kids, it's just for fun
 | ||||
| // 
 | ||||
| 
 | ||||
| // require('letsencrypt-express')
 | ||||
| require('../').testing().create(require('express')().use(function (_, r) { | ||||
|   r.end('Hi!'); | ||||
| })).listen(); | ||||
| @ -1,39 +0,0 @@ | ||||
| "use strict"; | ||||
| 
 | ||||
| var pkg = require("../../package.json"); | ||||
| //require("greenlock-express")
 | ||||
| require("../../") | ||||
| 	.init(function getConfig() { | ||||
| 		// Greenlock Config
 | ||||
| 
 | ||||
| 		return { | ||||
| 			package: { name: "websocket-example", version: pkg.version }, | ||||
| 			maintainerEmail: "jon@example.com", | ||||
| 
 | ||||
| 			// When you're ready to go full cloud scale, you just change this to true:
 | ||||
| 			// Note: in cluster you CANNOT use in-memory state (see below)
 | ||||
| 			cluster: true, | ||||
| 
 | ||||
|       // This will default to the number of workers being equal to
 | ||||
|       // n-1 cpus, with a minimum of 2
 | ||||
|       workers: 4 | ||||
| 		}; | ||||
| 	}) | ||||
| 	.serve(httpsWorker); | ||||
| 
 | ||||
| function httpsWorker(glx) { | ||||
| 	// WRONG
 | ||||
| 	// This won't work like you
 | ||||
| 	// think because EACH worker
 | ||||
| 	// has ITS OWN `count`.
 | ||||
| 	var count = 0; | ||||
| 
 | ||||
| 	var app = function(req, res) { | ||||
| 		res.end("Hello... how many times now? Oh, " + count + " times"); | ||||
| 		count += 1; | ||||
| 	}; | ||||
| 
 | ||||
| 	// Serves on 80 and 443... for each worker
 | ||||
| 	// Get's SSL certificates magically!
 | ||||
| 	glx.serveApp(app); | ||||
| } | ||||
							
								
								
									
										50
									
								
								examples/express.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								examples/express.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,50 @@ | ||||
| 'use strict'; | ||||
| 
 | ||||
| // allow node.js to bind to port 80 and 443 without root:
 | ||||
| //
 | ||||
| //   sudo setcap 'cap_net_bind_service=+ep' `which node`
 | ||||
| 
 | ||||
| /* Note: using staging server url, remove .testing() for production | ||||
| Using .testing() will overwrite the debug flag with true */ | ||||
| var LEX = require('letsencrypt-express').testing(); | ||||
| var http = require('http'); | ||||
| // NOTE: you could use the old https module if for some reason
 | ||||
| // you don't want to support modern browsers
 | ||||
| var https = require('spdy'); | ||||
| var leConfDir = require('os').homedir() + '/letsencrypt/etc'; | ||||
| throw new Error( | ||||
|     "You must edit the example to change the email address (and remove this error)." | ||||
|   + " Also, you'll need to remove .testing() and rm -rf '" + leConfDir + "'" | ||||
|   + " to get actual, trusted production certificates."); | ||||
| var lex = LEX.create({ | ||||
|   configDir: leConfDir | ||||
| , approveRegistration: function (hostname, cb) { // leave `null` to disable automatic registration
 | ||||
|     // Note: this is the place to check your database to get the user associated with this domain
 | ||||
|     cb(null, { | ||||
|       domains: [hostname] | ||||
|     , email: 'CHANGE_ME' // user@example.com
 | ||||
|     , agreeTos: true | ||||
|     }); | ||||
|   } | ||||
| }); | ||||
| 
 | ||||
| function redirectHttp() { | ||||
|   http.createServer(LEX.createAcmeResponder(lex, function redirectHttps(req, res) { | ||||
|     res.setHeader('Location', 'https://' + req.headers.host + req.url); | ||||
|     res.statusCode = 302; | ||||
|     res.end('<!-- Hello Developer Person! Please use HTTPS instead -->'); | ||||
|   })).listen(80); | ||||
| } | ||||
| 
 | ||||
| function serveHttps() { | ||||
|   var app = require('express')(); | ||||
| 
 | ||||
|   app.use('/', function (req, res) { | ||||
|     res.end('Hello!'); | ||||
|   }); | ||||
| 
 | ||||
|   https.createServer(lex.httpsOptions, LEX.createAcmeResponder(lex, app)).listen(443); | ||||
| } | ||||
| 
 | ||||
| redirectHttp(); | ||||
| serveHttps(); | ||||
| @ -1,17 +0,0 @@ | ||||
| "use strict"; | ||||
| 
 | ||||
| var express = require("express"); | ||||
| var app = express(); | ||||
| 
 | ||||
| app.use("/", function(req, res) { | ||||
| 	res.setHeader("Content-Type", "text/html; charset=utf-8"); | ||||
| 	res.end("Hello, World!\n\n💚 🔒.js"); | ||||
| }); | ||||
| 
 | ||||
| // DO NOT DO app.listen() unless we're testing this directly
 | ||||
| if (require.main === module) { | ||||
| 	app.listen(3000); | ||||
| } | ||||
| 
 | ||||
| // Instead do export the app:
 | ||||
| module.exports = app; | ||||
| @ -1,27 +0,0 @@ | ||||
| "use strict"; | ||||
| 
 | ||||
| function httpsWorker(glx) { | ||||
| 	var app = require("./my-express-app.js"); | ||||
| 
 | ||||
| 	app.get("/hello", function(req, res) { | ||||
| 		res.end("Hello, Encrypted World!"); | ||||
| 	}); | ||||
| 
 | ||||
| 	// Serves on 80 and 443
 | ||||
| 	// Get's SSL certificates magically!
 | ||||
| 	glx.serveApp(app); | ||||
| } | ||||
| 
 | ||||
| var pkg = require("../../package.json"); | ||||
| //require("greenlock-express")
 | ||||
| require("../../") | ||||
| 	.init(function getConfig() { | ||||
| 		// Greenlock Config
 | ||||
| 
 | ||||
| 		return { | ||||
| 			package: { name: "http2-example", version: pkg.version }, | ||||
| 			maintainerEmail: "jon@example.com", | ||||
| 			cluster: false | ||||
| 		}; | ||||
| 	}) | ||||
| 	.serve(httpsWorker); | ||||
| @ -1,44 +0,0 @@ | ||||
| "use strict"; | ||||
| 
 | ||||
| function httpsWorker(glx) { | ||||
| 	// we need the raw https server
 | ||||
| 	var server = glx.httpsServer(); | ||||
| 	var proxy = require("http-proxy").createProxyServer({ xfwd: true }); | ||||
| 
 | ||||
| 	// catches error events during proxying
 | ||||
| 	proxy.on("error", function(err, req, res) { | ||||
| 		console.error(err); | ||||
| 		res.statusCode = 500; | ||||
| 		res.end(); | ||||
| 		return; | ||||
| 	}); | ||||
| 
 | ||||
| 	// We'll proxy websockets too
 | ||||
| 	server.on("upgrade", function(req, socket, head) { | ||||
| 		proxy.ws(req, socket, head, { | ||||
| 			ws: true, | ||||
| 			target: "ws://localhost:3000" | ||||
| 		}); | ||||
| 	}); | ||||
| 
 | ||||
| 	// servers a node app that proxies requests to a localhost
 | ||||
| 	glx.serveApp(function(req, res) { | ||||
| 		proxy.web(req, res, { | ||||
| 			target: "http://localhost:3000" | ||||
| 		}); | ||||
| 	}); | ||||
| } | ||||
| 
 | ||||
| var pkg = require("../../package.json"); | ||||
| //require("greenlock-express")
 | ||||
| require("../../") | ||||
| 	.init(function getConfig() { | ||||
| 		// Greenlock Config
 | ||||
| 
 | ||||
| 		return { | ||||
| 			package: { name: "http-proxy-example", version: pkg.version }, | ||||
| 			maintainerEmail: "jon@example.com", | ||||
| 			cluster: false | ||||
| 		}; | ||||
| 	}) | ||||
| 	.serve(httpsWorker); | ||||
| @ -1,42 +0,0 @@ | ||||
| "use strict"; | ||||
| 
 | ||||
| var pkg = require("../../package.json"); | ||||
| 
 | ||||
| // The WRONG way:
 | ||||
| //var http = require('http');
 | ||||
| //var httpServer = https.createSecureServer(redirectToHttps);
 | ||||
| //
 | ||||
| // Why is that wrong?
 | ||||
| // Greenlock needs to change some low-level http and https options.
 | ||||
| // Use glx.httpServer(redirectToHttps) instead.
 | ||||
| 
 | ||||
| function httpsWorker(glx) { | ||||
| 	//
 | ||||
| 	// HTTP can only be used for ACME HTTP-01 Challenges
 | ||||
| 	// (and it is not required for DNS-01 challenges)
 | ||||
| 	//
 | ||||
| 
 | ||||
| 	// Get the raw http server:
 | ||||
| 	var httpServer = glx.httpServer(function(req, res) { | ||||
| 		res.statusCode = 301; | ||||
| 		res.setHeader("Location", "https://" + req.headers.host + req.path); | ||||
| 		res.end("Insecure connections are not allowed. Redirecting..."); | ||||
| 	}); | ||||
| 
 | ||||
| 	httpServer.listen(80, "0.0.0.0", function() { | ||||
| 		console.info("Listening on ", httpServer.address()); | ||||
| 	}); | ||||
| } | ||||
| 
 | ||||
| //require("greenlock-express")
 | ||||
| require("../../") | ||||
| 	.init(function getConfig() { | ||||
| 		// Greenlock Config
 | ||||
| 
 | ||||
| 		return { | ||||
| 			package: { name: "plain-http-example", version: pkg.version }, | ||||
| 			maintainerEmail: "jon@example.com", | ||||
| 			cluster: false | ||||
| 		}; | ||||
| 	}) | ||||
| 	.serve(httpsWorker); | ||||
| @ -1,48 +0,0 @@ | ||||
| "use strict"; | ||||
| 
 | ||||
| var pkg = require("../../package.json"); | ||||
| 
 | ||||
| // The WRONG way:
 | ||||
| //var http2 = require('http2');
 | ||||
| //var http2Server = https.createSecureServer(tlsOptions, app);
 | ||||
| //
 | ||||
| // Why is that wrong?
 | ||||
| // Greenlock needs to change some low-level http and https options.
 | ||||
| // Use glx.httpsServer(tlsOptions, app) instead.
 | ||||
| 
 | ||||
| function httpsWorker(glx) { | ||||
| 	//
 | ||||
| 	// HTTP2 is the default httpsServer for node v12+
 | ||||
| 	// (HTTPS/1.1 is used for node <= v11)
 | ||||
| 	//
 | ||||
| 
 | ||||
| 	// Get the raw http2 server:
 | ||||
| 	var http2Server = glx.httpsServer(function(req, res) { | ||||
| 		res.end("Hello, Encrypted World!"); | ||||
| 	}); | ||||
| 
 | ||||
| 	http2Server.listen(443, "0.0.0.0", function() { | ||||
| 		console.info("Listening on ", http2Server.address()); | ||||
| 	}); | ||||
| 
 | ||||
| 	// Note:
 | ||||
| 	// You must ALSO listen on port 80 for ACME HTTP-01 Challenges
 | ||||
| 	// (the ACME and http->https middleware are loaded by glx.httpServer)
 | ||||
| 	var httpServer = glx.httpServer(); | ||||
| 	httpServer.listen(80, "0.0.0.0", function() { | ||||
| 		console.info("Listening on ", httpServer.address()); | ||||
| 	}); | ||||
| } | ||||
| 
 | ||||
| //require("greenlock-express")
 | ||||
| require("../../") | ||||
| 	.init(function getConfig() { | ||||
| 		// Greenlock Config
 | ||||
| 
 | ||||
| 		return { | ||||
| 			package: { name: "http2-example", version: pkg.version }, | ||||
| 			maintainerEmail: "jon@example.com", | ||||
| 			cluster: false | ||||
| 		}; | ||||
| 	}) | ||||
| 	.serve(httpsWorker); | ||||
| @ -1,49 +0,0 @@ | ||||
| "use strict"; | ||||
| 
 | ||||
| var pkg = require("../../package.json"); | ||||
| 
 | ||||
| // The WRONG way:
 | ||||
| //var https = require('https');
 | ||||
| //var httpsServer = https.createServer(tlsOptions, app);
 | ||||
| //
 | ||||
| // Why is that wrong?
 | ||||
| // Greenlock needs to change some low-level http and https options.
 | ||||
| // Use glx.httpsServer(tlsOptions, app) instead.
 | ||||
| 
 | ||||
| function httpsWorker(glx) { | ||||
| 	//
 | ||||
| 	// HTTPS/1.1 is only used for node v11 or lower
 | ||||
| 	// (HTTP2 is used for node v12+)
 | ||||
| 	//
 | ||||
| 	// Why not just require('https')?
 | ||||
| 
 | ||||
| 	// Get the raw https server:
 | ||||
| 	var httpsServer = glx.httpsServer(null, function(req, res) { | ||||
| 		res.end("Hello, Encrypted World!"); | ||||
| 	}); | ||||
| 
 | ||||
| 	httpsServer.listen(443, "0.0.0.0", function() { | ||||
| 		console.info("Listening on ", httpsServer.address()); | ||||
| 	}); | ||||
| 
 | ||||
| 	// Note:
 | ||||
| 	// You must ALSO listen on port 80 for ACME HTTP-01 Challenges
 | ||||
| 	// (the ACME and http->https middleware are loaded by glx.httpServer)
 | ||||
| 	var httpServer = glx.httpServer(); | ||||
| 	httpServer.listen(80, "0.0.0.0", function() { | ||||
| 		console.info("Listening on ", httpServer.address()); | ||||
| 	}); | ||||
| } | ||||
| 
 | ||||
| //require("greenlock-express")
 | ||||
| require("../../") | ||||
| 	.init(function getConfig() { | ||||
| 		// Greenlock Config
 | ||||
| 
 | ||||
| 		return { | ||||
| 			package: { name: "https1-example", version: pkg.version }, | ||||
| 			maintainerEmail: "jon@example.com", | ||||
| 			cluster: false | ||||
| 		}; | ||||
| 	}) | ||||
| 	.serve(httpsWorker); | ||||
| @ -1,22 +0,0 @@ | ||||
| # Quick Start for Let's Encrypt with Node.js | ||||
| 
 | ||||
| ```js | ||||
| npm install --save greenlock-express | ||||
| ``` | ||||
| 
 | ||||
| Manage via API or the config file: | ||||
| 
 | ||||
| `~/.config/greenlock/manage.json`: (default filesystem config) | ||||
| 
 | ||||
| ```json | ||||
| { | ||||
| 	"subscriberEmail": "letsencrypt-test@therootcompany.com", | ||||
| 	"agreeToTerms": true, | ||||
| 	"sites": { | ||||
| 		"example.com": { | ||||
| 			"subject": "example.com", | ||||
| 			"altnames": ["example.com", "www.example.com"] | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| ``` | ||||
| @ -1,32 +0,0 @@ | ||||
| "use strict"; | ||||
| 
 | ||||
| function httpsWorker(glx) { | ||||
| 	// This can be a node http app (shown),
 | ||||
| 	// an Express app, or Hapi, Koa, Rill, etc
 | ||||
| 	var app = function(req, res) { | ||||
| 		res.end("Hello, Encrypted World!"); | ||||
| 	}; | ||||
| 
 | ||||
| 	// Serves on 80 and 443
 | ||||
| 	// Get's SSL certificates magically!
 | ||||
| 	glx.serveApp(app); | ||||
| } | ||||
| 
 | ||||
| var pkg = require("../../package.json"); | ||||
| //require("greenlock-express")
 | ||||
| require("../../") | ||||
| 	.init(function getConfig() { | ||||
| 		// Greenlock Config
 | ||||
| 
 | ||||
| 		return { | ||||
| 			// Package name+version is used for ACME client user agent
 | ||||
| 			package: { name: "websocket-example", version: pkg.version }, | ||||
| 
 | ||||
| 			// Maintainer email is the contact for critical bug and security notices
 | ||||
| 			maintainerEmail: "jon@example.com", | ||||
| 
 | ||||
| 			// Change to true when you're ready to make your app cloud-scale
 | ||||
| 			cluster: false | ||||
| 		}; | ||||
| 	}) | ||||
| 	.serve(httpsWorker); | ||||
							
								
								
									
										14
									
								
								examples/serve.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								examples/serve.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,14 @@ | ||||
| 'use strict'; | ||||
| 
 | ||||
| // Note: using staging server url, remove .testing() for production
 | ||||
| var lex = require('letsencrypt-express').testing(); | ||||
| var express = require('express'); | ||||
| var app = express(); | ||||
| 
 | ||||
| app.use('/', function (req, res) { | ||||
|   res.send({ success: true }); | ||||
| }); | ||||
| 
 | ||||
| lex.create('./letsencrypt.config', app).listen([80], [443, 5001], function () { | ||||
|   console.log("ENCRYPT __ALL__ THE DOMAINS!"); | ||||
| }); | ||||
| @ -1,49 +0,0 @@ | ||||
| // First and foremost:
 | ||||
| // I'm not a fan of `socket.io` because it's huge and complex.
 | ||||
| // I much prefer `ws` because it's very simple and easy.
 | ||||
| // That said, it's popular.......
 | ||||
| "use strict"; | ||||
| 
 | ||||
| // Note: You DO NOT NEED socket.io
 | ||||
| //       You can just use WebSockets
 | ||||
| //       (see the websocket example)
 | ||||
| 
 | ||||
| function httpsWorker(glx) { | ||||
| 	var socketio = require("socket.io"); | ||||
| 	var io; | ||||
| 
 | ||||
| 	// we need the raw https server
 | ||||
| 	var server = glx.httpsServer(); | ||||
| 
 | ||||
| 	io = socketio(server); | ||||
| 
 | ||||
| 	// Then you do your socket.io stuff
 | ||||
| 	io.on("connection", function(socket) { | ||||
| 		console.log("a user connected"); | ||||
| 		socket.emit("Welcome"); | ||||
| 
 | ||||
| 		socket.on("chat message", function(msg) { | ||||
| 			socket.broadcast.emit("chat message", msg); | ||||
| 		}); | ||||
| 	}); | ||||
| 
 | ||||
| 	// servers a node app that proxies requests to a localhost
 | ||||
| 	glx.serveApp(function(req, res) { | ||||
| 		res.setHeader("Content-Type", "text/html; charset=utf-8"); | ||||
| 		res.end("Hello, World!\n\n💚 🔒.js"); | ||||
| 	}); | ||||
| } | ||||
| 
 | ||||
| var pkg = require("../../package.json"); | ||||
| //require("greenlock-express")
 | ||||
| require("../../") | ||||
| 	.init(function getConfig() { | ||||
| 		// Greenlock Config
 | ||||
| 
 | ||||
| 		return { | ||||
| 			package: { name: "socket-io-example", version: pkg.version }, | ||||
| 			maintainerEmail: "jon@example.com", | ||||
| 			cluster: false | ||||
| 		}; | ||||
| 	}) | ||||
| 	.serve(httpsWorker); | ||||
| @ -1,3 +0,0 @@ | ||||
| // SPDY is dead. It was replaced by HTTP2, which is a native node module
 | ||||
| //
 | ||||
| // Greenlock uses HTTP2 as the default https server in node v12+
 | ||||
							
								
								
									
										1
									
								
								examples/tweet.js
									
									
									
									
									
										Symbolic link
									
								
							
							
						
						
									
										1
									
								
								examples/tweet.js
									
									
									
									
									
										Symbolic link
									
								
							| @ -0,0 +1 @@ | ||||
| 116.js | ||||
| @ -1,42 +0,0 @@ | ||||
| "use strict"; | ||||
| 
 | ||||
| function httpsWorker(glx) { | ||||
| 	// we need the raw https server
 | ||||
| 	var server = glx.httpsServer(); | ||||
| 	var WebSocket = require("ws"); | ||||
| 	var ws = new WebSocket.Server({ server: server }); | ||||
| 	ws.on("connection", function(ws, req) { | ||||
| 		// inspect req.headers.authorization (or cookies) for session info
 | ||||
| 		ws.send( | ||||
| 			"[Secure Echo Server] Hello!\nAuth: '" + | ||||
| 				(req.headers.authorization || "none") + | ||||
| 				"'\n" + | ||||
| 				"Cookie: '" + | ||||
| 				(req.headers.cookie || "none") + | ||||
| 				"'\n" | ||||
| 		); | ||||
| 		ws.on("message", function(data) { | ||||
| 			ws.send(data); | ||||
| 		}); | ||||
| 	}); | ||||
| 
 | ||||
| 	// servers a node app that proxies requests to a localhost
 | ||||
| 	glx.serveApp(function(req, res) { | ||||
| 		res.setHeader("Content-Type", "text/html; charset=utf-8"); | ||||
| 		res.end("Hello, World!\n\n💚 🔒.js"); | ||||
| 	}); | ||||
| } | ||||
| 
 | ||||
| var pkg = require("../../package.json"); | ||||
| //require("greenlock-express")
 | ||||
| require("../../") | ||||
| 	.init(function getConfig() { | ||||
| 		// Greenlock Config
 | ||||
| 
 | ||||
| 		return { | ||||
| 			package: { name: "websocket-example", version: pkg.version }, | ||||
| 			maintainerEmail: "jon@example.com", | ||||
| 			cluster: false | ||||
| 		}; | ||||
| 	}) | ||||
| 	.serve(httpsWorker); | ||||
| @ -1,44 +0,0 @@ | ||||
| "use strict"; | ||||
| 
 | ||||
| require("./lib/compat"); | ||||
| var cluster = require("cluster"); | ||||
| 
 | ||||
| // Greenlock Express
 | ||||
| var GLE = module.exports; | ||||
| 
 | ||||
| // Node's cluster is awesome, because it encourages writing scalable services.
 | ||||
| //
 | ||||
| // The point of this provide an API that is consistent between single-process
 | ||||
| // and multi-process services so that beginners can more easily take advantage
 | ||||
| // of what cluster has to offer.
 | ||||
| //
 | ||||
| // This API provides just enough abstraction to make it easy, but leaves just
 | ||||
| // enough hoopla so that there's not a large gap in understanding what happens
 | ||||
| // under the hood. That's the hope, anyway.
 | ||||
| 
 | ||||
| GLE.init = function(fn) { | ||||
| 	if (cluster.isWorker) { | ||||
| 		// ignore the init function and launch the worker
 | ||||
| 		return require("./worker.js").create(); | ||||
| 	} | ||||
| 
 | ||||
| 	var opts = fn(); | ||||
| 	if (!opts || "object" !== typeof opts) { | ||||
| 		throw new Error( | ||||
| 			"the `Greenlock.init(fn)` function should return an object `{ maintainerEmail, packageAgent, notify }`" | ||||
| 		); | ||||
| 	} | ||||
| 
 | ||||
| 	// just for ironic humor
 | ||||
| 	["cloudnative", "cloudscale", "webscale", "distributed", "blockchain"].forEach(function(k) { | ||||
| 		if (opts[k]) { | ||||
| 			opts.cluster = true; | ||||
| 		} | ||||
| 	}); | ||||
| 
 | ||||
| 	if (opts.cluster) { | ||||
| 		return require("./master.js").create(opts); | ||||
| 	} | ||||
| 
 | ||||
| 	return require("./single.js").create(opts); | ||||
| }; | ||||
							
								
								
									
										119
									
								
								greenlock.js
									
									
									
									
									
								
							
							
						
						
									
										119
									
								
								greenlock.js
									
									
									
									
									
								
							| @ -1,119 +0,0 @@ | ||||
| "use strict"; | ||||
| 
 | ||||
| module.exports.create = function(opts) { | ||||
| 	opts = parsePackage(opts); | ||||
| 	opts.packageAgent = addGreenlockAgent(opts); | ||||
| 
 | ||||
| 	var Greenlock = require("@root/greenlock"); | ||||
| 	var greenlock = Greenlock.create(opts); | ||||
| 
 | ||||
| 	// TODO move to greenlock proper
 | ||||
| 	greenlock.getAcmeHttp01ChallengeResponse = function(opts) { | ||||
| 		// TODO some sort of caching to prevent database hits?
 | ||||
| 		return greenlock | ||||
| 			._config({ servername: opts.servername }) | ||||
| 			.then(function(site) { | ||||
| 				if (!site) { | ||||
| 					return null; | ||||
| 				} | ||||
| 
 | ||||
| 				// Hmm... this _should_ be impossible
 | ||||
| 				if (!site.challenges || !site.challenges["http-01"]) { | ||||
| 					return null; | ||||
| 				} | ||||
| 
 | ||||
| 				return Greenlock._loadChallenge(site.challenges, "http-01"); | ||||
| 			}) | ||||
| 			.then(function(plugin) { | ||||
| 				return plugin | ||||
| 					.get({ | ||||
| 						challenge: { | ||||
| 							type: opts.type, | ||||
| 							//hostname: opts.servername,
 | ||||
| 							altname: opts.servername, | ||||
| 							identifier: { value: opts.servername }, | ||||
| 							token: opts.token | ||||
| 						} | ||||
| 					}) | ||||
| 					.then(function(result) { | ||||
| 						var keyAuth; | ||||
| 						if (result) { | ||||
| 							// backwards compat that shouldn't be dropped
 | ||||
| 							// because new v3 modules had to do this to be
 | ||||
| 							// backwards compatible with Greenlock v2.7 at
 | ||||
| 							// the time.
 | ||||
| 							if (result.challenge) { | ||||
| 								result = challenge; | ||||
| 							} | ||||
| 							keyAuth = result.keyAuthorization; | ||||
| 						} | ||||
| 						return { | ||||
| 							keyAuthorization: keyAuth | ||||
| 						}; | ||||
| 					}); | ||||
| 			}); | ||||
| 	}; | ||||
| 
 | ||||
| 	return greenlock; | ||||
| }; | ||||
| 
 | ||||
| function addGreenlockAgent(opts) { | ||||
| 	// Add greenlock as part of Agent, unless this is greenlock
 | ||||
| 	var packageAgent = opts.packageAgent || ""; | ||||
| 	if (!/greenlock(-express|-pro)?/i.test(packageAgent)) { | ||||
| 		var pkg = require("./package.json"); | ||||
| 		packageAgent += " Greenlock_Express/" + pkg.version; | ||||
| 	} | ||||
| 
 | ||||
| 	return packageAgent.trim(); | ||||
| } | ||||
| 
 | ||||
| // ex: "John Doe <john@example.com> (https://john.doe)"
 | ||||
| // ex: "John Doe <john@example.com>"
 | ||||
| // ex: "<john@example.com>"
 | ||||
| // ex: "john@example.com"
 | ||||
| var looseEmailRe = /(^|[\s<])([^'" <>:;`]+@[^'" <>:;`]+\.[^'" <>:;`]+)/; | ||||
| function parsePackage(opts) { | ||||
| 	// 'package' is sometimes a reserved word
 | ||||
| 	var pkg = opts.package || opts.pkg; | ||||
| 	if (!pkg) { | ||||
| 		opts.maintainerEmail = parseMaintainer(opts.maintainerEmail); | ||||
| 		return opts; | ||||
| 	} | ||||
| 
 | ||||
| 	if (!opts.packageAgent) { | ||||
| 		var err = "missing `package.THING`, which is used for the ACME client user agent string"; | ||||
| 		if (!pkg.name) { | ||||
| 			throw new Error(err.replace("THING", "name")); | ||||
| 		} | ||||
| 		if (!pkg.version) { | ||||
| 			throw new Error(err.replace("THING", "version")); | ||||
| 		} | ||||
| 		opts.packageAgent = pkg.name + "/" + pkg.version; | ||||
| 	} | ||||
| 
 | ||||
| 	if (!opts.maintainerEmail) { | ||||
| 		try { | ||||
| 			opts.maintainerEmail = pkg.author.email || pkg.author.match(looseEmailRe)[2]; | ||||
| 		} catch (e) {} | ||||
| 	} | ||||
| 	if (!opts.maintainerEmail) { | ||||
| 		throw new Error("missing or malformed `package.author`, which is used as the contact for support notices"); | ||||
| 	} | ||||
| 	opts.package = undefined; | ||||
| 	opts.maintainerEmail = parseMaintainer(opts.maintainerEmail); | ||||
| 
 | ||||
| 	return opts; | ||||
| } | ||||
| 
 | ||||
| function parseMaintainer(maintainerEmail) { | ||||
| 	try { | ||||
| 		maintainerEmail = maintainerEmail.match(looseEmailRe)[2]; | ||||
| 	} catch (e) { | ||||
| 		maintainerEmail = null; | ||||
| 	} | ||||
| 	if (!maintainerEmail) { | ||||
| 		throw new Error("missing or malformed `maintainerEmail`, which is used as the contact for support notices"); | ||||
| 	} | ||||
| 	return maintainerEmail; | ||||
| } | ||||
| @ -1,106 +0,0 @@ | ||||
| "use strict"; | ||||
| 
 | ||||
| var HttpMiddleware = module.exports; | ||||
| var servernameRe = /^[a-z0-9\.\-]+$/i; | ||||
| var challengePrefix = "/.well-known/acme-challenge/"; | ||||
| 
 | ||||
| HttpMiddleware.create = function(gl, defaultApp) { | ||||
| 	if (defaultApp && "function" !== typeof defaultApp) { | ||||
| 		throw new Error("use greenlock.httpMiddleware() or greenlock.httpMiddleware(function (req, res) {})"); | ||||
| 	} | ||||
| 
 | ||||
| 	return function(req, res, next) { | ||||
| 		var hostname = HttpMiddleware.sanitizeHostname(req); | ||||
| 
 | ||||
| 		req.on("error", function(err) { | ||||
| 			explainError(gl, err, "http_01_middleware_socket", hostname); | ||||
| 		}); | ||||
| 
 | ||||
| 		if (skipIfNeedBe(req, res, next, defaultApp, hostname)) { | ||||
| 			return; | ||||
| 		} | ||||
| 
 | ||||
| 		var token = req.url.slice(challengePrefix.length); | ||||
| 
 | ||||
| 		gl.getAcmeHttp01ChallengeResponse({ type: "http-01", servername: hostname, token: token }) | ||||
| 			.catch(function(err) { | ||||
| 				respondToError(gl, res, err, "http_01_middleware_challenge_response", hostname); | ||||
| 				return { __done: true }; | ||||
| 			}) | ||||
| 			.then(function(result) { | ||||
| 				if (result && result.__done) { | ||||
| 					return; | ||||
| 				} | ||||
| 				return respondWithGrace(res, result, hostname, token); | ||||
| 			}); | ||||
| 	}; | ||||
| }; | ||||
| 
 | ||||
| function skipIfNeedBe(req, res, next, defaultApp, hostname) { | ||||
| 	if (!hostname || 0 !== req.url.indexOf(challengePrefix)) { | ||||
| 		if ("function" === typeof defaultApp) { | ||||
| 			defaultApp(req, res, next); | ||||
| 		} else if ("function" === typeof next) { | ||||
| 			next(); | ||||
| 		} else { | ||||
| 			res.statusCode = 500; | ||||
| 			res.end("[500] Developer Error: app.use('/', greenlock.httpMiddleware()) or greenlock.httpMiddleware(app)"); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| function respondWithGrace(res, result, hostname, token) { | ||||
| 	var keyAuth = result && result.keyAuthorization; | ||||
| 	if (keyAuth && "string" === typeof keyAuth) { | ||||
| 		res.setHeader("Content-Type", "text/plain; charset=utf-8"); | ||||
| 		res.end(keyAuth); | ||||
| 		return; | ||||
| 	} | ||||
| 
 | ||||
| 	res.statusCode = 404; | ||||
| 	res.setHeader("Content-Type", "application/json; charset=utf-8"); | ||||
| 	res.end(JSON.stringify({ error: { message: "domain '" + hostname + "' has no token '" + token + "'." } })); | ||||
| } | ||||
| 
 | ||||
| function explainError(gl, err, ctx, hostname) { | ||||
| 	if (!err.servername) { | ||||
| 		err.servername = hostname; | ||||
| 	} | ||||
| 	if (!err.context) { | ||||
| 		err.context = ctx; | ||||
| 	} | ||||
| 	(gl.notify || gl._notify)("error", err); | ||||
| 	return err; | ||||
| } | ||||
| 
 | ||||
| function respondToError(gl, res, err, ctx, hostname) { | ||||
| 	err = explainError(gl, err, ctx, hostname); | ||||
| 	res.statusCode = 500; | ||||
| 	res.end("Internal Server Error: See logs for details."); | ||||
| } | ||||
| 
 | ||||
| HttpMiddleware.getHostname = function(req) { | ||||
| 	return req.hostname || req.headers["x-forwarded-host"] || (req.headers.host || ""); | ||||
| }; | ||||
| HttpMiddleware.sanitizeHostname = function(req) { | ||||
| 	// we can trust XFH because spoofing causes no ham in this limited use-case scenario
 | ||||
| 	// (and only telebit would be legitimately setting XFH)
 | ||||
| 	var servername = HttpMiddleware.getHostname(req) | ||||
| 		.toLowerCase() | ||||
| 		.replace(/:.*/, ""); | ||||
| 	try { | ||||
| 		req.hostname = servername; | ||||
| 	} catch (e) { | ||||
| 		// read-only express property
 | ||||
| 	} | ||||
| 	if (req.headers["x-forwarded-host"]) { | ||||
| 		req.headers["x-forwarded-host"] = servername; | ||||
| 	} | ||||
| 	try { | ||||
| 		req.headers.host = servername; | ||||
| 	} catch (e) { | ||||
| 		// TODO is this a possible error?
 | ||||
| 	} | ||||
| 
 | ||||
| 	return (servernameRe.test(servername) && -1 === servername.indexOf("..") && servername) || ""; | ||||
| }; | ||||
| @ -1,139 +0,0 @@ | ||||
| "use strict"; | ||||
| 
 | ||||
| var SanitizeHost = module.exports; | ||||
| var HttpMiddleware = require("./http-middleware.js"); | ||||
| 
 | ||||
| SanitizeHost.create = function(gl, app) { | ||||
| 	return function(req, res, next) { | ||||
| 		function realNext() { | ||||
| 			if ("function" === typeof app) { | ||||
| 				app(req, res); | ||||
| 			} else if ("function" === typeof next) { | ||||
| 				next(); | ||||
| 			} else { | ||||
| 				res.statusCode = 500; | ||||
| 				res.end("Error: no middleware assigned"); | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		var hostname = HttpMiddleware.getHostname(req); | ||||
| 		// Replace the hostname, and get the safe version
 | ||||
| 		var safehost = HttpMiddleware.sanitizeHostname(req); | ||||
| 
 | ||||
| 		// if no hostname, move along
 | ||||
| 		if (!hostname) { | ||||
| 			realNext(); | ||||
| 			return; | ||||
| 		} | ||||
| 
 | ||||
| 		// if there were unallowed characters, complain
 | ||||
| 		if (safehost.length !== hostname.length) { | ||||
| 			res.statusCode = 400; | ||||
| 			res.end("Malformed HTTP Header: 'Host: " + hostname + "'"); | ||||
| 			return; | ||||
| 		} | ||||
| 
 | ||||
| 		// Note: This sanitize function is also called on plain sockets, which don't need Domain Fronting checks
 | ||||
| 		if (req.socket.encrypted) { | ||||
| 			if (req.socket && "string" === typeof req.socket.servername) { | ||||
| 				// Workaround for https://github.com/nodejs/node/issues/22389
 | ||||
| 				if (!SanitizeHost._checkServername(safehost, req.socket)) { | ||||
| 					res.statusCode = 400; | ||||
| 					res.setHeader("Content-Type", "text/html; charset=utf-8"); | ||||
| 					res.end( | ||||
| 						"<h1>Domain Fronting Error</h1>" + | ||||
| 							"<p>This connection was secured using TLS/SSL for '" + | ||||
| 							(req.socket.servername || "").toLowerCase() + | ||||
| 							"'</p>" + | ||||
| 							"<p>The HTTP request specified 'Host: " + | ||||
| 							safehost + | ||||
| 							"', which is (obviously) different.</p>" + | ||||
| 							"<p>Because this looks like a domain fronting attack, the connection has been terminated.</p>" | ||||
| 					); | ||||
| 					return; | ||||
| 				} | ||||
| 			} | ||||
| 			/* | ||||
|       else if (safehost && !gl._skip_fronting_check) { | ||||
| 
 | ||||
| 				// We used to print a log message here, but it turns out that it's
 | ||||
| 				// really common for IoT devices to not use SNI (as well as many bots
 | ||||
| 				// and such).
 | ||||
| 				// It was common for the log message to pop up as the first request
 | ||||
| 				// to the server, and that was confusing. So instead now we do nothing.
 | ||||
| 
 | ||||
| 				//console.warn("no string for req.socket.servername," + " skipping fronting check for '" + safehost + "'");
 | ||||
| 				//gl._skip_fronting_check = true;
 | ||||
| 			} | ||||
|       */ | ||||
| 		} | ||||
| 
 | ||||
| 		// carry on
 | ||||
| 		realNext(); | ||||
| 	}; | ||||
| }; | ||||
| 
 | ||||
| var warnDomainFronting = true; | ||||
| var warnUnexpectedError = true; | ||||
| SanitizeHost._checkServername = function(safeHost, tlsSocket) { | ||||
| 	var servername = (tlsSocket.servername || "").toLowerCase(); | ||||
| 
 | ||||
| 	// acceptable: older IoT devices may lack SNI support
 | ||||
| 	if (!servername) { | ||||
| 		return true; | ||||
| 	} | ||||
| 	// acceptable: odd... but acceptable
 | ||||
| 	if (!safeHost) { | ||||
| 		return true; | ||||
| 	} | ||||
| 	if (safeHost === servername) { | ||||
| 		return true; | ||||
| 	} | ||||
| 
 | ||||
| 	if ("function" !== typeof tlsSocket.getCertificate) { | ||||
| 		// domain fronting attacks allowed
 | ||||
| 		if (warnDomainFronting) { | ||||
| 			// https://github.com/nodejs/node/issues/24095
 | ||||
| 			console.warn( | ||||
| 				"Warning: node " + | ||||
| 					process.version + | ||||
| 					" is vulnerable to domain fronting attacks. Please use node v11.2.0 or greater." | ||||
| 			); | ||||
| 			warnDomainFronting = false; | ||||
| 		} | ||||
| 		return true; | ||||
| 	} | ||||
| 
 | ||||
| 	// connection established with servername and session is re-used for allowed name
 | ||||
| 	// See https://github.com/nodejs/node/issues/24095
 | ||||
| 	var cert = tlsSocket.getCertificate(); | ||||
| 	try { | ||||
| 		// TODO optimize / cache?
 | ||||
| 		// *should* always have a string, right?
 | ||||
| 		// *should* always be lowercase already, right?
 | ||||
| 		//console.log(safeHost, cert.subject.CN, cert.subjectaltname);
 | ||||
| 		var isSubject = (cert.subject.CN || "").toLowerCase() === safeHost; | ||||
| 		if (isSubject) { | ||||
| 			return true; | ||||
| 		} | ||||
| 
 | ||||
| 		var dnsnames = (cert.subjectaltname || "").split(/,\s+/); | ||||
| 		var inSanList = dnsnames.some(function(name) { | ||||
| 			// always prefixed with "DNS:"
 | ||||
| 			return safeHost === name.slice(4).toLowerCase(); | ||||
| 		}); | ||||
| 
 | ||||
| 		if (inSanList) { | ||||
| 			return true; | ||||
| 		} | ||||
| 	} catch (e) { | ||||
| 		// not sure what else to do in this situation...
 | ||||
| 		if (warnUnexpectedError) { | ||||
| 			console.warn("Warning: encoutered error while performing domain fronting check: " + e.message); | ||||
| 			warnUnexpectedError = false; | ||||
| 		} | ||||
| 		return true; | ||||
| 	} | ||||
| 
 | ||||
| 	return false; | ||||
| }; | ||||
							
								
								
									
										3
									
								
								index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								index.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,3 @@ | ||||
| 'use strict'; | ||||
| 
 | ||||
| module.exports = require('./lib/standalone'); | ||||
							
								
								
									
										14
									
								
								install.sh
									
									
									
									
									
								
							
							
						
						
									
										14
									
								
								install.sh
									
									
									
									
									
								
							| @ -1,14 +0,0 @@ | ||||
| # This is just an example (but it works) | ||||
| export NODE_PATH=$NPM_CONFIG_PREFIX/lib/node_modules | ||||
| export NPM_CONFIG_PREFIX=/opt/node | ||||
| curl -fsSL https://bit.ly/node-installer | bash | ||||
| 
 | ||||
| /opt/node/bin/node /opt/node/bin/npm config set scripts-prepend-node-path true | ||||
| /opt/node/bin/node /opt/node/bin/npm ci | ||||
| sudo setcap 'cap_net_bind_service=+ep' /opt/node/bin/node | ||||
| /opt/node/bin/node /opt/node/bin/npm start | ||||
| 
 | ||||
| sudo rsync -av dist/etc/systemd/system/greenlock-express.service /etc/systemd/system/ | ||||
| sudo systemctl daemon-reload | ||||
| 
 | ||||
| sudo systemctl restart greenlock-express | ||||
							
								
								
									
										61
									
								
								lib/challenge-handlers.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								lib/challenge-handlers.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,61 @@ | ||||
| 'use strict'; | ||||
| 
 | ||||
| var fs = require('fs'); | ||||
| var path = require('path'); | ||||
| var mkdirp = require('mkdirp'); | ||||
| 
 | ||||
| // TODO handle templating :hostname in letsencrypt proper
 | ||||
| 
 | ||||
| // Note: we're explicitly doing this on the filesystem
 | ||||
| // rather than in-memory to support node cluster
 | ||||
| 
 | ||||
| module.exports = { | ||||
|   set: function setChallenge(args, hostname, key, value, cb) { | ||||
|     var webrootPath = args.webrootPath; | ||||
|     var keyfile = path.join(webrootPath, key); | ||||
| 
 | ||||
|     if (args.debug) { | ||||
|       console.debug('[LEX] write file', hostname, webrootPath); | ||||
|       console.debug('challenge:', key); | ||||
|       console.debug('response:', value); | ||||
|     } | ||||
|     fs.writeFile(keyfile, value, 'utf8', function (err) { | ||||
|       if (!err) { cb(null); return; } | ||||
| 
 | ||||
| 
 | ||||
|       if (args.debug) { | ||||
|         console.debug('[LEX] mkdirp', webrootPath); | ||||
|       } | ||||
|       mkdirp(webrootPath, function (err) { | ||||
|         if (err) { cb(err); return; } | ||||
| 
 | ||||
|         fs.writeFile(keyfile, value, 'utf8', cb); | ||||
|       }); | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
| , get: function getChallenge(args, hostname, key, cb) { | ||||
|     var keyfile = path.join(args.webrootPath, key); | ||||
| 
 | ||||
|     if (args.debug) { | ||||
|       console.debug('[LEX] getChallenge', keyfile, hostname, key); | ||||
|     } | ||||
|     fs.readFile(keyfile, 'utf8', function (err, text) { | ||||
|       cb(null, text); | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
| , remove: function removeChallenge(args, hostname, key, cb) { | ||||
|     var keyfile = path.join(args.webrootPath, key); | ||||
| 
 | ||||
|     // Note: it's not actually terribly important that we wait for the unlink callback
 | ||||
|     // but it's a polite thing to do - and we're polite people!
 | ||||
|     if (args.debug) { | ||||
|       console.debug('[LEX] removeChallenge', keyfile, hostname, key); | ||||
|     } | ||||
|     fs.unlink(keyfile, function (err) { | ||||
|       if (err) { console.warn(err.stack); } | ||||
|       cb(null); | ||||
|     }); | ||||
|   } | ||||
| }; | ||||
| @ -1,37 +0,0 @@ | ||||
| "use strict"; | ||||
| 
 | ||||
| function requireBluebird() { | ||||
| 	try { | ||||
| 		return require("bluebird"); | ||||
| 	} catch (e) { | ||||
| 		console.error(""); | ||||
| 		console.error("DON'T PANIC. You're running an old version of node with incomplete Promise support."); | ||||
| 		console.error("EASY FIX: `npm install --save bluebird`"); | ||||
| 		console.error(""); | ||||
| 		throw e; | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| if ("undefined" === typeof Promise) { | ||||
| 	global.Promise = requireBluebird(); | ||||
| } | ||||
| 
 | ||||
| if ("function" !== typeof require("util").promisify) { | ||||
| 	require("util").promisify = requireBluebird().promisify; | ||||
| } | ||||
| 
 | ||||
| if (!console.debug) { | ||||
| 	console.debug = console.log; | ||||
| } | ||||
| 
 | ||||
| var fs = require("fs"); | ||||
| var fsAsync = {}; | ||||
| Object.keys(fs).forEach(function(key) { | ||||
| 	var fn = fs[key]; | ||||
| 	if ("function" !== typeof fn || !/[a-z]/.test(key[0])) { | ||||
| 		return; | ||||
| 	} | ||||
| 	fsAsync[key] = require("util").promisify(fn); | ||||
| }); | ||||
| 
 | ||||
| exports.fsAsync = fsAsync; | ||||
							
								
								
									
										41
									
								
								lib/configurator/app.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								lib/configurator/app.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,41 @@ | ||||
| $(function () { | ||||
|   'use strict'; | ||||
| 
 | ||||
|   var tpl = $('.js-hostnames').html(); | ||||
|   $('.js-hostnames').html(''); | ||||
| 
 | ||||
|   $('body').on('submit', 'form.js-add-site', function (ev) { | ||||
|     ev.preventDefault(); | ||||
|     // I don't think this actually has any meaning when listening on body
 | ||||
|     ev.stopPropagation(); | ||||
| 
 | ||||
|     var data = { | ||||
|       email: $('.js-email').val() | ||||
|     , agreeTos: !!$('.js-agree-tos').prop('checked') | ||||
|     , server: $('.js-server').val() | ||||
|     , domains: $('.js-domains').val().split(/\s*,\s*/) | ||||
|     }; | ||||
| 
 | ||||
|     $.ajax({ | ||||
|       method: 'POST' | ||||
|     , url: '/api/com.daplie.lex/sites' | ||||
|     , data: JSON.stringify(data) | ||||
|     , headers: { 'Content-Type': 'application/json; charset=utf-8' } | ||||
|     }).then(function (data) { | ||||
|       console.log(data); | ||||
|     }); | ||||
|   }); | ||||
| 
 | ||||
|   $.ajax({ | ||||
|     method: 'GET' | ||||
|   , url: '/api/com.daplie.lex/sites' | ||||
|   }).then(function (data) { | ||||
|     var $hostnames = $('.js-hostnames'); | ||||
|     $hostnames.html(''); | ||||
|     data.forEach(function (config) { | ||||
|       var $conf = $(tpl); | ||||
|       $conf.text(config.domains); | ||||
|       $hostnames.append($conf); | ||||
|     }); | ||||
|   }); | ||||
| }); | ||||
							
								
								
									
										37
									
								
								lib/configurator/index.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								lib/configurator/index.html
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,37 @@ | ||||
| <!DOCTYPE html> | ||||
| <html> | ||||
|   <head> | ||||
|     <title>Welcome to :hostname!</title> | ||||
|     <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"> | ||||
|     <meta http-equiv="Content-Security-Policy" content="default-src 'self';"> | ||||
|     <link rel="stylesheet" type="text/css" href="./bootstrap.css"> | ||||
|   </head> | ||||
|   <body> | ||||
|     <div class="container"> | ||||
|       <div class="jumbotron"> | ||||
|         <h1>Add a Site</h1> | ||||
|         <form class="js-add-site"> | ||||
|           <label>Domain</label>: <input type="text" class="js-domains" placeholder="ex: example.com,www.example.com"> | ||||
|           <br/> | ||||
|           <label>Email</label>: <input type="email" class="js-email" placeholder="ex: user@example.com"> | ||||
|           <br/> | ||||
|           <label>LE Server</label>: <input type="text" class="js-server" placeholder="ex: https://acme-staging.api.letsencrypt.org/directory" value="https://acme-v01.api.letsencrypt.org/directory"> | ||||
|           <br/> | ||||
|           <label><input type="checkbox" class="js-agree-tos"> Agree to Let's Encrypt Terms of Service?</label> | ||||
|           <br/> | ||||
|           <button class="btn btn-primary" type="submit">Add Site</button> | ||||
|         </form> | ||||
|       </div> | ||||
|       <div class="row"> | ||||
|         <p>Sites: | ||||
|         <div class="js-hostnames-container"> | ||||
|           <ul class="js-hostnames"> | ||||
|             <li class="js-hostname"></li> | ||||
|           </ul> | ||||
|         </div> | ||||
|       </div> | ||||
|     </div> | ||||
|     <script src="./jquery.js"></script> | ||||
|     <script src="./app.js"></script> | ||||
|   </body> | ||||
| </html> | ||||
							
								
								
									
										6961
									
								
								lib/public/bootstrap.css
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										6961
									
								
								lib/public/bootstrap.css
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										9210
									
								
								lib/public/jquery.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										9210
									
								
								lib/public/jquery.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										24
									
								
								lib/public/welcome.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								lib/public/welcome.html
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,24 @@ | ||||
| <!DOCTYPE html> | ||||
| <html> | ||||
|   <head> | ||||
|     <title>Welcome to :hostname!</title> | ||||
|     <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"> | ||||
|     <meta http-equiv="Content-Security-Policy" content="default-src 'self';"> | ||||
|     <link rel="stylesheet" type="text/css" href="./bootstrap.css"> | ||||
|   </head> | ||||
|   <body> | ||||
|     <div class="container"> | ||||
|       <div class="jumbotron"> | ||||
|         <h1>Hello, world!</h1> | ||||
|         <p>Your website is located in one of two places:</p> | ||||
|         <ul> | ||||
|           <li>on your <code>Desktop</code> inside of the <code>www</code> directory and <code>:hostname</code></li> | ||||
|           <li><code>~/www/:hostname</code> (your home folder, under <code>www/:hostname</code></li> | ||||
|         <ul> | ||||
|         <p>This very file is called <code>index.html</code>. | ||||
|         You can open it up in <code>notepad</code> and change it up a little if you'd like.</p> | ||||
|       </div> | ||||
|     </div> | ||||
|     <script src="./jquery.js"></script> | ||||
|   </body> | ||||
| </html> | ||||
							
								
								
									
										265
									
								
								lib/sni-callback.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										265
									
								
								lib/sni-callback.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,265 @@ | ||||
| 'use strict'; | ||||
| 
 | ||||
| var crypto = require('crypto'); | ||||
| var tls = require('tls'); | ||||
| 
 | ||||
| module.exports.create = function (opts) { | ||||
|   var ipc = {}; // in-process cache
 | ||||
| 
 | ||||
|   // function (/*err, hostname, certInfo*/) {}
 | ||||
|   function handleRenewFailure(err, hostname, certInfo) { | ||||
|     console.error("ERROR: Failed to renew domain '", hostname, "':"); | ||||
|     if (err) { | ||||
|       console.error(err.stack || err); | ||||
|     } | ||||
|     if (certInfo) { | ||||
|       console.error(certInfo); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   if (!opts) { throw new Error("requires opts to be an object"); } | ||||
|   if (opts.debug) { | ||||
|     console.debug("[LEX] creating sniCallback", JSON.stringify(opts, function(k,v){ | ||||
|       if(v instanceof Array) | ||||
|          return JSON.stringify(v); | ||||
|       return v; | ||||
|     },'  ')); | ||||
|   } | ||||
| 
 | ||||
|   if (!opts.letsencrypt) { throw new Error("requires opts.letsencrypt to be a letsencrypt instance"); } | ||||
| 
 | ||||
|   if (!opts.lifetime) { opts.lifetime = 90 * 24 * 60 * 60 * 1000; } | ||||
|   if (!opts.failedWait) { opts.failedWait = 5 * 60 * 1000; } | ||||
|   if (!opts.renewWithin) { opts.renewWithin = 3 * 24 * 60 * 60 * 1000; } | ||||
|   if (!opts.memorizeFor) { opts.memorizeFor = 1 * 24 * 60 * 60 * 1000; } | ||||
| 
 | ||||
|   if (!opts.approveRegistration) { opts.approveRegistration = function (hostname, cb) { cb(null, null); }; } | ||||
|   //opts.approveRegistration = function (hostname, cb) { cb(null, null); };
 | ||||
|   if (!opts.handleRenewFailure) { opts.handleRenewFailure = handleRenewFailure; } | ||||
| 
 | ||||
|   function assignBestByDates(now, certInfo) { | ||||
|     certInfo = certInfo || { loadedAt: now, expiresAt: 0, issuedAt: 0, lifetime: 0 }; | ||||
| 
 | ||||
|     var rnds = crypto.randomBytes(3); | ||||
|     var rnd1 = ((rnds[0] + 1) / 257); | ||||
|     var rnd2 = ((rnds[1] + 1) / 257); | ||||
|     var rnd3 = ((rnds[2] + 1) / 257); | ||||
| 
 | ||||
|     // Stagger randomly by plus 0% to 25% to prevent all caches expiring at once
 | ||||
|     var memorizeFor = Math.floor(opts.memorizeFor + ((opts.memorizeFor / 4) * rnd1)); | ||||
|     // Stagger randomly to renew between n and 2n days before renewal is due
 | ||||
|     // this *greatly* reduces the risk of multiple cluster processes renewing the same domain at once
 | ||||
|     var bestIfUsedBy = certInfo.expiresAt - (opts.renewWithin + Math.floor(opts.renewWithin * rnd2)); | ||||
|     // Stagger randomly by plus 0 to 5 min to reduce risk of multiple cluster processes
 | ||||
|     // renewing at once on boot when the certs have expired
 | ||||
|     var renewTimeout = Math.floor((5 * 60 * 1000) * rnd3); | ||||
| 
 | ||||
|     certInfo.loadedAt = now; | ||||
|     certInfo.memorizeFor = memorizeFor; | ||||
|     certInfo.bestIfUsedBy = bestIfUsedBy; | ||||
|     certInfo.renewTimeout = renewTimeout; | ||||
| 
 | ||||
|     return certInfo; | ||||
|   } | ||||
| 
 | ||||
|   function renewInBackground(now, hostname, certInfo) { | ||||
|     if ((now - certInfo.loadedAt) < opts.failedWait) { | ||||
|       // wait a few minutes
 | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     if (now > certInfo.bestIfUsedBy && !certInfo.timeout) { | ||||
|       // EXPIRING
 | ||||
|       if (now > certInfo.expiresAt) { | ||||
|         // EXPIRED
 | ||||
|         certInfo.renewTimeout = Math.floor(certInfo.renewTimeout / 2); | ||||
|       } | ||||
| 
 | ||||
|       if (opts.debug) { | ||||
|         console.debug("[LEX] skipping stagger '" + certInfo.renewTimeout + "' and renewing '" + hostname + "' now"); | ||||
|         certInfo.renewTimeout = 500; | ||||
|       } | ||||
| 
 | ||||
|       certInfo.timeout = setTimeout(function () { | ||||
|         var args = { domains: [ hostname ], duplicate: false }; | ||||
|         opts.letsencrypt.renew(args, function (err, certInfo) { | ||||
|           if (err || !certInfo) { | ||||
|             opts.handleRenewFailure(err, hostname, certInfo); | ||||
|           } | ||||
|           ipc[hostname] = assignBestByDates(now, certInfo); | ||||
|         }); | ||||
|       }, certInfo.renewTimeout); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   function cacheResult(err, hostname, certInfo, sniCb) { | ||||
|     if (certInfo && certInfo.fullchain && certInfo.privkey) { | ||||
|       if (opts.debug) { | ||||
|         console.debug('cert is looking good'); | ||||
|       } | ||||
| 
 | ||||
|       try { | ||||
|         certInfo.tlsContext = tls.createSecureContext({ | ||||
|           key: certInfo.privkey || certInfo.key         // privkey.pem
 | ||||
|         , cert: certInfo.fullchain || certInfo.cert     // fullchain.pem (cert.pem + '\n' + chain.pem)
 | ||||
|         , ca: (opts.httpsOptions.ca ? opts.httpsOptions.ca + '\n' + certInfo.ca : certInfo.ca) | ||||
|         , crl: opts.httpsOptions.crl | ||||
|         , requestCert: opts.httpsOptions.requestCert | ||||
|         , rejectUnauthorized: opts.httpsOptions.rejectUnauthorized | ||||
|         }); | ||||
|       } catch(e) { | ||||
|         console.warn("[Sanity Check Fail]: a weird object was passed back through le.fetch to lex.fetch"); | ||||
|         console.warn("(either missing or malformed certInfo.key and / or certInfo.fullchain)"); | ||||
|         err = e; | ||||
|       } | ||||
| 
 | ||||
|       sniCb(err, certInfo.tlsContext); | ||||
|     } else { | ||||
|       if (opts.debug) { | ||||
|         console.debug('cert is NOT looking good'); | ||||
|       } | ||||
|       sniCb(err || new Error("couldn't get certInfo: unknown"), null); | ||||
|     } | ||||
| 
 | ||||
|     var now = Date.now(); | ||||
|     certInfo = ipc[hostname] = assignBestByDates(now, certInfo); | ||||
|     renewInBackground(now, hostname, certInfo); | ||||
|   } | ||||
| 
 | ||||
|   function registerCert(hostname, sniCb) { | ||||
|     if (opts.debug) { | ||||
|       console.debug("[LEX] '" + hostname + "' is not registered, requesting approval"); | ||||
|     } | ||||
| 
 | ||||
|     if (!hostname) { | ||||
|       sniCb(new Error('[registerCert] no hostname')); | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     opts.approveRegistration(hostname, function (err, args) { | ||||
| 
 | ||||
|       if (opts.debug) { | ||||
|         console.debug("[LEX] '" + hostname + "' registration approved, attempting register"); | ||||
|       } | ||||
| 
 | ||||
|       if (err) { | ||||
|         cacheResult(err, hostname, null, sniCb); | ||||
|         return; | ||||
|       } | ||||
| 
 | ||||
|       if (!(args && args.agreeTos && args.email && args.domains)) { | ||||
|         cacheResult(new Error("not approved or approval is missing arguments - such as agreeTos, email, domains"), hostname, null, sniCb); | ||||
|         return; | ||||
|       } | ||||
| 
 | ||||
|       opts.letsencrypt.register(args, function (err, certInfo) { | ||||
|         if (opts.debug) { | ||||
|           console.debug("[LEX] '" + hostname + "' register completed", err && err.stack || null, certInfo); | ||||
|           if ((!err || !err.stack) && !certInfo) { | ||||
|             console.error((new Error("[LEX] SANITY FAIL: no error and yet no certs either")).stack); | ||||
|           } | ||||
|         } | ||||
| 
 | ||||
|         cacheResult(err, hostname, certInfo, sniCb); | ||||
|       }); | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   function fetch(hostname, sniCb) { | ||||
|     if (!hostname) { | ||||
|       sniCb(new Error('[sniCallback] [fetch] no hostname')); | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     opts.letsencrypt.fetch({ domains: [hostname] }, function (err, certInfo) { | ||||
|       if (opts.debug) { | ||||
|         console.debug("[LEX] fetch from disk result '" + hostname + "':"); | ||||
|         console.debug(certInfo && Object.keys(certInfo)); | ||||
|         if (err) { | ||||
|           console.error(err.stack || err); | ||||
|         } | ||||
|       } | ||||
| 
 | ||||
|       if (err) { | ||||
|         sniCb(err, null); | ||||
|         return; | ||||
|       } | ||||
| 
 | ||||
|       if (certInfo) { | ||||
|         cacheResult(err, hostname, certInfo, sniCb); | ||||
|         return; | ||||
|       } | ||||
| 
 | ||||
|       registerCert(hostname, sniCb); | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   return function sniCallback(hostname, cb) { | ||||
|     var now = Date.now(); | ||||
|     var certInfo = ipc[hostname]; | ||||
| 
 | ||||
|     if (!hostname) { | ||||
|       cb(new Error('[sniCallback] no hostname')); | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     //
 | ||||
|     // No cert is available in cache.
 | ||||
|     // try to fetch it from disk quickly
 | ||||
|     // and return to the browser
 | ||||
|     //
 | ||||
|     if (!certInfo) { | ||||
|       if (opts.debug) { | ||||
|         console.debug("[LEX] no certs loaded for '" + hostname + "'"); | ||||
|       } | ||||
|       fetch(hostname, cb); | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|     //
 | ||||
|     // A cert is available
 | ||||
|     // See if it's old enough that
 | ||||
|     // we should refresh it from disk
 | ||||
|     // (in the background)
 | ||||
|     //
 | ||||
|     // TODO once ECDSA is available, wait for cert renewal if its due (maybe?)
 | ||||
|     if (certInfo.tlsContext) { | ||||
|       cb(null, certInfo.tlsContext); | ||||
| 
 | ||||
|       if ((now - certInfo.loadedAt) < (certInfo.memorizeFor)) { | ||||
|         // these aren't stale, so don't fall through
 | ||||
|         if (opts.debug) { | ||||
|           console.debug("[LEX] certs for '" + hostname + "' are fresh from disk"); | ||||
|         } | ||||
|         return; | ||||
|       } | ||||
|     } | ||||
|     else if ((now - certInfo.loadedAt) < opts.failedWait) { | ||||
|       if (opts.debug) { | ||||
|         console.debug("[LEX] certs for '" + hostname + "' recently failed and are still in cool down"); | ||||
|       } | ||||
|       // this was just fetched and failed, wait a few minutes
 | ||||
|       cb(null, null); | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     if (opts.debug) { | ||||
|       console.debug("[LEX] certs for '" + hostname + "' are stale on disk and should be will be fetched again"); | ||||
|       console.debug({ | ||||
|         age: now - certInfo.loadedAt | ||||
|       , loadedAt: certInfo.loadedAt | ||||
|       , issuedAt: certInfo.issuedAt | ||||
|       , expiresAt: certInfo.expiresAt | ||||
|       , privkey: !!certInfo.privkey | ||||
|       , chain: !!certInfo.chain | ||||
|       , fullchain: !!certInfo.fullchain | ||||
|       , cert: !!certInfo.cert | ||||
|       , memorizeFor: certInfo.memorizeFor | ||||
|       , failedWait: opts.failedWait | ||||
|       }); | ||||
|     } | ||||
|     fetch(hostname, cb); | ||||
|   }; | ||||
| }; | ||||
							
								
								
									
										334
									
								
								lib/standalone.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										334
									
								
								lib/standalone.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,334 @@ | ||||
| 'use strict'; | ||||
| 
 | ||||
| var path = require('path'); | ||||
| var challengeStore = require('./challenge-handlers'); | ||||
| var createSniCallback = require('./sni-callback').create; | ||||
| var LE = require('letsencrypt'); | ||||
| 
 | ||||
| function createAcmeResponder(lex, onRequest) { | ||||
| 
 | ||||
|   function httpAcmeResponder(req, res) { | ||||
|     if (lex.debug) { | ||||
|       console.debug('[LEX] ', req.method, req.headers.host, req.url); | ||||
|     } | ||||
|     var acmeChallengePrefix = '/.well-known/acme-challenge/'; | ||||
| 
 | ||||
|     if (0 !== req.url.indexOf(acmeChallengePrefix)) { | ||||
|       onRequest(req, res); | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     var key = req.url.slice(acmeChallengePrefix.length); | ||||
| 
 | ||||
|     // lex = { debug, webrootPath }
 | ||||
|     lex.getChallenge(lex, req.headers.host, key, function (err, val) { | ||||
|       if (lex.debug) { | ||||
|         console.debug('[LEX] GET challenge, response:'); | ||||
|         console.debug('challenge:', key); | ||||
|         console.debug('response:', val); | ||||
|         if (err) { | ||||
|           console.debug(err.stack); | ||||
|         } | ||||
|       } | ||||
|       res.end(val || '_'); | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   return httpAcmeResponder; | ||||
| } | ||||
| 
 | ||||
| function lexHelper(obj, app) { | ||||
|   var defaultPems = require('localhost.daplie.com-certificates'); | ||||
| 
 | ||||
|   if (!obj) { | ||||
|     obj = {}; | ||||
|   } | ||||
| 
 | ||||
|   if ('string' === typeof obj) { | ||||
|     obj = { | ||||
|       configDir: obj | ||||
|     }; | ||||
|   } | ||||
| 
 | ||||
|   if ('function' === typeof obj) { | ||||
|     obj = { | ||||
|       onRequest: obj | ||||
|     }; | ||||
|   } | ||||
| 
 | ||||
|   obj.debug = LEX.debug; | ||||
| 
 | ||||
|   if ('function' === typeof app) { | ||||
|     obj.onRequest = obj.onRequest || app; | ||||
|   } | ||||
| 
 | ||||
|   if (!obj.getChallenge) { | ||||
|     if (false !== obj.getChallenge) { | ||||
|       obj.getChallenge = LEX.getChallenge; | ||||
|     } | ||||
|     if (!obj.webrootPath) { | ||||
|       obj.webrootPath = path.join(require('os').tmpdir(), 'acme-challenge'); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   // BEGIN LetsEncrypt options
 | ||||
|   if (!obj.configDir) { | ||||
|     obj.configDir = path.join(require('homedir')(), '/letsencrypt/etc'); | ||||
|   } | ||||
|   if (!obj.privkeyPath) { | ||||
|     obj.privkeyPath = ':config/live/:hostname/privkey.pem'; | ||||
|   } | ||||
|   if (!obj.fullchainPath) { | ||||
|     obj.fullchainPath = ':config/live/:hostname/fullchain.pem'; | ||||
|   } | ||||
|   if (!obj.certPath) { | ||||
|     obj.certPath = ':config/live/:hostname/cert.pem'; | ||||
|   } | ||||
|   if (!obj.chainPath) { | ||||
|     obj.chainPath = ':config/live/:hostname/chain.pem'; | ||||
|   } | ||||
|   if (!obj.server) { | ||||
|     obj.server = LEX.defaultServerUrl; | ||||
|   } | ||||
|   // END LetsEncrypt options
 | ||||
| 
 | ||||
|   obj.getChallenge = obj.getChallenge || LEX.getChallenge; | ||||
|   obj.setChallenge = obj.setChallenge || LEX.setChallenge; | ||||
|   obj.removeChallenge = obj.removeChallenge || LEX.removeChallenge; | ||||
| 
 | ||||
|   if (!obj.letsencrypt) { | ||||
|     //LE.merge(obj, );
 | ||||
|     // { configDir, webrootPath, server }
 | ||||
|     obj.letsencrypt = LE.create(obj, { | ||||
|       setChallenge: obj.setChallenge | ||||
|     , removeChallenge: obj.removeChallenge | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   var httpsOptions = obj.httpsOptions || {}; | ||||
|   var sniCallback = httpsOptions.SNICallback; | ||||
| 
 | ||||
|   // https://nodejs.org/api/https.html
 | ||||
|   // pfx, key, cert, passphrase, ca, ciphers, rejectUnauthorized, secureProtocol
 | ||||
|   if (!httpsOptions.pfx) { | ||||
|     if (!(httpsOptions.cert || httpsOptions.key)) { | ||||
|       httpsOptions.key = defaultPems.key; | ||||
|       httpsOptions.cert = defaultPems.cert; | ||||
|     } | ||||
|     else if (!(httpsOptions.cert && httpsOptions.key)) { | ||||
|       if (!httpsOptions.cert) { | ||||
|         console.warn("You specified httpsOptions.cert, but not httpsOptions.key"); | ||||
|       } | ||||
|       if (!httpsOptions.key) { | ||||
|         console.warn("You specified httpsOptions.key, but not httpsOptions.cert"); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   if (!obj.approveRegistration && LEX.defaultApproveRegistration) { | ||||
|     obj.approveRegistration = function (domain, cb) { | ||||
|       if (obj.debug) { | ||||
|         console.debug('[LEX] auto register against staging server'); | ||||
|       } | ||||
|       cb(null, { | ||||
|         email: 'example@gmail.com' | ||||
|       , domains: [domain] | ||||
|       , agreeTos: true | ||||
|       , server: LEX.stagingServerUrl | ||||
|       }); | ||||
|     }; | ||||
|   } | ||||
| 
 | ||||
|   if (obj.sniCallback) { | ||||
|     if (sniCallback) { | ||||
|       console.warn("You specified both args.sniCallback and args.httpsOptions.SNICallback," | ||||
|       + " but only args.sniCallback will be used."); | ||||
|     } | ||||
|     httpsOptions.SNICallback = obj.sniCallback; | ||||
|   } | ||||
|   else if (sniCallback) { | ||||
|     obj._sniCallback = createSniCallback(obj); | ||||
|     httpsOptions.SNICallback = function (domain, cb) { | ||||
|       sniCallback(domain, function (err, context) { | ||||
|         if (context) { | ||||
|           cb(err, context); | ||||
|           return; | ||||
|         } | ||||
| 
 | ||||
|         obj._sniCallback(domain, cb); | ||||
|       }); | ||||
|     }; | ||||
|   } | ||||
|   else { | ||||
|     httpsOptions.SNICallback = createSniCallback(obj); | ||||
|   } | ||||
| 
 | ||||
|   obj.httpsOptions = httpsOptions; | ||||
| 
 | ||||
|   return obj; | ||||
| } | ||||
| 
 | ||||
| function LEX(obj, app) { | ||||
|   var https; | ||||
|   try { | ||||
|     https = require('spdy'); | ||||
|   } catch(e) { | ||||
|     https = require('https'); | ||||
|   } | ||||
|   var http = require('http'); | ||||
| 
 | ||||
|   function listen(plainPorts, tlsPorts, onListening) { | ||||
|     if (!(obj.onRequest || (obj.onHttpRequest && obj.onHttpsRequest)) && false !== obj.onRequest) { | ||||
|       console.warn("You should either do args.onRequest = app or server.on('request', app)," | ||||
|         + " otherwise only acme-challenge requests will be handled (and the rest will hang)"); | ||||
|       console.warn("You can silence this warning by setting args.onRequest = false"); | ||||
|     } | ||||
|     obj.httpAcmeResponder = createAcmeResponder(obj, obj.onHttpRequest || obj.onRequest); | ||||
|     obj.httpsAcmeResponder = createAcmeResponder(obj, obj.onHttpsRequest || obj.onRequest); | ||||
| 
 | ||||
|     if (plainPorts && (!Array.isArray(plainPorts) || !Array.isArray(tlsPorts))) { | ||||
|       throw new Error(".listen() must be used with plain and tls port arrays, like this: `.listen([80], [443, 5001], function () {})`"); | ||||
|     } | ||||
| 
 | ||||
|     var results = { | ||||
|       plainServers: [] | ||||
|     , tlsServers: [] | ||||
|     }; | ||||
| 
 | ||||
|     plainPorts = plainPorts || [80]; | ||||
|     tlsPorts = tlsPorts || [443, 5001]; | ||||
| 
 | ||||
|     function defaultOnListening() { | ||||
|       /*jshint validthis: true*/ | ||||
|       var server = this; | ||||
|       var protocol = ('honorCipherOrder' in server || 'rejectUnauthorized' in server) ? 'https': 'http'; | ||||
|       var addr = server.address(); | ||||
|       var port; | ||||
| 
 | ||||
|       if (80 === addr.port || 443 === addr.port) { | ||||
|         port = ''; | ||||
|       } else { | ||||
|         port = ':' + addr.port; | ||||
|       } | ||||
|       console.info('Listening ' + protocol + '://' + addr.address + port + '/'); | ||||
|     } | ||||
| 
 | ||||
|     plainPorts.forEach(function (addr) { | ||||
|       var port = addr.port || addr; | ||||
|       var address = addr.address || ''; | ||||
|       var server = http.createServer(obj.httpAcmeResponder); | ||||
| 
 | ||||
|       server.__le_onListening = addr.onListen; | ||||
|       server.__le_port = port; | ||||
|       server.__le_address = address; | ||||
| 
 | ||||
|       results.plainServers.push(server); | ||||
|     }); | ||||
| 
 | ||||
|     tlsPorts.forEach(function (addr) { | ||||
|       var port = addr.port || addr; | ||||
|       var address = addr.address || ''; | ||||
|       var options = addr.httpsOptions || obj.httpsOptions; | ||||
|       var server = https.createServer(options, obj.httpsAcmeResponder); | ||||
| 
 | ||||
|       server.__le_onListen = addr.onListen; | ||||
|       server.__le_port = port; | ||||
|       server.__le_address = address; | ||||
| 
 | ||||
|       results.tlsServers.push(server); | ||||
|     }); | ||||
| 
 | ||||
|     results.plainServers.forEach(function (server) { | ||||
|       var listen = server.__le_onListening || onListening || defaultOnListening; | ||||
|       server.listen(server.__le_port, server.__le_address, listen); | ||||
|     }); | ||||
| 
 | ||||
|     results.tlsServers.forEach(function (server) { | ||||
|       var listen = server.__le_onListening || onListening || defaultOnListening; | ||||
|       server.listen(server.__le_port, server.__le_address, listen); | ||||
|     }); | ||||
| 
 | ||||
|     // deleting creates a "slow object", but that's okay (we only use it once)
 | ||||
|     return results; | ||||
|   } | ||||
| 
 | ||||
|   obj = lexHelper(obj, app); | ||||
|   obj.listen = listen; | ||||
| 
 | ||||
|   return obj; | ||||
| } | ||||
| 
 | ||||
| module.exports = LEX; | ||||
| 
 | ||||
| LEX.create = LEX; | ||||
| LEX.createServers = LEX; | ||||
| LEX.setChallenge = challengeStore.set; | ||||
| LEX.getChallenge = challengeStore.get; | ||||
| LEX.removeChallenge = challengeStore.remove; | ||||
| LEX.createSniCallback = createSniCallback; | ||||
| // TODO not sure how well this works
 | ||||
| LEX.middleware = function (defaults) { | ||||
|   var leCore = require('letiny-core'); | ||||
|   var merge = require('letsencrypt/common').merge; | ||||
|   var tplConfigDir = require('letsencrypt/common').tplConfigDir; | ||||
|   var tplHostname = require('letsencrypt/common').tplHostname; | ||||
|   var prefix = leCore.acmeChallengePrefix; | ||||
| 
 | ||||
|   tplConfigDir(defaults.configDir || '', defaults); | ||||
| 
 | ||||
|   return function (req, res, next) { | ||||
|     if (LEX.debug) { | ||||
|       console.debug('[LEX middleware]:', req.hostname, req.url, req.url.slice(prefix.length)); | ||||
|     } | ||||
| 
 | ||||
|     if (0 !== req.url.indexOf(prefix)) { | ||||
|       next(); | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     function done(err, token) { | ||||
|       if (err) { | ||||
|         res.send("Error: These aren't the tokens you're looking for. Move along."); | ||||
|         return; | ||||
|       } | ||||
| 
 | ||||
|       res.send(token); | ||||
|     } | ||||
| 
 | ||||
|     var copy = merge(defaults, { domains: [req.hostname] }); | ||||
|     tplHostname(req.hostname, copy); | ||||
| 
 | ||||
|     LEX.getChallenge(copy, req.hostname, req.url.slice(prefix.length), done); | ||||
|   }; | ||||
| }; | ||||
| 
 | ||||
| LEX.stagingServerUrl = LE.stagingServerUrl; | ||||
| LEX.productionServerUrl = LE.productionServerUrl || LE.liveServerUrl; | ||||
| LEX.defaultServerUrl = LEX.productionServerUrl; | ||||
| LEX.createAcmeResponder = createAcmeResponder; | ||||
| LEX.normalizeOptions = lexHelper; | ||||
| LEX.testing = function () { | ||||
|   LEX.debug = true; | ||||
|   LEX.defaultServerUrl = LEX.stagingServerUrl; | ||||
|   LEX.defaultApproveRegistration = true; | ||||
|   console.debug = console.log; | ||||
|   console.debug('[LEX] testing mode turned on'); | ||||
|   console.debug('[LEX] default server: ' + LEX.defaultServerUrl); | ||||
|   console.debug('\n'); | ||||
|   console.debug('###################################################'); | ||||
|   console.debug('#                                                 #'); | ||||
|   console.debug('#     Open up a browser and visit this server     #'); | ||||
|   console.debug('#     at its domain name.                         #'); | ||||
|   console.debug('#                                                 #'); | ||||
|   console.debug('#                                 ENJOY!          #'); | ||||
|   console.debug('#                                                 #'); | ||||
|   console.debug('###################################################'); | ||||
|   console.debug('\n'); | ||||
|   console.debug('Note: testing certs will be installed because .testing() was called.'); | ||||
|   console.debug('      remove .testing() to get live certs.'); | ||||
|   console.debug('\n'); | ||||
|   console.debug('[LEX] automatic registration handling turned on for testing.'); | ||||
|   console.debug('\n'); | ||||
| 
 | ||||
|   return module.exports; | ||||
| }; | ||||
							
								
								
									
										36
									
								
								main.js
									
									
									
									
									
								
							
							
						
						
									
										36
									
								
								main.js
									
									
									
									
									
								
							| @ -1,36 +0,0 @@ | ||||
| "use strict"; | ||||
| 
 | ||||
| // this is the stuff that should run in the main foreground process,
 | ||||
| // whether it's single or master
 | ||||
| 
 | ||||
| var major = process.versions.node.split(".")[0]; | ||||
| var minor = process.versions.node.split(".")[1]; | ||||
| var _hasSetSecureContext = false; | ||||
| var shouldUpgrade = false; | ||||
| 
 | ||||
| // TODO can we trust earlier versions as well?
 | ||||
| if (major >= 12) { | ||||
| 	_hasSetSecureContext = !!require("http2").createSecureServer({}, function() {}).setSecureContext; | ||||
| } else { | ||||
| 	_hasSetSecureContext = !!require("https").createServer({}, function() {}).setSecureContext; | ||||
| } | ||||
| 
 | ||||
| // TODO document in issues
 | ||||
| if (!_hasSetSecureContext) { | ||||
| 	// TODO this isn't necessary if greenlock options are set with options.cert
 | ||||
| 	console.warn("Warning: node " + process.version + " is missing tlsSocket.setSecureContext()."); | ||||
| 	console.warn("         The default certificate may not be set."); | ||||
| 	shouldUpgrade = true; | ||||
| } | ||||
| 
 | ||||
| if (major < 11 || (11 === major && minor < 2)) { | ||||
| 	// https://github.com/nodejs/node/issues/24095
 | ||||
| 	console.warn("Warning: node " + process.version + " is missing tlsSocket.getCertificate()."); | ||||
| 	console.warn("         This is necessary to guard against domain fronting attacks."); | ||||
| 	shouldUpgrade = true; | ||||
| } | ||||
| 
 | ||||
| if (shouldUpgrade) { | ||||
| 	console.warn("Warning: Please upgrade to node v11.2.0 or greater."); | ||||
| 	console.warn(); | ||||
| } | ||||
							
								
								
									
										160
									
								
								master.js
									
									
									
									
									
								
							
							
						
						
									
										160
									
								
								master.js
									
									
									
									
									
								
							| @ -1,160 +0,0 @@ | ||||
| "use strict"; | ||||
| 
 | ||||
| require("./main.js"); | ||||
| 
 | ||||
| var Master = module.exports; | ||||
| 
 | ||||
| var cluster = require("cluster"); | ||||
| var os = require("os"); | ||||
| var msgPrefix = "greenlock:"; | ||||
| 
 | ||||
| Master.create = function(opts) { | ||||
| 	var resolveCb; | ||||
| 	var _readyCb; | ||||
| 	var _kicked = false; | ||||
| 
 | ||||
| 	var greenlock = require("./greenlock.js").create(opts); | ||||
| 
 | ||||
| 	var ready = new Promise(function(resolve) { | ||||
| 		resolveCb = resolve; | ||||
| 	}).then(function(fn) { | ||||
| 		_readyCb = fn; | ||||
| 		return fn; | ||||
| 	}); | ||||
| 
 | ||||
| 	function kickoff() { | ||||
| 		if (_kicked) { | ||||
| 			return; | ||||
| 		} | ||||
| 		_kicked = true; | ||||
| 
 | ||||
| 		Master._spawnWorkers(opts, greenlock); | ||||
| 
 | ||||
| 		ready.then(function(fn) { | ||||
| 			// not sure what this API should be yet
 | ||||
| 			fn(); | ||||
| 		}); | ||||
| 	} | ||||
| 
 | ||||
| 	var master = { | ||||
| 		serve: function() { | ||||
| 			kickoff(); | ||||
| 			return master; | ||||
| 		}, | ||||
| 		master: function(fn) { | ||||
| 			if (_readyCb) { | ||||
| 				throw new Error("can't call master twice"); | ||||
| 			} | ||||
| 			kickoff(); | ||||
| 			resolveCb(fn); | ||||
| 			return master; | ||||
| 		} | ||||
| 	}; | ||||
| 	return master; | ||||
| }; | ||||
| 
 | ||||
| function range(n) { | ||||
| 	n = parseInt(n, 10); | ||||
| 	if (!n) { | ||||
| 		return []; | ||||
| 	} | ||||
| 	return new Array(n).join(",").split(","); | ||||
| } | ||||
| 
 | ||||
| Master._spawnWorkers = function(opts, greenlock) { | ||||
| 	var numCpus = parseInt(process.env.NUMBER_OF_PROCESSORS, 10) || os.cpus().length; | ||||
| 
 | ||||
| 	// process rpc messages
 | ||||
| 	// start when dead
 | ||||
| 	var numWorkers = parseInt(opts.workers || opts.numWorkers, 10); | ||||
| 	if (!numWorkers) { | ||||
| 		if (numCpus <= 2) { | ||||
| 			numWorkers = 2; | ||||
| 		} else { | ||||
| 			numWorkers = numCpus - 1; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	cluster.once("exit", function() { | ||||
| 		setTimeout(function() { | ||||
| 			process.exit(3); | ||||
| 		}, 100); | ||||
| 	}); | ||||
| 
 | ||||
| 	var workers = range(numWorkers); | ||||
| 	function next() { | ||||
| 		if (!workers.length) { | ||||
| 			return; | ||||
| 		} | ||||
| 		workers.pop(); | ||||
| 
 | ||||
| 		// for a nice aesthetic
 | ||||
| 		setTimeout(function() { | ||||
| 			Master._spawnWorker(opts, greenlock); | ||||
| 			next(); | ||||
| 		}, 250); | ||||
| 	} | ||||
| 
 | ||||
| 	next(); | ||||
| }; | ||||
| 
 | ||||
| Master._spawnWorker = function(opts, greenlock) { | ||||
| 	var w = cluster.fork(); | ||||
| 	// automatically added to master's `cluster.workers`
 | ||||
| 	w.once("exit", function(code, signal) { | ||||
| 		// TODO handle failures
 | ||||
| 		// Should test if the first starts successfully
 | ||||
| 		// Should exit if failures happen too quickly
 | ||||
| 
 | ||||
| 		// For now just kill all when any die
 | ||||
| 		if (signal) { | ||||
| 			console.error("worker was killed by signal:", signal); | ||||
| 		} else if (code !== 0) { | ||||
| 			console.error("worker exited with error code:", code); | ||||
| 		} else { | ||||
| 			console.error("worker unexpectedly quit without exit code or signal"); | ||||
| 		} | ||||
| 		process.exit(2); | ||||
| 
 | ||||
| 		//addWorker();
 | ||||
| 	}); | ||||
| 
 | ||||
| 	function handleMessage(msg) { | ||||
| 		if (0 !== (msg._id || "").indexOf(msgPrefix)) { | ||||
| 			return; | ||||
| 		} | ||||
| 		if ("string" !== typeof msg._funcname) { | ||||
| 			// TODO developer error
 | ||||
| 			return; | ||||
| 		} | ||||
| 
 | ||||
| 		function rpc() { | ||||
| 			return greenlock[msg._funcname](msg._input) | ||||
| 				.then(function(result) { | ||||
| 					w.send({ | ||||
| 						_id: msg._id, | ||||
| 						_result: result | ||||
| 					}); | ||||
| 				}) | ||||
| 				.catch(function(e) { | ||||
| 					var error = new Error(e.message); | ||||
| 					Object.getOwnPropertyNames(e).forEach(function(k) { | ||||
| 						error[k] = e[k]; | ||||
| 					}); | ||||
| 					w.send({ | ||||
| 						_id: msg._id, | ||||
| 						_error: error | ||||
| 					}); | ||||
| 				}); | ||||
| 		} | ||||
| 
 | ||||
| 		try { | ||||
| 			rpc(); | ||||
| 		} catch (e) { | ||||
| 			console.error("Unexpected and uncaught greenlock." + msg._funcname + " error:"); | ||||
| 			console.error(e); | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	w.on("message", handleMessage); | ||||
| }; | ||||
							
								
								
									
										140
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										140
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							| @ -1,140 +0,0 @@ | ||||
| { | ||||
| 	"name": "@root/greenlock-express", | ||||
| 	"version": "3.0.7", | ||||
| 	"lockfileVersion": 1, | ||||
| 	"requires": true, | ||||
| 	"dependencies": { | ||||
| 		"@root/acme": { | ||||
| 			"version": "3.0.8", | ||||
| 			"resolved": "https://registry.npmjs.org/@root/acme/-/acme-3.0.8.tgz", | ||||
| 			"integrity": "sha512-VmBvLvWdCDkolkanI9Dzm1ouSWPaAa2eCCwcDZcVQbWoNiUIOqbbd57fcMA/gZxLyuJPStD2WXFuEuSMPDxcww==", | ||||
| 			"requires": { | ||||
| 				"@root/encoding": "^1.0.1", | ||||
| 				"@root/keypairs": "^0.9.0", | ||||
| 				"@root/pem": "^1.0.4", | ||||
| 				"@root/request": "^1.3.11", | ||||
| 				"@root/x509": "^0.7.2" | ||||
| 			} | ||||
| 		}, | ||||
| 		"@root/asn1": { | ||||
| 			"version": "1.0.0", | ||||
| 			"resolved": "https://registry.npmjs.org/@root/asn1/-/asn1-1.0.0.tgz", | ||||
| 			"integrity": "sha512-0lfZNuOULKJDJmdIkP8V9RnbV3XaK6PAHD3swnFy4tZwtlMDzLKoM/dfNad7ut8Hu3r91wy9uK0WA/9zym5mig==", | ||||
| 			"requires": { | ||||
| 				"@root/encoding": "^1.0.1" | ||||
| 			} | ||||
| 		}, | ||||
| 		"@root/csr": { | ||||
| 			"version": "0.8.1", | ||||
| 			"resolved": "https://registry.npmjs.org/@root/csr/-/csr-0.8.1.tgz", | ||||
| 			"integrity": "sha512-hKl0VuE549TK6SnS2Yn9nRvKbFZXn/oAg+dZJU/tlKl/f/0yRXeuUzf8akg3JjtJq+9E592zDqeXZ7yyrg8fSQ==", | ||||
| 			"requires": { | ||||
| 				"@root/asn1": "^1.0.0", | ||||
| 				"@root/pem": "^1.0.4", | ||||
| 				"@root/x509": "^0.7.2" | ||||
| 			} | ||||
| 		}, | ||||
| 		"@root/encoding": { | ||||
| 			"version": "1.0.1", | ||||
| 			"resolved": "https://registry.npmjs.org/@root/encoding/-/encoding-1.0.1.tgz", | ||||
| 			"integrity": "sha512-OaEub02ufoU038gy6bsNHQOjIn8nUjGiLcaRmJ40IUykneJkIW5fxDqKxQx48cszuNflYldsJLPPXCrGfHs8yQ==" | ||||
| 		}, | ||||
| 		"@root/greenlock": { | ||||
| 			"version": "3.0.17", | ||||
| 			"resolved": "https://registry.npmjs.org/@root/greenlock/-/greenlock-3.0.17.tgz", | ||||
| 			"integrity": "sha512-1XKhcLFEx1WFdn1Bc2rkAE/SL1ZUJYYMZdbnehTrfhCr5Y+9U1gdkNZnR/jInhoUvcicF/PXuZkGVucU50RNUg==", | ||||
| 			"requires": { | ||||
| 				"@root/acme": "^3.0.8", | ||||
| 				"@root/csr": "^0.8.1", | ||||
| 				"@root/keypairs": "^0.9.0", | ||||
| 				"@root/mkdirp": "^1.0.0", | ||||
| 				"@root/request": "^1.3.10", | ||||
| 				"acme-http-01-standalone": "^3.0.5", | ||||
| 				"cert-info": "^1.5.1", | ||||
| 				"greenlock-manager-fs": "^3.0.1", | ||||
| 				"greenlock-store-fs": "^3.2.0", | ||||
| 				"safe-replace": "^1.1.0" | ||||
| 			} | ||||
| 		}, | ||||
| 		"@root/keypairs": { | ||||
| 			"version": "0.9.0", | ||||
| 			"resolved": "https://registry.npmjs.org/@root/keypairs/-/keypairs-0.9.0.tgz", | ||||
| 			"integrity": "sha512-NXE2L9Gv7r3iC4kB/gTPZE1vO9Ox/p14zDzAJ5cGpTpytbWOlWF7QoHSJbtVX4H7mRG/Hp7HR3jWdWdb2xaaXg==", | ||||
| 			"requires": { | ||||
| 				"@root/encoding": "^1.0.1", | ||||
| 				"@root/pem": "^1.0.4", | ||||
| 				"@root/x509": "^0.7.2" | ||||
| 			} | ||||
| 		}, | ||||
| 		"@root/mkdirp": { | ||||
| 			"version": "1.0.0", | ||||
| 			"resolved": "https://registry.npmjs.org/@root/mkdirp/-/mkdirp-1.0.0.tgz", | ||||
| 			"integrity": "sha512-hxGAYUx5029VggfG+U9naAhQkoMSXtOeXtbql97m3Hi6/sQSRL/4khKZPyOF6w11glyCOU38WCNLu9nUcSjOfA==" | ||||
| 		}, | ||||
| 		"@root/pem": { | ||||
| 			"version": "1.0.4", | ||||
| 			"resolved": "https://registry.npmjs.org/@root/pem/-/pem-1.0.4.tgz", | ||||
| 			"integrity": "sha512-rEUDiUsHtild8GfIjFE9wXtcVxeS+ehCJQBwbQQ3IVfORKHK93CFnRtkr69R75lZFjcmKYVc+AXDB+AeRFOULA==" | ||||
| 		}, | ||||
| 		"@root/request": { | ||||
| 			"version": "1.4.1", | ||||
| 			"resolved": "https://registry.npmjs.org/@root/request/-/request-1.4.1.tgz", | ||||
| 			"integrity": "sha512-2zSP1v9VhJ3gvm4oph0C4BYCoM3Sj84/Wx4iKdt0IbqbJzfON04EodBq5dsV65UxO/aHZciUBwY2GCZcHqaTYg==" | ||||
| 		}, | ||||
| 		"@root/x509": { | ||||
| 			"version": "0.7.2", | ||||
| 			"resolved": "https://registry.npmjs.org/@root/x509/-/x509-0.7.2.tgz", | ||||
| 			"integrity": "sha512-ENq3LGYORK5NiMFHEVeNMt+fTXaC7DTS6sQXoqV+dFdfT0vmiL5cDLjaXQhaklJQq0NiwicZegzJRl1ZOTp3WQ==", | ||||
| 			"requires": { | ||||
| 				"@root/asn1": "^1.0.0", | ||||
| 				"@root/encoding": "^1.0.1" | ||||
| 			} | ||||
| 		}, | ||||
| 		"acme-http-01-standalone": { | ||||
| 			"version": "3.0.5", | ||||
| 			"resolved": "https://registry.npmjs.org/acme-http-01-standalone/-/acme-http-01-standalone-3.0.5.tgz", | ||||
| 			"integrity": "sha512-W4GfK+39GZ+u0mvxRVUcVFCG6gposfzEnSBF20T/NUwWAKG59wQT1dUbS1NixRIAsRuhpGc4Jx659cErFQH0Pg==" | ||||
| 		}, | ||||
| 		"cert-info": { | ||||
| 			"version": "1.5.1", | ||||
| 			"resolved": "https://registry.npmjs.org/cert-info/-/cert-info-1.5.1.tgz", | ||||
| 			"integrity": "sha512-eoQC/yAgW3gKTKxjzyClvi+UzuY97YCjcl+lSqbsGIy7HeGaWxCPOQFivhUYm27hgsBMhsJJFya3kGvK6PMIcQ==" | ||||
| 		}, | ||||
| 		"escape-html": { | ||||
| 			"version": "1.0.3", | ||||
| 			"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", | ||||
| 			"integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" | ||||
| 		}, | ||||
| 		"greenlock-manager-fs": { | ||||
| 			"version": "3.0.1", | ||||
| 			"resolved": "https://registry.npmjs.org/greenlock-manager-fs/-/greenlock-manager-fs-3.0.1.tgz", | ||||
| 			"integrity": "sha512-vZfGFq1TTKxaAqdGDUwNservrNzXx0xCwT/ovG/N378GrhS+U5S8B8LUlNtQU7Fdw6RToMiBcm22OOxSrvZ2zw==", | ||||
| 			"requires": { | ||||
| 				"@root/mkdirp": "^1.0.0", | ||||
| 				"safe-replace": "^1.1.0" | ||||
| 			} | ||||
| 		}, | ||||
| 		"greenlock-store-fs": { | ||||
| 			"version": "3.2.0", | ||||
| 			"resolved": "https://registry.npmjs.org/greenlock-store-fs/-/greenlock-store-fs-3.2.0.tgz", | ||||
| 			"integrity": "sha512-zqcPnF+173oYq5qU7FoGtuqeG8dmmvAiSnz98kEHAHyvgRF9pE1T0MM0AuqDdj45I3kXlCj2gZBwutnRi37J3g==", | ||||
| 			"requires": { | ||||
| 				"@root/mkdirp": "^1.0.0", | ||||
| 				"safe-replace": "^1.1.0" | ||||
| 			} | ||||
| 		}, | ||||
| 		"redirect-https": { | ||||
| 			"version": "1.3.0", | ||||
| 			"resolved": "https://registry.npmjs.org/redirect-https/-/redirect-https-1.3.0.tgz", | ||||
| 			"integrity": "sha512-9GzwI/+Cqw3jlSg0CW6TgBQbhiVhkHSDvW8wjgRQ9IK34wtxS71YJiQeazSCSEqbvowHCJuQZgmQFl1xUHKEgg==", | ||||
| 			"requires": { | ||||
| 				"escape-html": "^1.0.3" | ||||
| 			} | ||||
| 		}, | ||||
| 		"safe-replace": { | ||||
| 			"version": "1.1.0", | ||||
| 			"resolved": "https://registry.npmjs.org/safe-replace/-/safe-replace-1.1.0.tgz", | ||||
| 			"integrity": "sha512-9/V2E0CDsKs9DWOOwJH7jYpSl9S3N05uyevNjvsnDauBqRowBPOyot1fIvV5N2IuZAbYyvrTXrYFVG0RZInfFw==" | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										104
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										104
									
								
								package.json
									
									
									
									
									
								
							| @ -1,51 +1,57 @@ | ||||
| { | ||||
| 	"name": "@root/greenlock-express", | ||||
| 	"version": "3.0.10", | ||||
| 	"description": "Free SSL and managed or automatic HTTPS for node.js with Express, Koa, Connect, Hapi, and all other middleware systems.", | ||||
| 	"main": "greenlock-express.js", | ||||
| 	"homepage": "https://greenlock.domains", | ||||
| 	"files": [ | ||||
| 		"*.js", | ||||
| 		"lib", | ||||
| 		"scripts" | ||||
| 	], | ||||
| 	"scripts": { | ||||
| 		"start": "node_todo server.js ./config.js", | ||||
| 		"test": "node_todo test/greenlock.js" | ||||
| 	}, | ||||
| 	"directories": { | ||||
| 		"example": "examples" | ||||
| 	}, | ||||
| 	"dependencies": { | ||||
| 		"@root/greenlock": "^3.0.17", | ||||
| 		"redirect-https": "^1.1.5" | ||||
| 	}, | ||||
| 	"trulyOptionalDependencies": { | ||||
| 		"http-proxy": "^1.17.0", | ||||
| 		"express": "^4.16.3", | ||||
| 		"express-basic-auth": "^1.2.0", | ||||
| 		"finalhandler": "^1.1.1", | ||||
| 		"serve-index": "^1.9.1", | ||||
| 		"serve-static": "^1.13.2", | ||||
| 		"ws": "^5.2.1" | ||||
| 	}, | ||||
| 	"devDependencies": {}, | ||||
| 	"repository": { | ||||
| 		"type": "git", | ||||
| 		"url": "https://git.rootprojects.org/root/greenlock-express.js.git" | ||||
| 	}, | ||||
| 	"keywords": [ | ||||
| 		"Let's Encrypt", | ||||
| 		"ACME", | ||||
| 		"greenlock", | ||||
| 		"Free SSL", | ||||
| 		"Automated HTTPS", | ||||
| 		"https", | ||||
| 		"tls" | ||||
| 	], | ||||
| 	"author": "AJ ONeal <coolaj86@gmail.com> (https://solderjs.com/)", | ||||
| 	"license": "MPL-2.0", | ||||
| 	"bugs": { | ||||
| 		"url": "https://git.rootprojects.org/root/greenlock-express.js/issues" | ||||
| 	} | ||||
|   "name": "letsencrypt-express", | ||||
|   "version": "1.2.0", | ||||
|   "description": "Free SSL and Automatic HTTPS for node.js with Express, Connect, and other middleware systems", | ||||
|   "main": "index.js", | ||||
|   "bin": { | ||||
|     "letsencrypt-express": "bin/lex.js", | ||||
|     "lex": "bin/lex.js" | ||||
|   }, | ||||
|   "files": [ | ||||
|     "lib/", | ||||
|     "bin/", | ||||
|     "examples/", | ||||
|     "index.js" | ||||
|   ], | ||||
|   "directories": { | ||||
|     "example": "examples" | ||||
|   }, | ||||
|   "scripts": { | ||||
|     "test": "echo \"Error: no test specified\" && exit 1" | ||||
|   }, | ||||
|   "repository": { | ||||
|     "type": "git", | ||||
|     "url": "git+https://github.com/Daplie/letsencrypt-express.git" | ||||
|   }, | ||||
|   "keywords": [ | ||||
|     "acme", | ||||
|     "free", | ||||
|     "ssl", | ||||
|     "tls", | ||||
|     "https", | ||||
|     "letsencrypt", | ||||
|     "le", | ||||
|     "boulder", | ||||
|     "express", | ||||
|     "expressjs", | ||||
|     "connect", | ||||
|     "middleware" | ||||
|   ], | ||||
|   "author": "AJ ONeal <coolaj86@gmail.com> (https://coolaj86.com/)", | ||||
|   "license": "(MIT OR Apache-2.0)", | ||||
|   "bugs": { | ||||
|     "url": "https://github.com/Daplie/letsencrypt-express/issues" | ||||
|   }, | ||||
|   "homepage": "https://github.com/Daplie/letsencrypt-express#readme", | ||||
|   "devDependencies": { | ||||
|     "body-parser": "^1.14.2", | ||||
|     "cli": "^0.11.1", | ||||
|     "express": "^4.13.3" | ||||
|   }, | ||||
|   "dependencies": { | ||||
|     "homedir": "^0.6.0", | ||||
|     "letsencrypt": "^1.5.0", | ||||
|     "localhost.daplie.com-certificates": "^1.1.2", | ||||
|     "mkdirp": "^0.5.1" | ||||
|   } | ||||
| } | ||||
|  | ||||
| @ -1,77 +0,0 @@ | ||||
| #!/usr/bin/env node | ||||
| "use strict"; | ||||
| 
 | ||||
| // BG WH \u001b[47m | ||||
| // BOLD  \u001b[1m | ||||
| // RED   \u001b[31m | ||||
| // GREEN \u001b[32m | ||||
| // RESET \u001b[0m | ||||
| 
 | ||||
| var grabbers = [ | ||||
| 	[ | ||||
| 		"", | ||||
| 		"================================================================================", | ||||
| 		"", | ||||
| 		" 🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥", | ||||
| 		"🔥                            🔥", | ||||
| 		"🔥  Do you rely on Greenlock? 🔥", | ||||
| 		"🔥                            🔥", | ||||
| 		" 🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥" | ||||
| 	], | ||||
| 
 | ||||
| 	[ | ||||
| 		"", | ||||
| 		"================================================================================", | ||||
| 		"", | ||||
| 		" 🍒🍒🍒🍒🍒🍒🍒🍒🍒🍒🍒🍒🍒🍒🍒🍒", | ||||
| 		"🍒                              🍒", | ||||
| 		"🍒  Do you rely on Greenlock?   🍒", | ||||
| 		"🍒                              🍒", | ||||
| 		" 🍒🍒🍒🍒🍒🍒🍒🍒🍒🍒🍒🍒🍒🍒🍒🍒" | ||||
| 	], | ||||
| 
 | ||||
| 	[ | ||||
| 		"", | ||||
| 		"================================================================================", | ||||
| 		"", | ||||
| 		" 👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇", | ||||
| 		"👉                             👈", | ||||
| 		"👉  Do you rely on Greenlock?  👈", | ||||
| 		"👉                             👈", | ||||
| 		" 👆👆👆👆👆👆👆👆👆👆👆👆👆👆👆 " | ||||
| 	], | ||||
| 
 | ||||
| 	[ | ||||
| 		"", | ||||
| 		"================================================================================", | ||||
| 		"", | ||||
| 		" 👀 👀 👀 👀 👀 👀 👀 👀 👀 👀 👀 ", | ||||
| 		"👀                              👀", | ||||
| 		"👀  Do you rely on Greenlock?   👀", | ||||
| 		"👀                              👀", | ||||
| 		" 👀 👀 👀 👀 👀 👀 👀 👀 👀 👀 👀 ", | ||||
| 	] | ||||
| ]; | ||||
| 
 | ||||
| setTimeout(function() { | ||||
| 	grabbers[Math.floor(Math.random() * grabbers.length)].concat([ | ||||
| 		"", | ||||
| 		"Hey! Let's Encrypt will \u001b[31mSTOP WORKING\u001b[0m with Greenlock v2 at the end of October,", | ||||
| 		"and \u001b[31mWITHOUT YOUR HELP\u001b[0m we won't get the next release out in time.", | ||||
| 		"", | ||||
| 		"If Greenlock has saved you time and money, and taken stress out of your life,", | ||||
| 		"or you just love it, please reach out to return the favor today:", | ||||
| 		"", | ||||
| 		"\u001b[31mSAVE GREENLOCK:\u001b[0m", | ||||
| 		"https://indiegogo.com/at/greenlock", | ||||
| 		"", | ||||
| 		"================================================================================", | ||||
| 		"" | ||||
| 	]).forEach(function(line) { | ||||
| 		console.info(line); | ||||
| 	}); | ||||
| }, 300); | ||||
| 
 | ||||
| setTimeout(function() { | ||||
| 	// give time to read | ||||
| }, 1500); | ||||
							
								
								
									
										157
									
								
								servers.js
									
									
									
									
									
								
							
							
						
						
									
										157
									
								
								servers.js
									
									
									
									
									
								
							| @ -1,157 +0,0 @@ | ||||
| "use strict"; | ||||
| 
 | ||||
| var Servers = module.exports; | ||||
| 
 | ||||
| var http = require("http"); | ||||
| var HttpMiddleware = require("./http-middleware.js"); | ||||
| var HttpsMiddleware = require("./https-middleware.js"); | ||||
| var sni = require("./sni.js"); | ||||
| var cluster = require("cluster"); | ||||
| 
 | ||||
| Servers.create = function(greenlock) { | ||||
| 	var servers = {}; | ||||
| 	var _httpServer; | ||||
| 	var _httpsServer; | ||||
| 
 | ||||
| 	function startError(e) { | ||||
| 		explainError(e); | ||||
| 		process.exit(1); | ||||
| 	} | ||||
| 
 | ||||
| 	servers.httpServer = function(defaultApp) { | ||||
| 		if (_httpServer) { | ||||
| 			return _httpServer; | ||||
| 		} | ||||
| 
 | ||||
| 		_httpServer = http.createServer(HttpMiddleware.create(greenlock, defaultApp)); | ||||
| 		_httpServer.once("error", startError); | ||||
| 
 | ||||
| 		return _httpServer; | ||||
| 	}; | ||||
| 
 | ||||
| 	var _middlewareApp; | ||||
| 
 | ||||
| 	servers.httpsServer = function(secureOpts, defaultApp) { | ||||
| 		if (defaultApp) { | ||||
| 			// TODO guard against being set twice?
 | ||||
| 			_middlewareApp = defaultApp; | ||||
| 		} | ||||
| 
 | ||||
| 		if (_httpsServer) { | ||||
| 			if (secureOpts && Object.keys(secureOpts).length) { | ||||
| 				throw new Error("Call glx.httpsServer(tlsOptions) before calling glx.serveApp(app)"); | ||||
| 			} | ||||
| 			return _httpsServer; | ||||
| 		} | ||||
| 
 | ||||
| 		if (!secureOpts) { | ||||
| 			secureOpts = {}; | ||||
| 		} | ||||
| 
 | ||||
| 		_httpsServer = createSecureServer( | ||||
| 			wrapDefaultSniCallback(greenlock, secureOpts), | ||||
| 			HttpsMiddleware.create(greenlock, function(req, res) { | ||||
| 				if (!_middlewareApp) { | ||||
| 					throw new Error("Set app with `glx.serveApp(app)` or `glx.httpsServer(tlsOptions, app)`"); | ||||
| 				} | ||||
| 				_middlewareApp(req, res); | ||||
| 			}) | ||||
| 		); | ||||
| 		_httpsServer.once("error", startError); | ||||
| 
 | ||||
| 		return _httpsServer; | ||||
| 	}; | ||||
| 
 | ||||
| 	servers.id = function() { | ||||
| 		return (cluster.isWorker && cluster.worker.id) || "0"; | ||||
| 	}; | ||||
| 	servers.serveApp = function(app) { | ||||
| 		return new Promise(function(resolve, reject) { | ||||
| 			if ("function" !== typeof app) { | ||||
| 				reject(new Error("glx.serveApp(app) expects a node/express app in the format `function (req, res) { ... }`")); | ||||
| 				return; | ||||
| 			} | ||||
| 
 | ||||
| 			var id = cluster.isWorker && cluster.worker.id; | ||||
| 			var idstr = (id && "#" + id + " ") || ""; | ||||
| 			var plainServer = servers.httpServer(require("redirect-https")()); | ||||
| 			var plainAddr = "0.0.0.0"; | ||||
| 			var plainPort = 80; | ||||
| 			plainServer.listen(plainPort, plainAddr, function() { | ||||
| 				console.info( | ||||
| 					idstr + "Listening on", | ||||
| 					plainAddr + ":" + plainPort, | ||||
| 					"for ACME challenges, and redirecting to HTTPS" | ||||
| 				); | ||||
| 
 | ||||
| 				// TODO fetch greenlock.servername
 | ||||
| 				_middlewareApp = app || _middlewareApp; | ||||
| 				var secureServer = servers.httpsServer(null, app); | ||||
| 				var secureAddr = "0.0.0.0"; | ||||
| 				var securePort = 443; | ||||
| 				secureServer.listen(securePort, secureAddr, function() { | ||||
| 					console.info(idstr + "Listening on", secureAddr + ":" + securePort, "for secure traffic"); | ||||
| 
 | ||||
| 					plainServer.removeListener("error", startError); | ||||
| 					secureServer.removeListener("error", startError); | ||||
| 					resolve(); | ||||
| 				}); | ||||
| 			}); | ||||
| 		}); | ||||
| 	}; | ||||
| 
 | ||||
| 	return servers; | ||||
| }; | ||||
| 
 | ||||
| function explainError(e) { | ||||
| 	console.error(); | ||||
| 	console.error("Error: " + e.message); | ||||
| 	if ("EACCES" === e.errno) { | ||||
| 		console.error("You don't have prmission to access '" + e.address + ":" + e.port + "'."); | ||||
| 		console.error('You probably need to use "sudo" or "sudo setcap \'cap_net_bind_service=+ep\' $(which node)"'); | ||||
| 	} else if ("EADDRINUSE" === e.errno) { | ||||
| 		console.error("'" + e.address + ":" + e.port + "' is already being used by some other program."); | ||||
| 		console.error("You probably need to stop that program or restart your computer."); | ||||
| 	} else { | ||||
| 		console.error(e.code + ": '" + e.address + ":" + e.port + "'"); | ||||
| 	} | ||||
| 	console.error(); | ||||
| } | ||||
| 
 | ||||
| function wrapDefaultSniCallback(greenlock, secureOpts) { | ||||
| 	// I'm not sure yet if the original SNICallback
 | ||||
| 	// should be called before or after, so I'm just
 | ||||
| 	// going to delay making that choice until I have the use case
 | ||||
| 	/* | ||||
| 		if (!secureOpts.SNICallback) { | ||||
| 			secureOpts.SNICallback = function(servername, cb) { | ||||
| 				cb(null, null); | ||||
| 			}; | ||||
| 		} | ||||
|   */ | ||||
| 	if (secureOpts.SNICallback) { | ||||
| 		console.warn(); | ||||
| 		console.warn("[warning] Ignoring the given tlsOptions.SNICallback function."); | ||||
| 		console.warn(); | ||||
| 		console.warn("          We're very open to implementing support for this,"); | ||||
| 		console.warn("          we just don't understand the use case yet."); | ||||
| 		console.warn("          Please open an issue to discuss. We'd love to help."); | ||||
| 		console.warn(); | ||||
| 	} | ||||
| 
 | ||||
| 	// TODO greenlock.servername for workers
 | ||||
| 	secureOpts.SNICallback = sni.create(greenlock, secureOpts); | ||||
| 	return secureOpts; | ||||
| } | ||||
| 
 | ||||
| function createSecureServer(secureOpts, fn) { | ||||
| 	var major = process.versions.node.split(".")[0]; | ||||
| 
 | ||||
| 	// TODO can we trust earlier versions as well?
 | ||||
| 	if (major >= 12) { | ||||
| 		secureOpts.allowHTTP1 = true; | ||||
| 		return require("http2").createSecureServer(secureOpts, fn); | ||||
| 	} else { | ||||
| 		return require("https").createServer(secureOpts, fn); | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										25
									
								
								single.js
									
									
									
									
									
								
							
							
						
						
									
										25
									
								
								single.js
									
									
									
									
									
								
							| @ -1,25 +0,0 @@ | ||||
| "use strict"; | ||||
| 
 | ||||
| require("./main.js"); | ||||
| 
 | ||||
| var Single = module.exports; | ||||
| var Servers = require("./servers.js"); | ||||
| 
 | ||||
| Single.create = function(opts) { | ||||
| 	var greenlock = require("./greenlock.js").create(opts); | ||||
| 
 | ||||
| 	var servers = Servers.create(greenlock); | ||||
| 
 | ||||
| 	var single = { | ||||
| 		serve: function(fn) { | ||||
| 			fn(servers); | ||||
| 			return single; | ||||
| 		}, | ||||
| 		master: function(/*fn*/) { | ||||
| 			// ignore
 | ||||
| 			//fn(master);
 | ||||
| 			return single; | ||||
| 		} | ||||
| 	}; | ||||
| 	return single; | ||||
| }; | ||||
							
								
								
									
										194
									
								
								sni.js
									
									
									
									
									
								
							
							
						
						
									
										194
									
								
								sni.js
									
									
									
									
									
								
							| @ -1,194 +0,0 @@ | ||||
| "use strict"; | ||||
| 
 | ||||
| var sni = module.exports; | ||||
| var tls = require("tls"); | ||||
| var servernameRe = /^[a-z0-9\.\-]+$/i; | ||||
| 
 | ||||
| // a nice, round, irrational number - about every 6¼ hours
 | ||||
| var refreshOffset = Math.round(Math.PI * 2 * (60 * 60 * 1000)); | ||||
| // and another, about 15 minutes
 | ||||
| var refreshStagger = Math.round(Math.PI * 5 * (60 * 1000)); | ||||
| // and another, about 30 seconds
 | ||||
| var smallStagger = Math.round(Math.PI * (30 * 1000)); | ||||
| 
 | ||||
| //secureOpts.SNICallback = sni.create(greenlock, secureOpts);
 | ||||
| sni.create = function(greenlock, secureOpts) { | ||||
| 	var _cache = {}; | ||||
| 	var defaultServername = greenlock.servername || ""; | ||||
| 
 | ||||
| 	if (secureOpts.cert) { | ||||
| 		// Note: it's fine if greenlock.servername is undefined,
 | ||||
| 		// but if the caller wants this to auto-renew, they should define it
 | ||||
| 		_cache[defaultServername] = { | ||||
| 			refreshAt: 0, | ||||
| 			secureContext: tls.createSecureContext(secureOpts) | ||||
| 		}; | ||||
| 	} | ||||
| 
 | ||||
| 	return getSecureContext; | ||||
| 
 | ||||
| 	function notify(ev, args) { | ||||
| 		try { | ||||
| 			// TODO _notify() or notify()?
 | ||||
| 			(greenlock.notify || greenlock._notify)(ev, args); | ||||
| 		} catch (e) { | ||||
| 			console.error(e); | ||||
| 			console.error(ev, args); | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	function getSecureContext(servername, cb) { | ||||
| 		//console.log("debug sni", servername);
 | ||||
| 		if ("string" !== typeof servername) { | ||||
| 			// this will never happen... right? but stranger things have...
 | ||||
| 			console.error("[sanity fail] non-string servername:", servername); | ||||
| 			cb(new Error("invalid servername"), null); | ||||
| 			return; | ||||
| 		} | ||||
| 
 | ||||
| 		var secureContext = getCachedContext(servername); | ||||
| 		if (secureContext) { | ||||
| 			//console.log("debug sni got cached context", servername, getCachedMeta(servername));
 | ||||
| 			cb(null, secureContext); | ||||
| 			return; | ||||
| 		} | ||||
| 
 | ||||
| 		getFreshContext(servername) | ||||
| 			.then(function(secureContext) { | ||||
| 				if (secureContext) { | ||||
| 					//console.log("debug sni got fresh context", servername, getCachedMeta(servername));
 | ||||
| 					cb(null, secureContext); | ||||
| 					return; | ||||
| 				} | ||||
| 				// Note: this does not replace tlsSocket.setSecureContext()
 | ||||
| 				// as it only works when SNI has been sent
 | ||||
| 				//console.log("debug sni got default context", servername, getCachedMeta(servername));
 | ||||
| 				cb(null, getDefaultContext()); | ||||
| 			}) | ||||
| 			.catch(function(err) { | ||||
| 				if (!err.context) { | ||||
| 					err.context = "sni_callback"; | ||||
| 				} | ||||
| 				notify("error", err); | ||||
| 				//console.log("debug sni error", servername, err);
 | ||||
| 				cb(err); | ||||
| 			}); | ||||
| 	} | ||||
| 
 | ||||
| 	function getCachedMeta(servername) { | ||||
| 		var meta = _cache[servername]; | ||||
| 		if (!meta) { | ||||
| 			if (!_cache[wildname(servername)]) { | ||||
| 				return null; | ||||
| 			} | ||||
| 		} | ||||
| 		return meta; | ||||
| 	} | ||||
| 
 | ||||
| 	function getCachedContext(servername) { | ||||
| 		var meta = getCachedMeta(servername); | ||||
| 		if (!meta) { | ||||
| 			return null; | ||||
| 		} | ||||
| 
 | ||||
| 		// always renew in background
 | ||||
| 		if (!meta.refreshAt || Date.now() >= meta.refreshAt) { | ||||
| 			getFreshContext(servername).catch(function(e) { | ||||
| 				if (!e.context) { | ||||
| 					e.context = "sni_background_refresh"; | ||||
| 				} | ||||
| 				notify("error", e); | ||||
| 			}); | ||||
| 		} | ||||
| 
 | ||||
| 		// under normal circumstances this would never be expired
 | ||||
| 		// and, if it is expired, something is so wrong it's probably
 | ||||
| 		// not worth wating for the renewal - it has probably failed
 | ||||
| 		return meta.secureContext; | ||||
| 	} | ||||
| 
 | ||||
| 	function getFreshContext(servername) { | ||||
| 		var meta = getCachedMeta(servername); | ||||
| 		if (!meta && !validServername(servername)) { | ||||
| 			return Promise.resolve(null); | ||||
| 		} | ||||
| 
 | ||||
| 		if (meta) { | ||||
| 			// prevent stampedes
 | ||||
| 			meta.refreshAt = Date.now() + randomRefreshOffset(); | ||||
| 		} | ||||
| 
 | ||||
| 		// TODO don't get unknown certs at all, rely on auto-updates from greenlock
 | ||||
| 		// Note: greenlock.get() will return an existing fresh cert or issue a new one
 | ||||
| 		return greenlock.get({ servername: servername }).then(function(result) { | ||||
| 			var meta = getCachedMeta(servername); | ||||
| 			if (!meta) { | ||||
| 				meta = _cache[servername] = { secureContext: { _valid: false } }; | ||||
| 			} | ||||
| 			// prevent from being punked by bot trolls
 | ||||
| 			meta.refreshAt = Date.now() + smallStagger; | ||||
| 
 | ||||
| 			// nothing to do
 | ||||
| 			if (!result) { | ||||
| 				return null; | ||||
| 			} | ||||
| 
 | ||||
| 			// we only care about the first one
 | ||||
| 			var pems = result.pems; | ||||
| 			var site = result.site; | ||||
| 			if (!pems || !pems.cert) { | ||||
| 				// nothing to do
 | ||||
| 				// (and the error should have been reported already)
 | ||||
| 				return null; | ||||
| 			} | ||||
| 
 | ||||
| 			meta = { | ||||
| 				refreshAt: Date.now() + randomRefreshOffset(), | ||||
| 				secureContext: tls.createSecureContext({ | ||||
| 					// TODO support passphrase-protected privkeys
 | ||||
| 					key: pems.privkey, | ||||
| 					cert: pems.cert + "\n" + pems.chain + "\n" | ||||
| 				}) | ||||
| 			}; | ||||
| 			meta.secureContext._valid = true; | ||||
| 
 | ||||
| 			// copy this same object into every place
 | ||||
| 			(result.altnames || site.altnames || [result.subject || site.subject]).forEach(function(altname) { | ||||
| 				_cache[altname] = meta; | ||||
| 			}); | ||||
| 
 | ||||
| 			return meta.secureContext; | ||||
| 		}); | ||||
| 	} | ||||
| 
 | ||||
| 	function getDefaultContext() { | ||||
| 		return getCachedContext(defaultServername); | ||||
| 	} | ||||
| }; | ||||
| 
 | ||||
| // whenever we need to know when to refresh next
 | ||||
| function randomRefreshOffset() { | ||||
| 	var stagger = Math.round(refreshStagger / 2) - Math.round(Math.random() * refreshStagger); | ||||
| 	return refreshOffset + stagger; | ||||
| } | ||||
| 
 | ||||
| function validServername(servername) { | ||||
| 	// format and (lightly) sanitize sni so that users can be naive
 | ||||
| 	// and not have to worry about SQL injection or fs discovery
 | ||||
| 
 | ||||
| 	servername = (servername || "").toLowerCase(); | ||||
| 	// hostname labels allow a-z, 0-9, -, and are separated by dots
 | ||||
| 	// _ is sometimes allowed, but not as a "hostname", and not by Let's Encrypt ACME
 | ||||
| 	// REGEX // https://www.codeproject.com/Questions/1063023/alphanumeric-validation-javascript-without-regex
 | ||||
| 	return servernameRe.test(servername) && -1 === servername.indexOf(".."); | ||||
| } | ||||
| 
 | ||||
| function wildname(servername) { | ||||
| 	return ( | ||||
| 		"*." + | ||||
| 		servername | ||||
| 			.split(".") | ||||
| 			.slice(1) | ||||
| 			.join(".") | ||||
| 	); | ||||
| } | ||||
| @ -1,85 +0,0 @@ | ||||
| #!/usr/bin/env node
 | ||||
| var Greenlock = require("../"); | ||||
| var greenlock = Greenlock.create({ | ||||
| 	version: "draft-11", | ||||
| 	server: "https://acme-staging-v02.api.letsencrypt.org/directory", | ||||
| 	agreeTos: true, | ||||
| 	approvedDomains: ["example.com", "www.example.com"], | ||||
| 	configDir: require("path").join(require("os").tmpdir(), "acme"), | ||||
| 
 | ||||
| 	app: require("express")().use("/", function(req, res) { | ||||
| 		res.setHeader("Content-Type", "text/html; charset=utf-8"); | ||||
| 		res.end("Hello, World!\n\n💚 🔒.js"); | ||||
| 	}) | ||||
| }); | ||||
| 
 | ||||
| var server1 = greenlock.listen(5080, 5443); | ||||
| server1.on("listening", function() { | ||||
| 	console.log("### THREE 3333 - All is well server1", this.address()); | ||||
| 	setTimeout(function() { | ||||
| 		// so that the address() object doesn't disappear
 | ||||
| 		server1.close(); | ||||
| 		server1.unencrypted.close(); | ||||
| 	}, 10); | ||||
| }); | ||||
| setTimeout(function() { | ||||
| 	var server2 = greenlock.listen(6080, 6443, function() { | ||||
| 		console.log("### FIVE 55555 - Started server 2!"); | ||||
| 		setTimeout(function() { | ||||
| 			server2.close(); | ||||
| 			server2.unencrypted.close(); | ||||
| 			server6.close(); | ||||
| 			server6.unencrypted.close(); | ||||
| 			server7.close(); | ||||
| 			server7.unencrypted.close(); | ||||
| 			setTimeout(function() { | ||||
| 				// TODO greenlock needs a close event (and to listen to its server's close event)
 | ||||
| 				process.exit(0); | ||||
| 			}, 1000); | ||||
| 		}, 1000); | ||||
| 	}); | ||||
| 	server2.on("listening", function() { | ||||
| 		console.log("### FOUR 44444 - All is well server2", server2.address()); | ||||
| 	}); | ||||
| }, 1000); | ||||
| 
 | ||||
| var server3 = greenlock.listen( | ||||
| 	22, | ||||
| 	22, | ||||
| 	function() { | ||||
| 		console.error("Error: expected to get an error when launching plain server on port 22"); | ||||
| 	}, | ||||
| 	function() { | ||||
| 		console.error("Error: expected to get an error when launching " + server3.type + " server on port 22"); | ||||
| 	} | ||||
| ); | ||||
| server3.unencrypted.on("error", function() { | ||||
| 	console.log("Success: caught expected (plain) error"); | ||||
| }); | ||||
| server3.on("error", function() { | ||||
| 	console.log("Success: caught expected " + server3.type + " error"); | ||||
| 	//server3.close();
 | ||||
| }); | ||||
| 
 | ||||
| var server4 = greenlock.listen( | ||||
| 	7080, | ||||
| 	7443, | ||||
| 	function() { | ||||
| 		console.log("Success: server4: plain"); | ||||
| 		server4.unencrypted.close(); | ||||
| 	}, | ||||
| 	function() { | ||||
| 		console.log("Success: server4: " + server4.type); | ||||
| 		server4.close(); | ||||
| 	} | ||||
| ); | ||||
| 
 | ||||
| var server5 = greenlock.listen(10080, 10443, function() { | ||||
| 	console.log("Server 5 with one fn", this.address()); | ||||
| 	server5.close(); | ||||
| 	server5.unencrypted.close(); | ||||
| }); | ||||
| 
 | ||||
| var server6 = greenlock.listen("[::]:11080", "[::1]:11443"); | ||||
| 
 | ||||
| var server7 = greenlock.listen("/tmp/gl.plain.sock", "/tmp/gl.sec.sock"); | ||||
							
								
								
									
										62
									
								
								worker.js
									
									
									
									
									
								
							
							
						
						
									
										62
									
								
								worker.js
									
									
									
									
									
								
							| @ -1,62 +0,0 @@ | ||||
| "use strict"; | ||||
| 
 | ||||
| var Worker = module.exports; | ||||
| // *very* generous, but well below the http norm of 120
 | ||||
| var messageTimeout = 30 * 1000; | ||||
| var msgPrefix = "greenlock:"; | ||||
| 
 | ||||
| Worker.create = function() { | ||||
| 	var greenlock = {}; | ||||
| 	["getAcmeHttp01ChallengeResponse", "get", "notify"].forEach(function(k) { | ||||
| 		greenlock[k] = function(args) { | ||||
| 			return rpc(k, args); | ||||
| 		}; | ||||
| 	}); | ||||
| 
 | ||||
| 	var worker = { | ||||
| 		serve: function(fn) { | ||||
| 			var servers = require("./servers.js").create(greenlock); | ||||
| 			fn(servers); | ||||
| 			return worker; | ||||
| 		}, | ||||
| 		master: function() { | ||||
| 			// ignore
 | ||||
| 			return worker; | ||||
| 		} | ||||
| 	}; | ||||
| 	return worker; | ||||
| }; | ||||
| 
 | ||||
| function rpc(funcname, msg) { | ||||
| 	return new Promise(function(resolve, reject) { | ||||
| 		var rnd = Math.random() | ||||
| 			.toString() | ||||
| 			.slice(2) | ||||
| 			.toString(16); | ||||
| 		var id = msgPrefix + rnd; | ||||
| 		var timeout; | ||||
| 
 | ||||
| 		function getResponse(msg) { | ||||
| 			if (msg._id !== id) { | ||||
| 				return; | ||||
| 			} | ||||
| 			process.removeListener("message", getResponse); | ||||
| 			clearTimeout(timeout); | ||||
| 			resolve(msg._result); | ||||
| 		} | ||||
| 
 | ||||
| 		// TODO keep a single listener than just responds
 | ||||
| 		// via a collection of callbacks? or leave as is?
 | ||||
| 		process.on("message", getResponse); | ||||
| 		process.send({ | ||||
| 			_id: id, | ||||
| 			_funcname: funcname, | ||||
| 			_input: msg | ||||
| 		}); | ||||
| 
 | ||||
| 		timeout = setTimeout(function() { | ||||
| 			process.removeListener("message", getResponse); | ||||
| 			reject(new Error("worker rpc request timeout")); | ||||
| 		}, messageTimeout); | ||||
| 	}); | ||||
| } | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user