@cocreate/acme 1.5.0 → 1.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/automated.yml +1 -1
- package/CHANGELOG.md +7 -0
- package/README.md +83 -58
- package/package.json +13 -10
- package/release.config.js +1 -1
- package/src/index.js +8 -6
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
## [1.5.1](https://github.com/CoCreate-app/CoCreate-acme/compare/v1.5.0...v1.5.1) (2026-07-19)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* set default branch to main ([383f061](https://github.com/CoCreate-app/CoCreate-acme/commit/383f061a286d5f0016c33c28acca72c79af3edf9))
|
|
7
|
+
|
|
1
8
|
# [1.5.0](https://github.com/CoCreate-app/CoCreate-acme/compare/v1.4.1...v1.5.0) (2026-07-19)
|
|
2
9
|
|
|
3
10
|
|
package/README.md
CHANGED
|
@@ -1,97 +1,122 @@
|
|
|
1
|
-
|
|
1
|
+
Here is the accurately updated, tailored README for `@cocreate/acme` based directly on your provided source code implementation. It reflects your exact class signatures, custom validation mechanics (including the randomized stagger variables), filesystem structure (`/etc/certificates`), and your native `this.crud` database/file injection flows.
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
# @cocreate/acme
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-

|
|
7
|
-

|
|
8
|
-

|
|
9
|
-

|
|
10
|
-

|
|
5
|
+
An automated, multi-tenant ACME (Automated Certificate Management Environment) client and automated SSL/TLS provisioning wrapper built on top of `acme-client`. This package coordinates programmatic certificate ordering via Let's Encrypt, maps file validation challenges natively into your cluster using your specialized `crud.send()` engine, and provisions live keys to `/etc/certificates/` before dynamically updating running proxy boundaries.
|
|
11
6
|
|
|
12
|
-
|
|
7
|
+
---
|
|
13
8
|
|
|
14
|
-
|
|
15
|
-
- **Direct API Access within Applications**: Seamlessly integrates with your application, providing an accessible API for on-the-fly certificate management, ideal for dynamic and distributed environments.
|
|
16
|
-
- **Scalable for Distributed Applications**: Engineered to support the unique needs of distributed applications, ensuring secure, scalable certificate provisioning.
|
|
9
|
+
## Table of Contents
|
|
17
10
|
|
|
18
|
-
|
|
11
|
+
* [Features](#features)
|
|
12
|
+
* [Installation](#installation)
|
|
13
|
+
* [Usage](#usage)
|
|
14
|
+
* [How it Works](#how-it-works)
|
|
15
|
+
* [API Reference](#api-reference)
|
|
16
|
+
* [How to Contribute](#how-to-contribute)
|
|
17
|
+
* [License](#license)
|
|
19
18
|
|
|
20
|
-
|
|
19
|
+
---
|
|
21
20
|
|
|
22
|
-
|
|
21
|
+
## Features
|
|
23
22
|
|
|
24
|
-
|
|
23
|
+
* **Automated Account & Key Creation:** Gracefully initializes `/etc/certificates`, provisions or loads an asymmetric `account.pem` key profile, and dynamically registers newly generated account contexts with the ACME Directory.
|
|
24
|
+
* **Reactive Multi-Node Challenge Handling:** Writes `http-01` challenge validation payloads natively directly into persistent network collection tracks (`array: "files"`), enabling external verification processes to easily query and clear challenges on any active cluster node.
|
|
25
|
+
* **Staggered Expiration Guards:** Leverages unique math-random constants (`DAYS`, `HOURS`, `MINUTES`) across server instances to prevent simultaneous, blocking stampedes when scanning and renewing multi-tenant custom domains.
|
|
26
|
+
* **Inline Local Filesystem Provisioning:** Auto-writes validated credentials (`fullchain.pem` and `private-key.pem`) into organized, host-specific directories right inside `/etc/certificates/{host}/`.
|
|
27
|
+
* **Hot Proxy Re-Initialization:** Calls hot proxy callbacks (`this.proxy.createServer(host)`) immediately upon successful token generation loops, refreshing SSL profiles without requiring master process reboots.
|
|
25
28
|
|
|
26
|
-
|
|
27
|
-
<script src="https://cdn.cocreate.app/acme/latest/CoCreate-acme.min.js"></script>
|
|
28
|
-
```
|
|
29
|
+
---
|
|
29
30
|
|
|
30
|
-
|
|
31
|
-
<script src="https://cdn.cocreate.app/acme/latest/CoCreate-acme.min.css"></script>
|
|
32
|
-
```
|
|
31
|
+
## Installation
|
|
33
32
|
|
|
34
|
-
|
|
33
|
+
```bash
|
|
34
|
+
npm install @cocreate/acme
|
|
35
35
|
|
|
36
|
-
```shell
|
|
37
|
-
$ npm i @cocreate/acme
|
|
38
36
|
```
|
|
39
37
|
|
|
40
|
-
|
|
38
|
+
---
|
|
41
39
|
|
|
42
|
-
|
|
43
|
-
$ yarn install @cocreate/acme
|
|
44
|
-
```
|
|
40
|
+
## Usage
|
|
45
41
|
|
|
46
|
-
|
|
42
|
+
### Class Initialization
|
|
47
43
|
|
|
48
|
-
|
|
49
|
-
- [Announcements](#announcements)
|
|
50
|
-
- [Roadmap](#roadmap)
|
|
51
|
-
- [How to Contribute](#how-to-contribute)
|
|
52
|
-
- [About](#about)
|
|
53
|
-
- [License](#license)
|
|
44
|
+
Inject your cluster's routing `proxy` interface and core database `crud` middleware service layer right into the instance constructor context:
|
|
54
45
|
|
|
55
|
-
|
|
46
|
+
```javascript
|
|
47
|
+
const CoCreateAcme = require('@cocreate/acme');
|
|
56
48
|
|
|
57
|
-
|
|
49
|
+
const proxyInstance = {
|
|
50
|
+
createServer: (host) => console.log(`Hot-reloaded server certificates for: ${host}`)
|
|
51
|
+
};
|
|
58
52
|
|
|
59
|
-
|
|
53
|
+
const crudInstance = {
|
|
54
|
+
send: async (payload) => { /* ... handling database syncing ... */ },
|
|
55
|
+
getOrganization: async ({ host, organization_id }) => { /* ... fetches organization document ... */ }
|
|
56
|
+
};
|
|
60
57
|
|
|
61
|
-
|
|
58
|
+
// Initializes paths, sets up permissions, loads/creates account.pem, and sets up Let's Encrypt connection
|
|
59
|
+
const acmeManager = new CoCreateAcme(proxyInstance, crudInstance);
|
|
62
60
|
|
|
63
|
-
|
|
61
|
+
```
|
|
64
62
|
|
|
65
|
-
|
|
63
|
+
### Checking and Provisioning Certificates Dynamically
|
|
66
64
|
|
|
67
|
-
|
|
65
|
+
Intercept traffic requests or cron loops natively, evaluating if a domain possesses valid, active SSL coverage or requires a zero-friction automated provisioning lifecycle:
|
|
68
66
|
|
|
69
|
-
|
|
67
|
+
```javascript
|
|
68
|
+
const targetHost = "my-custom-domain.app.cocreate.js";
|
|
69
|
+
const orgId = "652c8d62679eca03e0b116a7";
|
|
70
70
|
|
|
71
|
-
|
|
71
|
+
// Bypasses localhost/127.0.0.1, scans local caches, validates file states, or auto-provisions a new bundle
|
|
72
|
+
const operational = await acmeManager.checkCertificate(targetHost, orgId);
|
|
72
73
|
|
|
73
|
-
|
|
74
|
+
if (operational) {
|
|
75
|
+
console.log(`Secure gateway communication channels verified for ${targetHost}`);
|
|
76
|
+
} else {
|
|
77
|
+
console.log(`Failed to configure or provision certificates for ${targetHost}`);
|
|
78
|
+
}
|
|
74
79
|
|
|
75
|
-
|
|
80
|
+
```
|
|
76
81
|
|
|
77
|
-
|
|
82
|
+
---
|
|
78
83
|
|
|
79
|
-
|
|
84
|
+
## How it Works
|
|
80
85
|
|
|
81
|
-
|
|
86
|
+
1. **Environmental Bootstrap:** On launch, `init()` issues root command scripts (`sudo mkdir` / `sudo chmod`) to establish a shared space at `/etc/certificates`. It scans for `account.pem`; if missing, it spawns a fresh private key profile via `forge.createPrivateKey()` and calls Let's Encrypt (`client.createAccount`) to sign the structural Terms of Service agreements.
|
|
87
|
+
2. **Pre-Screening Verification:** `checkCertificate()` bypasses checking entirely if the host resolves to localized loops or internal incoming routing markers (`/.well-known/acme-challenge/`). It inspects internal state hashes (`certificates[host]`); if vacant, it checks `/etc/certificates/{host}/fullchain.pem` directly from disks.
|
|
88
|
+
3. **Staggered Lifespan Validation:** `isValid()` assesses whether a certificate has entered its expiration horizon. By computing a dynamic random constant combination (`DAYS` + `HOURS` + `MINUTES`), the lifespan window varies up to 7 days per instance, ensuring multi-node platforms distribute renewal requests randomly over time.
|
|
89
|
+
4. **Distributed Challenge Injection:** If certificates are expired or missing, `getCertificate()` calls `requestCertificate()`. When the `client.auto()` handshake processes tokens, `challengeCreateFn` hooks fire. It pipes a structure using `this.crud.send()` with a method payload targeting `object.create`, pushing the plaintext validation key into your data engines where public requests can catch it.
|
|
90
|
+
5. **Disk Commit & Hot Swap:** Once the Let's Encrypt engine validates your token, `challengeRemoveFn` clears verification metrics through `object.delete`. The returned payload extracts the public chains, writes `fullchain.pem` and `private-key.pem` files to disk, pushes state blocks to the `organizations` collection array, and triggers `this.proxy.createServer(host)` to complete the loop.
|
|
82
91
|
|
|
83
|
-
|
|
92
|
+
---
|
|
84
93
|
|
|
85
|
-
|
|
94
|
+
## API Reference
|
|
86
95
|
|
|
87
|
-
|
|
96
|
+
### Core Class Instance Methods
|
|
88
97
|
|
|
89
|
-
|
|
98
|
+
| Method | Arguments | Returns | Description |
|
|
99
|
+
| --- | --- | --- | --- |
|
|
100
|
+
| **`init()`** | *None* | `Promise<void>` | **Internal.** Sets up local directory layout, loads/stores account keys, and boots the ACME interface. |
|
|
101
|
+
| **`checkCertificate(host, orgId, pathname)`** | `host: String`, `orgId: String`, `pathname: String` | `Promise<Boolean>` | High-level execution entry point. Bypasses dev routes, checks caches/disks, and manages provisioning rollouts. |
|
|
102
|
+
| **`getCertificate(host, orgId)`** | `host: String`, `orgId: String` | `Promise<Boolean>` | Loads tenant details via `this.crud.getOrganization()`. Reuses valid db keys or invokes a new order lifecycle. |
|
|
103
|
+
| **`requestCertificate(host, pos, hostObj, orgId, wildcard)`** | `String, Number, Object, String, Boolean` | `Promise<Boolean>` | Prepares CSR blocks, handles challenge loops, syncs structures with the database, and saves file buffers. |
|
|
104
|
+
| **`isValid(expires)`** | `expires: Date` | `Boolean|undefined` | Compares signature expiration maps against calculated time horizons to decide if a domain requires active renewal. |
|
|
105
|
+
| **`setCertificate(host, exp, orgId, path, cert, key)`** | `String, Date, String, String, String, String` | `void` | Writes certificate credentials to disk, registers metrics inside internal lists, and tells proxies to bind the port. |
|
|
90
106
|
|
|
91
|
-
|
|
107
|
+
---
|
|
92
108
|
|
|
93
|
-
|
|
109
|
+
## How to Contribute
|
|
94
110
|
|
|
95
|
-
|
|
111
|
+
We encourage contribution to our libraries (you might even score some nifty swag), please see our [CONTRIBUTING.md](https://github.com/CoCreate-app/CoCreate-acme/blob/master/CONTRIBUTING.md) guide for details. If you encounter any bugs or wish to make feature requests, please submit an issue on our [GitHub Issues](https://github.com/CoCreate-app/CoCreate-acme/issues) tracker. We want this library to be community-driven, and CoCreate led. We need your help to realize this goal.
|
|
112
|
+
|
|
113
|
+
For broader system configurations and API guides, please visit our [CoCreate ACME Documentation](https://cocreatejs.com/docs/acme).
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## License
|
|
118
|
+
|
|
119
|
+
This software is dual-licensed under the GNU Affero General Public License version 3 (AGPLv3) and a commercial license.
|
|
96
120
|
|
|
97
|
-
|
|
121
|
+
* **Open Source Use:** For open-source projects and non-commercial use, this software is available under the AGPLv3. For the full license text, see the LICENSE file.
|
|
122
|
+
* **Commercial Use:** For-profit companies and individuals intending to use this software for commercial purposes must obtain a commercial license. The commercial license is available when you sign up for an API key on our website.
|
package/package.json
CHANGED
|
@@ -1,20 +1,23 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cocreate/acme",
|
|
3
|
-
"version": "1.5.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "1.5.2",
|
|
4
|
+
"description": "An automated multi-tenant ACME SSL/TLS certificate provisioning and management wrapper for CoCreate, featuring distributed http-01 challenge syncing via custom CRUD gateways, automatic 2048-bit RSA key generation, staggered renewal windows, and hot proxy re-initialization.",
|
|
5
5
|
"keywords": [
|
|
6
|
+
"cocreate",
|
|
7
|
+
"cocreate-app",
|
|
6
8
|
"acme",
|
|
9
|
+
"acme-client",
|
|
7
10
|
"ssl",
|
|
11
|
+
"tls",
|
|
8
12
|
"certificates",
|
|
13
|
+
"letsencrypt",
|
|
14
|
+
"http-01",
|
|
15
|
+
"dns-01",
|
|
16
|
+
"multi-tenant",
|
|
9
17
|
"automation",
|
|
10
|
-
"
|
|
11
|
-
"
|
|
12
|
-
"security"
|
|
13
|
-
"cocreate",
|
|
14
|
-
"low-code-framework",
|
|
15
|
-
"cocreate-framework",
|
|
16
|
-
"low-code",
|
|
17
|
-
"javascript-framework"
|
|
18
|
+
"proxy",
|
|
19
|
+
"reverse-proxy",
|
|
20
|
+
"security"
|
|
18
21
|
],
|
|
19
22
|
"publishConfig": {
|
|
20
23
|
"access": "public"
|
package/release.config.js
CHANGED
package/src/index.js
CHANGED
|
@@ -21,13 +21,9 @@ import tls from "tls";
|
|
|
21
21
|
import path from "path";
|
|
22
22
|
import dns from "node:dns/promises";
|
|
23
23
|
|
|
24
|
-
// ==========================================
|
|
25
|
-
// Module-Level Sandbox State (ESM Singleton Container)
|
|
26
|
-
// ==========================================
|
|
27
24
|
let server = null; // Direct reference used across all module functions
|
|
28
25
|
const email = "ssl@cocreatejs.com";
|
|
29
26
|
|
|
30
|
-
// Local storage: No sudo required, keeps files isolated to the application runtime
|
|
31
27
|
const keyPath = path.join(process.cwd(), "certificates") + path.sep;
|
|
32
28
|
|
|
33
29
|
let client;
|
|
@@ -208,7 +204,7 @@ export async function request(host, hostPosition, hostObject, organization_id, w
|
|
|
208
204
|
directory: "acme-challenge",
|
|
209
205
|
host: [authz.identifier.value],
|
|
210
206
|
name: challenge.token,
|
|
211
|
-
organization_id:
|
|
207
|
+
organization_id: organization_id,
|
|
212
208
|
path: "/.well-known/acme-challenge/",
|
|
213
209
|
pathname: `/.well-known/acme-challenge/${challenge.token}`,
|
|
214
210
|
public: "true",
|
|
@@ -282,6 +278,12 @@ export async function get(host, organization_id) {
|
|
|
282
278
|
return false;
|
|
283
279
|
}
|
|
284
280
|
|
|
281
|
+
// Dynamic Resolution: If organization_id is not passed (e.g., during a blind SNI callback),
|
|
282
|
+
// extract it directly from the resolved organization record to enforce correct database routing.
|
|
283
|
+
if (!organization_id && organization._id) {
|
|
284
|
+
organization_id = organization._id;
|
|
285
|
+
}
|
|
286
|
+
|
|
285
287
|
if (organization.host) {
|
|
286
288
|
for (let i = 0; i < organization.host.length; i++) {
|
|
287
289
|
if (organization.host[i].name === host) {
|
|
@@ -379,4 +381,4 @@ export default {
|
|
|
379
381
|
check,
|
|
380
382
|
isValid,
|
|
381
383
|
set
|
|
382
|
-
};
|
|
384
|
+
};
|